Commit b492c19c authored by icecraft's avatar icecraft
Browse files

refactor: move some constants or enums defs to config folder

parent bc992433
import copy import copy
import re
from sklearn.cluster import DBSCAN
import numpy as np import numpy as np
from loguru import logger from loguru import logger
import re from sklearn.cluster import DBSCAN
from magic_pdf.libs.boxbase import _is_in_or_part_overlap_with_area_ratio as is_in_layout
from magic_pdf.libs.ocr_content_type import ContentType, BlockType from magic_pdf.config.constants import * # noqa: F403
from magic_pdf.model.magic_model import MagicModel from magic_pdf.config.ocr_content_type import BlockType, ContentType
from magic_pdf.libs.Constants import * from magic_pdf.libs.boxbase import \
_is_in_or_part_overlap_with_area_ratio as is_in_layout
LINE_STOP_FLAG = ['.', '!', '?', '。', '!', '?', ":", ":", ")", ")", ";"] LINE_STOP_FLAG = ['.', '!', '?', '。', '!', '?', ':', ':', ')', ')', ';']
INLINE_EQUATION = ContentType.InlineEquation INLINE_EQUATION = ContentType.InlineEquation
INTERLINE_EQUATION = ContentType.InterlineEquation INTERLINE_EQUATION = ContentType.InterlineEquation
TEXT = ContentType.Text TEXT = ContentType.Text
...@@ -36,7 +37,9 @@ def __detect_list_lines(lines, new_layout_bboxes, lang): ...@@ -36,7 +37,9 @@ def __detect_list_lines(lines, new_layout_bboxes, lang):
ones_indices = [] ones_indices = []
i = 0 i = 0
while i < len(lst): # Loop through the entire list while i < len(lst): # Loop through the entire list
if lst[i] == 1: # If we encounter a '1', we might be at the start of a pattern if (
lst[i] == 1
): # If we encounter a '1', we might be at the start of a pattern
start = i start = i
ones_in_this_interval = [i] ones_in_this_interval = [i]
i += 1 i += 1
...@@ -46,7 +49,10 @@ def __detect_list_lines(lines, new_layout_bboxes, lang): ...@@ -46,7 +49,10 @@ def __detect_list_lines(lines, new_layout_bboxes, lang):
ones_in_this_interval.append(i) ones_in_this_interval.append(i)
i += 1 i += 1
if len(ones_in_this_interval) > 1 or ( if len(ones_in_this_interval) > 1 or (
start < len(lst) - 1 and ones_in_this_interval and lst[start + 1] in [2, 3]): start < len(lst) - 1
and ones_in_this_interval
and lst[start + 1] in [2, 3]
):
indices.append((start, i - 1)) indices.append((start, i - 1))
ones_indices.append(ones_in_this_interval) ones_indices.append(ones_in_this_interval)
else: else:
...@@ -65,7 +71,12 @@ def __detect_list_lines(lines, new_layout_bboxes, lang): ...@@ -65,7 +71,12 @@ def __detect_list_lines(lines, new_layout_bboxes, lang):
while i < len(lst) and lst[i] in [2, 3]: while i < len(lst) and lst[i] in [2, 3]:
i += 1 i += 1
# 验证下一个序列是否符合条件 # 验证下一个序列是否符合条件
if i < len(lst) - 1 and lst[i] == 1 and lst[i + 1] in [2, 3] and lst[i - 1] in [2, 3]: if (
i < len(lst) - 1
and lst[i] == 1
and lst[i + 1] in [2, 3]
and lst[i - 1] in [2, 3]
):
while i < len(lst) and lst[i] in [1, 2, 3]: while i < len(lst) and lst[i] in [1, 2, 3]:
if lst[i] == 1: if lst[i] == 1:
ones_in_this_interval.append(i) ones_in_this_interval.append(i)
...@@ -114,7 +125,7 @@ def __detect_list_lines(lines, new_layout_bboxes, lang): ...@@ -114,7 +125,7 @@ def __detect_list_lines(lines, new_layout_bboxes, lang):
""" """
if len(lines) > 0: if len(lines) > 0:
x_map_tag_dict, min_x_tag = cluster_line_x(lines) x_map_tag_dict, min_x_tag = cluster_line_x(lines)
for l in lines: for l in lines: # noqa: E741
span_text = __get_span_text(l['spans'][0]) span_text = __get_span_text(l['spans'][0])
if not span_text: if not span_text:
line_fea_encode.append(0) line_fea_encode.append(0)
...@@ -142,28 +153,26 @@ def __detect_list_lines(lines, new_layout_bboxes, lang): ...@@ -142,28 +153,26 @@ def __detect_list_lines(lines, new_layout_bboxes, lang):
list_indice, list_start_idx = find_repeating_patterns2(line_fea_encode) list_indice, list_start_idx = find_repeating_patterns2(line_fea_encode)
if len(list_indice) > 0: if len(list_indice) > 0:
if debug_able: if debug_able:
logger.info(f"发现了列表,列表行数:{list_indice}{list_start_idx}") logger.info(f'发现了列表,列表行数:{list_indice}{list_start_idx}')
# TODO check一下这个特列表里缩进的行左侧是不是对齐的。 # TODO check一下这个特列表里缩进的行左侧是不是对齐的。
segments = []
for start, end in list_indice: for start, end in list_indice:
for i in range(start, end + 1): for i in range(start, end + 1):
if i > 0: if i > 0:
if line_fea_encode[i] == 4: if line_fea_encode[i] == 4:
if debug_able: if debug_able:
logger.info(f"列表行的第{i}行不是顶格的") logger.info(f'列表行的第{i}行不是顶格的')
break break
else: else:
if debug_able: if debug_able:
logger.info(f"列表行的第{start}到第{end}行是列表") logger.info(f'列表行的第{start}到第{end}行是列表')
return split_indices(total_lines, list_indice), list_start_idx return split_indices(total_lines, list_indice), list_start_idx
def cluster_line_x(lines: list) -> dict: def cluster_line_x(lines: list) -> dict:
""" """对一个block内所有lines的bbox的x0聚类."""
对一个block内所有lines的bbox的x0聚类
"""
min_distance = 5 min_distance = 5
min_sample = 1 min_sample = 1
x0_lst = np.array([[round(line['bbox'][0]), 0] for line in lines]) x0_lst = np.array([[round(line['bbox'][0]), 0] for line in lines])
...@@ -171,14 +180,16 @@ def cluster_line_x(lines: list) -> dict: ...@@ -171,14 +180,16 @@ def cluster_line_x(lines: list) -> dict:
x0_uniq_label = np.unique(x0_clusters.labels_) x0_uniq_label = np.unique(x0_clusters.labels_)
# x1_lst = np.array([[line['bbox'][2], 0] for line in lines]) # x1_lst = np.array([[line['bbox'][2], 0] for line in lines])
x0_2_new_val = {} # 存储旧值对应的新值映射 x0_2_new_val = {} # 存储旧值对应的新值映射
min_x0 = round(lines[0]["bbox"][0]) min_x0 = round(lines[0]['bbox'][0])
for label in x0_uniq_label: for label in x0_uniq_label:
if label == -1: if label == -1:
continue continue
x0_index_of_label = np.where(x0_clusters.labels_ == label) x0_index_of_label = np.where(x0_clusters.labels_ == label)
x0_raw_val = x0_lst[x0_index_of_label][:, 0] x0_raw_val = x0_lst[x0_index_of_label][:, 0]
x0_new_val = np.min(x0_lst[x0_index_of_label][:, 0]) x0_new_val = np.min(x0_lst[x0_index_of_label][:, 0])
x0_2_new_val.update({round(raw_val): round(x0_new_val) for raw_val in x0_raw_val}) x0_2_new_val.update(
{round(raw_val): round(x0_new_val) for raw_val in x0_raw_val}
)
if x0_new_val < min_x0: if x0_new_val < min_x0:
min_x0 = x0_new_val min_x0 = x0_new_val
return x0_2_new_val, min_x0 return x0_2_new_val, min_x0
...@@ -193,27 +204,41 @@ def if_match_reference_list(text: str) -> bool: ...@@ -193,27 +204,41 @@ def if_match_reference_list(text: str) -> bool:
def __valign_lines(blocks, layout_bboxes): def __valign_lines(blocks, layout_bboxes):
""" """在一个layoutbox内对齐行的左侧和右侧。 扫描行的左侧和右侧,如果x0,
在一个layoutbox内对齐行的左侧和右侧。 x1差距不超过一个阈值,就强行对齐到所处layout的左右两侧(和layout有一段距离)。
扫描行的左侧和右侧,如果x0, x1差距不超过一个阈值,就强行对齐到所处layout的左右两侧(和layout有一段距离)。 3是个经验值,TODO,计算得来,可以设置为1.5个正文字符。"""
3是个经验值,TODO,计算得来,可以设置为1.5个正文字符。
"""
min_distance = 3 min_distance = 3
min_sample = 2 min_sample = 2
new_layout_bboxes = [] new_layout_bboxes = []
# add bbox_fs for para split calculation # add bbox_fs for para split calculation
for block in blocks: for block in blocks:
block["bbox_fs"] = copy.deepcopy(block["bbox"]) block['bbox_fs'] = copy.deepcopy(block['bbox'])
for layout_box in layout_bboxes: for layout_box in layout_bboxes:
blocks_in_layoutbox = [b for b in blocks if blocks_in_layoutbox = [
b["type"] == BlockType.Text and is_in_layout(b['bbox'], layout_box['layout_bbox'])] b
if len(blocks_in_layoutbox) == 0 or len(blocks_in_layoutbox[0]["lines"]) == 0: for b in blocks
if b['type'] == BlockType.Text
and is_in_layout(b['bbox'], layout_box['layout_bbox'])
]
if len(blocks_in_layoutbox) == 0 or len(blocks_in_layoutbox[0]['lines']) == 0:
new_layout_bboxes.append(layout_box['layout_bbox']) new_layout_bboxes.append(layout_box['layout_bbox'])
continue continue
x0_lst = np.array([[line['bbox'][0], 0] for block in blocks_in_layoutbox for line in block['lines']]) x0_lst = np.array(
x1_lst = np.array([[line['bbox'][2], 0] for block in blocks_in_layoutbox for line in block['lines']]) [
[line['bbox'][0], 0]
for block in blocks_in_layoutbox
for line in block['lines']
]
)
x1_lst = np.array(
[
[line['bbox'][2], 0]
for block in blocks_in_layoutbox
for line in block['lines']
]
)
x0_clusters = DBSCAN(eps=min_distance, min_samples=min_sample).fit(x0_lst) x0_clusters = DBSCAN(eps=min_distance, min_samples=min_sample).fit(x0_lst)
x1_clusters = DBSCAN(eps=min_distance, min_samples=min_sample).fit(x1_lst) x1_clusters = DBSCAN(eps=min_distance, min_samples=min_sample).fit(x1_lst)
x0_uniq_label = np.unique(x0_clusters.labels_) x0_uniq_label = np.unique(x0_clusters.labels_)
...@@ -248,11 +273,13 @@ def __valign_lines(blocks, layout_bboxes): ...@@ -248,11 +273,13 @@ def __valign_lines(blocks, layout_bboxes):
# 由于修改了block里的line长度,现在需要重新计算block的bbox # 由于修改了block里的line长度,现在需要重新计算block的bbox
for block in blocks_in_layoutbox: for block in blocks_in_layoutbox:
if len(block["lines"]) > 0: if len(block['lines']) > 0:
block['bbox_fs'] = [min([line['bbox'][0] for line in block['lines']]), block['bbox_fs'] = [
min([line['bbox'][1] for line in block['lines']]), min([line['bbox'][0] for line in block['lines']]),
max([line['bbox'][2] for line in block['lines']]), min([line['bbox'][1] for line in block['lines']]),
max([line['bbox'][3] for line in block['lines']])] max([line['bbox'][2] for line in block['lines']]),
max([line['bbox'][3] for line in block['lines']]),
]
"""新计算layout的bbox,因为block的bbox变了。""" """新计算layout的bbox,因为block的bbox变了。"""
layout_x0 = min([block['bbox_fs'][0] for block in blocks_in_layoutbox]) layout_x0 = min([block['bbox_fs'][0] for block in blocks_in_layoutbox])
layout_y0 = min([block['bbox_fs'][1] for block in blocks_in_layoutbox]) layout_y0 = min([block['bbox_fs'][1] for block in blocks_in_layoutbox])
...@@ -264,18 +291,19 @@ def __valign_lines(blocks, layout_bboxes): ...@@ -264,18 +291,19 @@ def __valign_lines(blocks, layout_bboxes):
def __align_text_in_layout(blocks, layout_bboxes): def __align_text_in_layout(blocks, layout_bboxes):
""" """由于ocr出来的line,有时候会在前后有一段空白,这个时候需要对文本进行对齐,超出的部分被layout左右侧截断。"""
由于ocr出来的line,有时候会在前后有一段空白,这个时候需要对文本进行对齐,超出的部分被layout左右侧截断。
"""
for layout in layout_bboxes: for layout in layout_bboxes:
lb = layout['layout_bbox'] lb = layout['layout_bbox']
blocks_in_layoutbox = [block for block in blocks if blocks_in_layoutbox = [
block["type"] == BlockType.Text and is_in_layout(block['bbox'], lb)] block
for block in blocks
if block['type'] == BlockType.Text and is_in_layout(block['bbox'], lb)
]
if len(blocks_in_layoutbox) == 0: if len(blocks_in_layoutbox) == 0:
continue continue
for block in blocks_in_layoutbox: for block in blocks_in_layoutbox:
for line in block.get("lines", []): for line in block.get('lines', []):
x0, x1 = line['bbox'][0], line['bbox'][2] x0, x1 = line['bbox'][0], line['bbox'][2]
if x0 < lb[0]: if x0 < lb[0]:
line['bbox'][0] = lb[0] line['bbox'][0] = lb[0]
...@@ -284,9 +312,7 @@ def __align_text_in_layout(blocks, layout_bboxes): ...@@ -284,9 +312,7 @@ def __align_text_in_layout(blocks, layout_bboxes):
def __common_pre_proc(blocks, layout_bboxes): def __common_pre_proc(blocks, layout_bboxes):
""" """不分语言的,对文本进行预处理."""
不分语言的,对文本进行预处理
"""
# __add_line_period(blocks, layout_bboxes) # __add_line_period(blocks, layout_bboxes)
__align_text_in_layout(blocks, layout_bboxes) __align_text_in_layout(blocks, layout_bboxes)
aligned_layout_bboxes = __valign_lines(blocks, layout_bboxes) aligned_layout_bboxes = __valign_lines(blocks, layout_bboxes)
...@@ -295,32 +321,30 @@ def __common_pre_proc(blocks, layout_bboxes): ...@@ -295,32 +321,30 @@ def __common_pre_proc(blocks, layout_bboxes):
def __pre_proc_zh_blocks(blocks, layout_bboxes): def __pre_proc_zh_blocks(blocks, layout_bboxes):
""" """对中文文本进行分段预处理."""
对中文文本进行分段预处理
"""
pass pass
def __pre_proc_en_blocks(blocks, layout_bboxes): def __pre_proc_en_blocks(blocks, layout_bboxes):
""" """对英文文本进行分段预处理."""
对英文文本进行分段预处理
"""
pass pass
def __group_line_by_layout(blocks, layout_bboxes): def __group_line_by_layout(blocks, layout_bboxes):
""" """每个layout内的行进行聚合."""
每个layout内的行进行聚合
"""
# 因为只是一个block一行目前, 一个block就是一个段落 # 因为只是一个block一行目前, 一个block就是一个段落
blocks_group = [] blocks_group = []
for lyout in layout_bboxes: for lyout in layout_bboxes:
blocks_in_layout = [block for block in blocks if is_in_layout(block.get('bbox_fs', None), lyout['layout_bbox'])] blocks_in_layout = [
block
for block in blocks
if is_in_layout(block.get('bbox_fs', None), lyout['layout_bbox'])
]
blocks_group.append(blocks_in_layout) blocks_group.append(blocks_in_layout)
return blocks_group return blocks_group
def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang="en"): def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang='en'):
""" """
lines_group 进行行分段——layout内部进行分段。lines_group内每个元素是一个Layoutbox内的所有行。 lines_group 进行行分段——layout内部进行分段。lines_group内每个元素是一个Layoutbox内的所有行。
1. 先计算每个group的左右边界。 1. 先计算每个group的左右边界。
...@@ -336,17 +360,20 @@ def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang="en"): ...@@ -336,17 +360,20 @@ def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang="en"):
if len(blocks) == 0: if len(blocks) == 0:
list_info.append([False, False]) list_info.append([False, False])
continue continue
if blocks[0]["type"] != BlockType.Text and blocks[-1]["type"] != BlockType.Text: if blocks[0]['type'] != BlockType.Text and blocks[-1]['type'] != BlockType.Text:
list_info.append([False, False]) list_info.append([False, False])
continue continue
if blocks[0]["type"] != BlockType.Text: if blocks[0]['type'] != BlockType.Text:
is_start_list = False is_start_list = False
if blocks[-1]["type"] != BlockType.Text: if blocks[-1]['type'] != BlockType.Text:
is_end_list = False is_end_list = False
lines = [line for block in blocks if lines = [
block["type"] == BlockType.Text for line in line
block['lines']] for block in blocks
if block['type'] == BlockType.Text
for line in block['lines']
]
total_lines = len(lines) total_lines = len(lines)
if total_lines == 1 or total_lines == 0: if total_lines == 1 or total_lines == 0:
list_info.append([False, False]) list_info.append([False, False])
...@@ -359,7 +386,9 @@ def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang="en"): ...@@ -359,7 +386,9 @@ def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang="en"):
2. 左对齐的列表块(其特点是左侧顶格的行数小于等于非顶格的行数,非定格首字母会有小写,顶格90%是大写。并且左侧顶格行数大于1,大于1是为了这种模式连续出现才能称之为列表) 2. 左对齐的列表块(其特点是左侧顶格的行数小于等于非顶格的行数,非定格首字母会有小写,顶格90%是大写。并且左侧顶格行数大于1,大于1是为了这种模式连续出现才能称之为列表)
这样的文本块,顶格的为一个段落开头,紧随其后非顶格的行属于这个段落。 这样的文本块,顶格的为一个段落开头,紧随其后非顶格的行属于这个段落。
""" """
text_segments, list_start_line = __detect_list_lines(lines, new_layout_bbox, lang) text_segments, list_start_line = __detect_list_lines(
lines, new_layout_bbox, lang
)
"""根据list_range,把lines分成几个部分 """根据list_range,把lines分成几个部分
""" """
...@@ -368,10 +397,17 @@ def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang="en"): ...@@ -368,10 +397,17 @@ def __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang="en"):
for i in range(0, len(list_start)): for i in range(0, len(list_start)):
index = list_start[i] - 1 index = list_start[i] - 1
if index >= 0: if index >= 0:
if "content" in lines[index]["spans"][-1] and lines[index]["spans"][-1].get('type', '') not in [ if 'content' in lines[index]['spans'][-1] and lines[index][
ContentType.InlineEquation, ContentType.InterlineEquation]: 'spans'
lines[index]["spans"][-1]["content"] += '\n\n' ][-1].get('type', '') not in [
layout_list_info = [False, False] # 这个layout最后是不是列表,记录每一个layout里是不是列表开头,列表结尾 ContentType.InlineEquation,
ContentType.InterlineEquation,
]:
lines[index]['spans'][-1]['content'] += '\n\n'
layout_list_info = [
False,
False,
] # 这个layout最后是不是列表,记录每一个layout里是不是列表开头,列表结尾
for content_type, start, end in text_segments: for content_type, start, end in text_segments:
if content_type == 'list': if content_type == 'list':
if start == 0 and is_start_list is None: if start == 0 and is_start_list is None:
...@@ -388,8 +424,7 @@ def __split_para_lines(lines: list, text_blocks: list) -> list: ...@@ -388,8 +424,7 @@ def __split_para_lines(lines: list, text_blocks: list) -> list:
other_paras = [] other_paras = []
text_lines = [] text_lines = []
for line in lines: for line in lines:
spans_types = [span['type'] for span in line]
spans_types = [span["type"] for span in line]
if ContentType.Table in spans_types: if ContentType.Table in spans_types:
other_paras.append([line]) other_paras.append([line])
continue continue
...@@ -402,20 +437,22 @@ def __split_para_lines(lines: list, text_blocks: list) -> list: ...@@ -402,20 +437,22 @@ def __split_para_lines(lines: list, text_blocks: list) -> list:
text_lines.append(line) text_lines.append(line)
for block in text_blocks: for block in text_blocks:
block_bbox = block["bbox"] block_bbox = block['bbox']
para = [] para = []
for line in text_lines: for line in text_lines:
bbox = line["bbox"] bbox = line['bbox']
if is_in_layout(bbox, block_bbox): if is_in_layout(bbox, block_bbox):
para.append(line) para.append(line)
if len(para) > 0: if len(para) > 0:
text_paras.append(para) text_paras.append(para)
paras = other_paras.extend(text_paras) paras = other_paras.extend(text_paras)
paras_sorted = sorted(paras, key=lambda x: x[0]["bbox"][1]) paras_sorted = sorted(paras, key=lambda x: x[0]['bbox'][1])
return paras_sorted return paras_sorted
def __connect_list_inter_layout(blocks_group, new_layout_bbox, layout_list_info, page_num, lang): def __connect_list_inter_layout(
blocks_group, new_layout_bbox, layout_list_info, page_num, lang
):
global debug_able global debug_able
""" """
如果上个layout的最后一个段落是列表,下一个layout的第一个段落也是列表,那么将他们连接起来。 TODO 因为没有区分列表和段落,所以这个方法暂时不实现。 如果上个layout的最后一个段落是列表,下一个layout的第一个段落也是列表,那么将他们连接起来。 TODO 因为没有区分列表和段落,所以这个方法暂时不实现。
...@@ -429,74 +466,108 @@ def __connect_list_inter_layout(blocks_group, new_layout_bbox, layout_list_info, ...@@ -429,74 +466,108 @@ def __connect_list_inter_layout(blocks_group, new_layout_bbox, layout_list_info,
continue continue
pre_layout_list_info = layout_list_info[i - 1] pre_layout_list_info = layout_list_info[i - 1]
next_layout_list_info = layout_list_info[i] next_layout_list_info = layout_list_info[i]
pre_last_para = blocks_group[i - 1][-1].get("lines", []) pre_last_para = blocks_group[i - 1][-1].get('lines', [])
next_paras = blocks_group[i] next_paras = blocks_group[i]
next_first_para = next_paras[0] next_first_para = next_paras[0]
if pre_layout_list_info[1] and not next_layout_list_info[0] and next_first_para[ if (
"type"] == BlockType.Text: # 前一个是列表结尾,后一个是非列表开头,此时检测是否有相同的缩进 pre_layout_list_info[1]
and not next_layout_list_info[0]
and next_first_para['type'] == BlockType.Text
): # 前一个是列表结尾,后一个是非列表开头,此时检测是否有相同的缩进
if debug_able: if debug_able:
logger.info(f"连接page {page_num} 内的list") logger.info(f'连接page {page_num} 内的list')
# 向layout_paras[i] 寻找开头具有相同缩进的连续的行 # 向layout_paras[i] 寻找开头具有相同缩进的连续的行
may_list_lines = [] may_list_lines = []
lines = next_first_para.get("lines", []) lines = next_first_para.get('lines', [])
for line in lines: for line in lines:
if line['bbox'][0] > __find_layout_bbox_by_line(line['bbox'], new_layout_bbox)[0]: if (
line['bbox'][0]
> __find_layout_bbox_by_line(line['bbox'], new_layout_bbox)[0]
):
may_list_lines.append(line) may_list_lines.append(line)
else: else:
break break
# 如果这些行的缩进是相等的,那么连到上一个layout的最后一个段落上。 # 如果这些行的缩进是相等的,那么连到上一个layout的最后一个段落上。
if len(may_list_lines) > 0 and len(set([x['bbox'][0] for x in may_list_lines])) == 1: if (
len(may_list_lines) > 0
and len(set([x['bbox'][0] for x in may_list_lines])) == 1
):
pre_last_para.extend(may_list_lines) pre_last_para.extend(may_list_lines)
next_first_para["lines"] = next_first_para["lines"][len(may_list_lines):] next_first_para['lines'] = next_first_para['lines'][
len(may_list_lines) :
return blocks_group, [layout_list_info[0][0], layout_list_info[-1][1]] # 同时还返回了这个页面级别的开头、结尾是不是列表的信息 ]
return blocks_group, [
def __connect_list_inter_page(pre_page_paras, next_page_paras, pre_page_layout_bbox, next_page_layout_bbox, layout_list_info[0][0],
pre_page_list_info, next_page_list_info, page_num, lang): layout_list_info[-1][1],
""" ] # 同时还返回了这个页面级别的开头、结尾是不是列表的信息
如果上个layout的最后一个段落是列表,下一个layout的第一个段落也是列表,那么将他们连接起来。 TODO 因为没有区分列表和段落,所以这个方法暂时不实现。
根据layout_list_info判断是不是列表。,下个layout的第一个段如果不是列表,那么看他们是否有几行都有相同的缩进。
""" def __connect_list_inter_page(
if len(pre_page_paras) == 0 or len(next_page_paras) == 0: # 0的时候最后的return 会出错 pre_page_paras,
next_page_paras,
pre_page_layout_bbox,
next_page_layout_bbox,
pre_page_list_info,
next_page_list_info,
page_num,
lang,
):
"""如果上个layout的最后一个段落是列表,下一个layout的第一个段落也是列表,那么将他们连接起来。 TODO
因为没有区分列表和段落,所以这个方法暂时不实现。
根据layout_list_info判断是不是列表。,下个layout的第一个段如果不是列表,那么看他们是否有几行都有相同的缩进。"""
if (
len(pre_page_paras) == 0 or len(next_page_paras) == 0
): # 0的时候最后的return 会出错
return False return False
if len(pre_page_paras[-1]) == 0 or len(next_page_paras[0]) == 0: if len(pre_page_paras[-1]) == 0 or len(next_page_paras[0]) == 0:
return False return False
if pre_page_paras[-1][-1]["type"] != BlockType.Text or next_page_paras[0][0]["type"] != BlockType.Text: if (
pre_page_paras[-1][-1]['type'] != BlockType.Text
or next_page_paras[0][0]['type'] != BlockType.Text
):
return False return False
if pre_page_list_info[1] and not next_page_list_info[0]: # 前一个是列表结尾,后一个是非列表开头,此时检测是否有相同的缩进 if (
pre_page_list_info[1] and not next_page_list_info[0]
): # 前一个是列表结尾,后一个是非列表开头,此时检测是否有相同的缩进
if debug_able: if debug_able:
logger.info(f"连接page {page_num} 内的list") logger.info(f'连接page {page_num} 内的list')
# 向layout_paras[i] 寻找开头具有相同缩进的连续的行 # 向layout_paras[i] 寻找开头具有相同缩进的连续的行
may_list_lines = [] may_list_lines = []
next_page_first_para = next_page_paras[0][0] next_page_first_para = next_page_paras[0][0]
if next_page_first_para["type"] == BlockType.Text: if next_page_first_para['type'] == BlockType.Text:
lines = next_page_first_para["lines"] lines = next_page_first_para['lines']
for line in lines: for line in lines:
if line['bbox'][0] > __find_layout_bbox_by_line(line['bbox'], next_page_layout_bbox)[0]: if (
line['bbox'][0]
> __find_layout_bbox_by_line(line['bbox'], next_page_layout_bbox)[0]
):
may_list_lines.append(line) may_list_lines.append(line)
else: else:
break break
# 如果这些行的缩进是相等的,那么连到上一个layout的最后一个段落上。 # 如果这些行的缩进是相等的,那么连到上一个layout的最后一个段落上。
if len(may_list_lines) > 0 and len(set([x['bbox'][0] for x in may_list_lines])) == 1: if (
len(may_list_lines) > 0
and len(set([x['bbox'][0] for x in may_list_lines])) == 1
):
# pre_page_paras[-1].append(may_list_lines) # pre_page_paras[-1].append(may_list_lines)
# 下一页合并到上一页最后一段,打一个cross_page的标签 # 下一页合并到上一页最后一段,打一个cross_page的标签
for line in may_list_lines: for line in may_list_lines:
for span in line["spans"]: for span in line['spans']:
span[CROSS_PAGE] = True span[CROSS_PAGE] = True # noqa: F405
pre_page_paras[-1][-1]["lines"].extend(may_list_lines) pre_page_paras[-1][-1]['lines'].extend(may_list_lines)
next_page_first_para["lines"] = next_page_first_para["lines"][len(may_list_lines):] next_page_first_para['lines'] = next_page_first_para['lines'][
len(may_list_lines) :
]
return True return True
return False return False
def __find_layout_bbox_by_line(line_bbox, layout_bboxes): def __find_layout_bbox_by_line(line_bbox, layout_bboxes):
""" """根据line找到所在的layout."""
根据line找到所在的layout
"""
for layout in layout_bboxes: for layout in layout_bboxes:
if is_in_layout(line_bbox, layout): if is_in_layout(line_bbox, layout):
return layout return layout
...@@ -525,39 +596,59 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox): ...@@ -525,39 +596,59 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox):
connected_layout_blocks.append(blocks_group[i]) connected_layout_blocks.append(blocks_group[i])
continue continue
# text类型的段才需要考虑layout间的合并 # text类型的段才需要考虑layout间的合并
if blocks_group[i - 1][-1]["type"] != BlockType.Text or blocks_group[i][0]["type"] != BlockType.Text: if (
blocks_group[i - 1][-1]['type'] != BlockType.Text
or blocks_group[i][0]['type'] != BlockType.Text
):
connected_layout_blocks.append(blocks_group[i]) connected_layout_blocks.append(blocks_group[i])
continue continue
if len(blocks_group[i - 1][-1]["lines"]) == 0 or len(blocks_group[i][0]["lines"]) == 0: if (
len(blocks_group[i - 1][-1]['lines']) == 0
or len(blocks_group[i][0]['lines']) == 0
):
connected_layout_blocks.append(blocks_group[i]) connected_layout_blocks.append(blocks_group[i])
continue continue
pre_last_line = blocks_group[i - 1][-1]["lines"][-1] pre_last_line = blocks_group[i - 1][-1]['lines'][-1]
next_first_line = blocks_group[i][0]["lines"][0] next_first_line = blocks_group[i][0]['lines'][0]
except Exception as e: except Exception:
logger.error(f"page layout {i} has no line") logger.error(f'page layout {i} has no line')
continue continue
pre_last_line_text = ''.join([__get_span_text(span) for span in pre_last_line['spans']]) pre_last_line_text = ''.join(
[__get_span_text(span) for span in pre_last_line['spans']]
)
pre_last_line_type = pre_last_line['spans'][-1]['type'] pre_last_line_type = pre_last_line['spans'][-1]['type']
next_first_line_text = ''.join([__get_span_text(span) for span in next_first_line['spans']]) next_first_line_text = ''.join(
[__get_span_text(span) for span in next_first_line['spans']]
)
next_first_line_type = next_first_line['spans'][0]['type'] next_first_line_type = next_first_line['spans'][0]['type']
if pre_last_line_type not in [TEXT, INLINE_EQUATION] or next_first_line_type not in [TEXT, INLINE_EQUATION]: if pre_last_line_type not in [
TEXT,
INLINE_EQUATION,
] or next_first_line_type not in [TEXT, INLINE_EQUATION]:
connected_layout_blocks.append(blocks_group[i]) connected_layout_blocks.append(blocks_group[i])
continue continue
pre_layout = __find_layout_bbox_by_line(pre_last_line['bbox'], new_layout_bbox) pre_layout = __find_layout_bbox_by_line(pre_last_line['bbox'], new_layout_bbox)
next_layout = __find_layout_bbox_by_line(next_first_line['bbox'], new_layout_bbox) next_layout = __find_layout_bbox_by_line(
next_first_line['bbox'], new_layout_bbox
)
pre_x2_max = pre_layout[2] if pre_layout else -1 pre_x2_max = pre_layout[2] if pre_layout else -1
next_x0_min = next_layout[0] if next_layout else -1 next_x0_min = next_layout[0] if next_layout else -1
pre_last_line_text = pre_last_line_text.strip() pre_last_line_text = pre_last_line_text.strip()
next_first_line_text = next_first_line_text.strip() next_first_line_text = next_first_line_text.strip()
if pre_last_line['bbox'][2] == pre_x2_max and pre_last_line_text and pre_last_line_text[ if (
-1] not in LINE_STOP_FLAG and \ pre_last_line['bbox'][2] == pre_x2_max
next_first_line['bbox'][0] == next_x0_min: # 前面一行沾满了整个行,并且没有结尾符号.下一行没有空白开头。 and pre_last_line_text
and pre_last_line_text[-1] not in LINE_STOP_FLAG
and next_first_line['bbox'][0] == next_x0_min
): # 前面一行沾满了整个行,并且没有结尾符号.下一行没有空白开头。
"""连接段落条件成立,将前一个layout的段落和后一个layout的段落连接。""" """连接段落条件成立,将前一个layout的段落和后一个layout的段落连接。"""
connected_layout_blocks[-1][-1]["lines"].extend(blocks_group[i][0]["lines"]) connected_layout_blocks[-1][-1]['lines'].extend(blocks_group[i][0]['lines'])
blocks_group[i][0]["lines"] = [] # 删除后一个layout第一个段落中的lines,因为他已经被合并到前一个layout的最后一个段落了 blocks_group[i][0][
blocks_group[i][0][LINES_DELETED] = True 'lines'
] = [] # 删除后一个layout第一个段落中的lines,因为他已经被合并到前一个layout的最后一个段落了
blocks_group[i][0][LINES_DELETED] = True # noqa: F405
# if len(layout_paras[i]) == 0: # if len(layout_paras[i]) == 0:
# layout_paras.pop(i) # layout_paras.pop(i)
# else: # else:
...@@ -569,8 +660,14 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox): ...@@ -569,8 +660,14 @@ def __connect_para_inter_layoutbox(blocks_group, new_layout_bbox):
return connected_layout_blocks return connected_layout_blocks
def __connect_para_inter_page(pre_page_paras, next_page_paras, pre_page_layout_bbox, next_page_layout_bbox, page_num, def __connect_para_inter_page(
lang): pre_page_paras,
next_page_paras,
pre_page_layout_bbox,
next_page_layout_bbox,
page_num,
lang,
):
""" """
连接起来相邻两个页面的段落——前一个页面最后一个段落和后一个页面的第一个段落。 连接起来相邻两个页面的段落——前一个页面最后一个段落和后一个页面的第一个段落。
是否可以连接的条件: 是否可以连接的条件:
...@@ -578,33 +675,53 @@ def __connect_para_inter_page(pre_page_paras, next_page_paras, pre_page_layout_b ...@@ -578,33 +675,53 @@ def __connect_para_inter_page(pre_page_paras, next_page_paras, pre_page_layout_b
2. 后一个页面的第一个段落第一行没有空白开头。 2. 后一个页面的第一个段落第一行没有空白开头。
""" """
# 有的页面可能压根没有文字 # 有的页面可能压根没有文字
if len(pre_page_paras) == 0 or len(next_page_paras) == 0 or len(pre_page_paras[0]) == 0 or len( if (
next_page_paras[0]) == 0: # TODO [[]]为什么出现在pre_page_paras里? len(pre_page_paras) == 0
or len(next_page_paras) == 0
or len(pre_page_paras[0]) == 0
or len(next_page_paras[0]) == 0
): # TODO [[]]为什么出现在pre_page_paras里?
return False return False
pre_last_block = pre_page_paras[-1][-1] pre_last_block = pre_page_paras[-1][-1]
next_first_block = next_page_paras[0][0] next_first_block = next_page_paras[0][0]
if pre_last_block["type"] != BlockType.Text or next_first_block["type"] != BlockType.Text: if (
pre_last_block['type'] != BlockType.Text
or next_first_block['type'] != BlockType.Text
):
return False return False
if len(pre_last_block["lines"]) == 0 or len(next_first_block["lines"]) == 0: if len(pre_last_block['lines']) == 0 or len(next_first_block['lines']) == 0:
return False return False
pre_last_para = pre_last_block["lines"] pre_last_para = pre_last_block['lines']
next_first_para = next_first_block["lines"] next_first_para = next_first_block['lines']
pre_last_line = pre_last_para[-1] pre_last_line = pre_last_para[-1]
next_first_line = next_first_para[0] next_first_line = next_first_para[0]
pre_last_line_text = ''.join([__get_span_text(span) for span in pre_last_line['spans']]) pre_last_line_text = ''.join(
[__get_span_text(span) for span in pre_last_line['spans']]
)
pre_last_line_type = pre_last_line['spans'][-1]['type'] pre_last_line_type = pre_last_line['spans'][-1]['type']
next_first_line_text = ''.join([__get_span_text(span) for span in next_first_line['spans']]) next_first_line_text = ''.join(
[__get_span_text(span) for span in next_first_line['spans']]
)
next_first_line_type = next_first_line['spans'][0]['type'] next_first_line_type = next_first_line['spans'][0]['type']
if pre_last_line_type not in [TEXT, INLINE_EQUATION] or next_first_line_type not in [TEXT, if pre_last_line_type not in [
INLINE_EQUATION]: # TODO,真的要做好,要考虑跨table, image, 行间的情况 TEXT,
INLINE_EQUATION,
] or next_first_line_type not in [
TEXT,
INLINE_EQUATION,
]: # TODO,真的要做好,要考虑跨table, image, 行间的情况
# 不是文本,不连接 # 不是文本,不连接
return False return False
pre_x2_max_bbox = __find_layout_bbox_by_line(pre_last_line['bbox'], pre_page_layout_bbox) pre_x2_max_bbox = __find_layout_bbox_by_line(
pre_last_line['bbox'], pre_page_layout_bbox
)
if not pre_x2_max_bbox: if not pre_x2_max_bbox:
return False return False
next_x0_min_bbox = __find_layout_bbox_by_line(next_first_line['bbox'], next_page_layout_bbox) next_x0_min_bbox = __find_layout_bbox_by_line(
next_first_line['bbox'], next_page_layout_bbox
)
if not next_x0_min_bbox: if not next_x0_min_bbox:
return False return False
...@@ -613,18 +730,21 @@ def __connect_para_inter_page(pre_page_paras, next_page_paras, pre_page_layout_b ...@@ -613,18 +730,21 @@ def __connect_para_inter_page(pre_page_paras, next_page_paras, pre_page_layout_b
pre_last_line_text = pre_last_line_text.strip() pre_last_line_text = pre_last_line_text.strip()
next_first_line_text = next_first_line_text.strip() next_first_line_text = next_first_line_text.strip()
if pre_last_line['bbox'][2] == pre_x2_max and pre_last_line_text[-1] not in LINE_STOP_FLAG and \ if (
next_first_line['bbox'][0] == next_x0_min: # 前面一行沾满了整个行,并且没有结尾符号.下一行没有空白开头。 pre_last_line['bbox'][2] == pre_x2_max
and pre_last_line_text[-1] not in LINE_STOP_FLAG
and next_first_line['bbox'][0] == next_x0_min
): # 前面一行沾满了整个行,并且没有结尾符号.下一行没有空白开头。
"""连接段落条件成立,将前一个layout的段落和后一个layout的段落连接。""" """连接段落条件成立,将前一个layout的段落和后一个layout的段落连接。"""
# 下一页合并到上一页最后一段,打一个cross_page的标签 # 下一页合并到上一页最后一段,打一个cross_page的标签
for line in next_first_para: for line in next_first_para:
for span in line["spans"]: for span in line['spans']:
span[CROSS_PAGE] = True span[CROSS_PAGE] = True # noqa: F405
pre_last_para.extend(next_first_para) pre_last_para.extend(next_first_para)
# next_page_paras[0].pop(0) # 删除后一个页面的第一个段落, 因为他已经被合并到前一个页面的最后一个段落了。 # next_page_paras[0].pop(0) # 删除后一个页面的第一个段落, 因为他已经被合并到前一个页面的最后一个段落了。
next_page_paras[0][0]["lines"] = [] next_page_paras[0][0]['lines'] = []
next_page_paras[0][0][LINES_DELETED] = True next_page_paras[0][0][LINES_DELETED] = True # noqa: F405
return True return True
else: else:
return False return False
...@@ -667,38 +787,73 @@ def __connect_middle_align_text(page_paras, new_layout_bbox, page_num, lang): ...@@ -667,38 +787,73 @@ def __connect_middle_align_text(page_paras, new_layout_bbox, page_num, lang):
single_line_paras_tag = [] single_line_paras_tag = []
for i in range(len(layout_para)): for i in range(len(layout_para)):
# single_line_paras_tag.append(len(layout_para[i]) == 1 and layout_para[i][0]['spans'][0]['type'] == TEXT) # single_line_paras_tag.append(len(layout_para[i]) == 1 and layout_para[i][0]['spans'][0]['type'] == TEXT)
single_line_paras_tag.append(layout_para[i]['type'] == BlockType.Text and len(layout_para[i]["lines"]) == 1) single_line_paras_tag.append(
layout_para[i]['type'] == BlockType.Text
and len(layout_para[i]['lines']) == 1
)
"""找出来连续的单行文本,如果连续行高度相同,那么合并为一个段落。""" """找出来连续的单行文本,如果连续行高度相同,那么合并为一个段落。"""
consecutive_single_line_indices = find_consecutive_true_regions(single_line_paras_tag) consecutive_single_line_indices = find_consecutive_true_regions(
single_line_paras_tag
)
if len(consecutive_single_line_indices) > 0: if len(consecutive_single_line_indices) > 0:
"""检查这些行是否是高度相同的,居中的""" """检查这些行是否是高度相同的,居中的."""
for start, end in consecutive_single_line_indices: for start, end in consecutive_single_line_indices:
# start += index_offset # start += index_offset
# end += index_offset # end += index_offset
line_hi = np.array([block["lines"][0]['bbox'][3] - block["lines"][0]['bbox'][1] for block in line_hi = np.array(
layout_para[start:end + 1]]) [
first_line_text = ''.join([__get_span_text(span) for span in layout_para[start]["lines"][0]['spans']]) block['lines'][0]['bbox'][3] - block['lines'][0]['bbox'][1]
if "Table" in first_line_text or "Figure" in first_line_text: for block in layout_para[start : end + 1]
]
)
first_line_text = ''.join(
[
__get_span_text(span)
for span in layout_para[start]['lines'][0]['spans']
]
)
if 'Table' in first_line_text or 'Figure' in first_line_text:
pass pass
if debug_able: if debug_able:
logger.info(line_hi.std()) logger.info(line_hi.std())
if line_hi.std() < 2: if line_hi.std() < 2:
"""行高度相同,那么判断是否居中""" """行高度相同,那么判断是否居中."""
all_left_x0 = [block["lines"][0]['bbox'][0] for block in layout_para[start:end + 1]] all_left_x0 = [
all_right_x1 = [block["lines"][0]['bbox'][2] for block in layout_para[start:end + 1]] block['lines'][0]['bbox'][0]
for block in layout_para[start : end + 1]
]
all_right_x1 = [
block['lines'][0]['bbox'][2]
for block in layout_para[start : end + 1]
]
layout_center = (layout_box[0] + layout_box[2]) / 2 layout_center = (layout_box[0] + layout_box[2]) / 2
if all([x0 < layout_center < x1 for x0, x1 in zip(all_left_x0, all_right_x1)]) \ if (
and not all([x0 == layout_box[0] for x0 in all_left_x0]) \ all(
and not all([x1 == layout_box[2] for x1 in all_right_x1]): [
merge_para = [block["lines"][0] for block in layout_para[start:end + 1]] x0 < layout_center < x1
para_text = ''.join([__get_span_text(span) for line in merge_para for span in line['spans']]) for x0, x1 in zip(all_left_x0, all_right_x1)
]
)
and not all([x0 == layout_box[0] for x0 in all_left_x0])
and not all([x1 == layout_box[2] for x1 in all_right_x1])
):
merge_para = [
block['lines'][0] for block in layout_para[start : end + 1]
]
para_text = ''.join(
[
__get_span_text(span)
for line in merge_para
for span in line['spans']
]
)
if debug_able: if debug_able:
logger.info(para_text) logger.info(para_text)
layout_para[start]["lines"] = merge_para layout_para[start]['lines'] = merge_para
for i_para in range(start + 1, end + 1): for i_para in range(start + 1, end + 1):
layout_para[i_para]["lines"] = [] layout_para[i_para]['lines'] = []
layout_para[i_para][LINES_DELETED] = True layout_para[i_para][LINES_DELETED] = True # noqa: F405
# layout_para[start:end + 1] = [merge_para] # layout_para[start:end + 1] = [merge_para]
# index_offset -= end - start # index_offset -= end - start
...@@ -707,18 +862,13 @@ def __connect_middle_align_text(page_paras, new_layout_bbox, page_num, lang): ...@@ -707,18 +862,13 @@ def __connect_middle_align_text(page_paras, new_layout_bbox, page_num, lang):
def __merge_signle_list_text(page_paras, new_layout_bbox, page_num, lang): def __merge_signle_list_text(page_paras, new_layout_bbox, page_num, lang):
""" """找出来连续的单行文本,如果首行顶格,接下来的几个单行段落缩进对齐,那么合并为一个段落。"""
找出来连续的单行文本,如果首行顶格,接下来的几个单行段落缩进对齐,那么合并为一个段落。
"""
pass pass
def __do_split_page(blocks, layout_bboxes, new_layout_bbox, page_num, lang): def __do_split_page(blocks, layout_bboxes, new_layout_bbox, page_num, lang):
""" """根据line和layout情况进行分段 先实现一个根据行末尾特征分段的简单方法。"""
根据line和layout情况进行分段
先实现一个根据行末尾特征分段的简单方法。
"""
""" """
算法思路: 算法思路:
1. 扫描layout里每一行,找出来行尾距离layout有边界有一定距离的行。 1. 扫描layout里每一行,找出来行尾距离layout有边界有一定距离的行。
...@@ -727,15 +877,20 @@ def __do_split_page(blocks, layout_bboxes, new_layout_bbox, page_num, lang): ...@@ -727,15 +877,20 @@ def __do_split_page(blocks, layout_bboxes, new_layout_bbox, page_num, lang):
4. 图、表,目前独占一行,不考虑分段。 4. 图、表,目前独占一行,不考虑分段。
""" """
blocks_group = __group_line_by_layout(blocks, layout_bboxes) # block内分段 blocks_group = __group_line_by_layout(blocks, layout_bboxes) # block内分段
layout_list_info = __split_para_in_layoutbox(blocks_group, new_layout_bbox, lang) # layout内分段 layout_list_info = __split_para_in_layoutbox(
blocks_group, page_list_info = __connect_list_inter_layout(blocks_group, new_layout_bbox, layout_list_info, blocks_group, new_layout_bbox, lang
page_num, lang) # layout之间连接列表段落 ) # layout内分段
connected_layout_blocks = __connect_para_inter_layoutbox(blocks_group, new_layout_bbox) # layout间链接段落 blocks_group, page_list_info = __connect_list_inter_layout(
blocks_group, new_layout_bbox, layout_list_info, page_num, lang
) # layout之间连接列表段落
connected_layout_blocks = __connect_para_inter_layoutbox(
blocks_group, new_layout_bbox
) # layout间链接段落
return connected_layout_blocks, page_list_info return connected_layout_blocks, page_list_info
def para_split(pdf_info_dict, debug_mode, lang="en"): def para_split(pdf_info_dict, debug_mode, lang='en'):
global debug_able global debug_able
debug_able = debug_mode debug_able = debug_mode
new_layout_of_pages = [] # 数组的数组,每个元素是一个页面的layoutS new_layout_of_pages = [] # 数组的数组,每个元素是一个页面的layoutS
...@@ -745,7 +900,9 @@ def para_split(pdf_info_dict, debug_mode, lang="en"): ...@@ -745,7 +900,9 @@ def para_split(pdf_info_dict, debug_mode, lang="en"):
layout_bboxes = page['layout_bboxes'] layout_bboxes = page['layout_bboxes']
new_layout_bbox = __common_pre_proc(blocks, layout_bboxes) new_layout_bbox = __common_pre_proc(blocks, layout_bboxes)
new_layout_of_pages.append(new_layout_bbox) new_layout_of_pages.append(new_layout_bbox)
splited_blocks, page_list_info = __do_split_page(blocks, layout_bboxes, new_layout_bbox, page_num, lang) splited_blocks, page_list_info = __do_split_page(
blocks, layout_bboxes, new_layout_bbox, page_num, lang
)
all_page_list_info.append(page_list_info) all_page_list_info.append(page_list_info)
page['para_blocks'] = splited_blocks page['para_blocks'] = splited_blocks
...@@ -759,18 +916,31 @@ def para_split(pdf_info_dict, debug_mode, lang="en"): ...@@ -759,18 +916,31 @@ def para_split(pdf_info_dict, debug_mode, lang="en"):
pre_page_layout_bbox = new_layout_of_pages[page_num - 1] pre_page_layout_bbox = new_layout_of_pages[page_num - 1]
next_page_layout_bbox = new_layout_of_pages[page_num] next_page_layout_bbox = new_layout_of_pages[page_num]
is_conn = __connect_para_inter_page(pre_page_paras, next_page_paras, pre_page_layout_bbox, is_conn = __connect_para_inter_page(
next_page_layout_bbox, page_num, lang) pre_page_paras,
next_page_paras,
pre_page_layout_bbox,
next_page_layout_bbox,
page_num,
lang,
)
if debug_able: if debug_able:
if is_conn: if is_conn:
logger.info(f"连接了第{page_num - 1}页和第{page_num}页的段落") logger.info(f'连接了第{page_num - 1}页和第{page_num}页的段落')
is_list_conn = __connect_list_inter_page(pre_page_paras, next_page_paras, pre_page_layout_bbox, is_list_conn = __connect_list_inter_page(
next_page_layout_bbox, all_page_list_info[page_num - 1], pre_page_paras,
all_page_list_info[page_num], page_num, lang) next_page_paras,
pre_page_layout_bbox,
next_page_layout_bbox,
all_page_list_info[page_num - 1],
all_page_list_info[page_num],
page_num,
lang,
)
if debug_able: if debug_able:
if is_list_conn: if is_list_conn:
logger.info(f"连接了第{page_num - 1}页和第{page_num}页的列表段落") logger.info(f'连接了第{page_num - 1}页和第{page_num}页的列表段落')
"""接下来可能会漏掉一些特别的一些可以合并的内容,对他们进行段落连接 """接下来可能会漏掉一些特别的一些可以合并的内容,对他们进行段落连接
1. 正文中有时出现一个行顶格,接下来几行缩进的情况。 1. 正文中有时出现一个行顶格,接下来几行缩进的情况。
...@@ -786,4 +956,4 @@ def para_split(pdf_info_dict, debug_mode, lang="en"): ...@@ -786,4 +956,4 @@ def para_split(pdf_info_dict, debug_mode, lang="en"):
for page_num, page in enumerate(pdf_info_dict.values()): for page_num, page in enumerate(pdf_info_dict.values()):
page_paras = page['para_blocks'] page_paras = page['para_blocks']
page_blocks = [block for layout in page_paras for block in layout] page_blocks = [block for layout in page_paras for block in layout]
page["para_blocks"] = page_blocks page['para_blocks'] = page_blocks
import copy import copy
from loguru import logger from magic_pdf.config.constants import CROSS_PAGE, LINES_DELETED
from magic_pdf.config.ocr_content_type import BlockType, ContentType
from magic_pdf.libs.Constants import LINES_DELETED, CROSS_PAGE
from magic_pdf.libs.ocr_content_type import BlockType, ContentType LINE_STOP_FLAG = (
'.',
LINE_STOP_FLAG = ('.', '!', '?', '。', '!', '?', ')', ')', '"', '”', ':', ':', ';', ';') '!',
'?',
'。',
'!',
'?',
')',
')',
'"',
'”',
':',
':',
';',
';',
)
LIST_END_FLAG = ('.', '。', ';', ';') LIST_END_FLAG = ('.', '。', ';', ';')
class ListLineTag: class ListLineTag:
IS_LIST_START_LINE = "is_list_start_line" IS_LIST_START_LINE = 'is_list_start_line'
IS_LIST_END_LINE = "is_list_end_line" IS_LIST_END_LINE = 'is_list_end_line'
def __process_blocks(blocks): def __process_blocks(blocks):
...@@ -27,12 +40,14 @@ def __process_blocks(blocks): ...@@ -27,12 +40,14 @@ def __process_blocks(blocks):
# 如果当前块是 text 类型 # 如果当前块是 text 类型
if current_block['type'] == 'text': if current_block['type'] == 'text':
current_block["bbox_fs"] = copy.deepcopy(current_block["bbox"]) current_block['bbox_fs'] = copy.deepcopy(current_block['bbox'])
if 'lines' in current_block and len(current_block["lines"]) > 0: if 'lines' in current_block and len(current_block['lines']) > 0:
current_block['bbox_fs'] = [min([line['bbox'][0] for line in current_block['lines']]), current_block['bbox_fs'] = [
min([line['bbox'][1] for line in current_block['lines']]), min([line['bbox'][0] for line in current_block['lines']]),
max([line['bbox'][2] for line in current_block['lines']]), min([line['bbox'][1] for line in current_block['lines']]),
max([line['bbox'][3] for line in current_block['lines']])] max([line['bbox'][2] for line in current_block['lines']]),
max([line['bbox'][3] for line in current_block['lines']]),
]
current_group.append(current_block) current_group.append(current_block)
# 检查下一个块是否存在 # 检查下一个块是否存在
...@@ -83,9 +98,10 @@ def __is_list_or_index_block(block): ...@@ -83,9 +98,10 @@ def __is_list_or_index_block(block):
# logger.info(f"block_weight_radio: {block_weight_radio}") # logger.info(f"block_weight_radio: {block_weight_radio}")
# 如果首行左边不顶格而右边顶格,末行左边顶格而右边不顶格 (第一行可能可以右边不顶格) # 如果首行左边不顶格而右边顶格,末行左边顶格而右边不顶格 (第一行可能可以右边不顶格)
if (first_line['bbox'][0] - block['bbox_fs'][0] > line_height / 2 and if (
abs(last_line['bbox'][0] - block['bbox_fs'][0]) < line_height / 2 and first_line['bbox'][0] - block['bbox_fs'][0] > line_height / 2
block['bbox_fs'][2] - last_line['bbox'][2] > line_height and abs(last_line['bbox'][0] - block['bbox_fs'][0]) < line_height / 2
and block['bbox_fs'][2] - last_line['bbox'][2] > line_height
): ):
multiple_para_flag = True multiple_para_flag = True
...@@ -93,14 +109,14 @@ def __is_list_or_index_block(block): ...@@ -93,14 +109,14 @@ def __is_list_or_index_block(block):
line_mid_x = (line['bbox'][0] + line['bbox'][2]) / 2 line_mid_x = (line['bbox'][0] + line['bbox'][2]) / 2
block_mid_x = (block['bbox_fs'][0] + block['bbox_fs'][2]) / 2 block_mid_x = (block['bbox_fs'][0] + block['bbox_fs'][2]) / 2
if ( if (
line['bbox'][0] - block['bbox_fs'][0] > 0.8 * line_height and line['bbox'][0] - block['bbox_fs'][0] > 0.8 * line_height
block['bbox_fs'][2] - line['bbox'][2] > 0.8 * line_height and block['bbox_fs'][2] - line['bbox'][2] > 0.8 * line_height
): ):
external_sides_not_close_num += 1 external_sides_not_close_num += 1
if abs(line_mid_x - block_mid_x) < line_height / 2: if abs(line_mid_x - block_mid_x) < line_height / 2:
center_close_num += 1 center_close_num += 1
line_text = "" line_text = ''
for span in line['spans']: for span in line['spans']:
span_type = span['type'] span_type = span['type']
...@@ -148,15 +164,19 @@ def __is_list_or_index_block(block): ...@@ -148,15 +164,19 @@ def __is_list_or_index_block(block):
if line_text[-1].isdigit(): if line_text[-1].isdigit():
num_end_count += 1 num_end_count += 1
if num_start_count / len(lines_text_list) >= 0.8 or num_end_count / len(lines_text_list) >= 0.8: if (
num_start_count / len(lines_text_list) >= 0.8
or num_end_count / len(lines_text_list) >= 0.8
):
line_num_flag = True line_num_flag = True
if flag_end_count / len(lines_text_list) >= 0.8: if flag_end_count / len(lines_text_list) >= 0.8:
line_end_flag = True line_end_flag = True
# 有的目录右侧不贴边, 目前认为左边或者右边有一边全贴边,且符合数字规则极为index # 有的目录右侧不贴边, 目前认为左边或者右边有一边全贴边,且符合数字规则极为index
if ((left_close_num / len(block['lines']) >= 0.8 or right_close_num / len(block['lines']) >= 0.8) if (
and line_num_flag left_close_num / len(block['lines']) >= 0.8
): or right_close_num / len(block['lines']) >= 0.8
) and line_num_flag:
for line in block['lines']: for line in block['lines']:
line[ListLineTag.IS_LIST_START_LINE] = True line[ListLineTag.IS_LIST_START_LINE] = True
return BlockType.Index return BlockType.Index
...@@ -164,20 +184,20 @@ def __is_list_or_index_block(block): ...@@ -164,20 +184,20 @@ def __is_list_or_index_block(block):
# 全部line都居中的特殊list识别,每行都需要换行,特征是多行,且大多数行都前后not_close,每line中点x坐标接近 # 全部line都居中的特殊list识别,每行都需要换行,特征是多行,且大多数行都前后not_close,每line中点x坐标接近
# 补充条件block的长宽比有要求 # 补充条件block的长宽比有要求
elif ( elif (
external_sides_not_close_num >= 2 and external_sides_not_close_num >= 2
center_close_num == len(block['lines']) and and center_close_num == len(block['lines'])
external_sides_not_close_num / len(block['lines']) >= 0.5 and and external_sides_not_close_num / len(block['lines']) >= 0.5
block_height / block_weight > 0.4 and block_height / block_weight > 0.4
): ):
for line in block['lines']: for line in block['lines']:
line[ListLineTag.IS_LIST_START_LINE] = True line[ListLineTag.IS_LIST_START_LINE] = True
return BlockType.List return BlockType.List
elif ( elif (
left_close_num >= 2 left_close_num >= 2
and (right_not_close_num >= 2 or line_end_flag or left_not_close_num >= 2) and (right_not_close_num >= 2 or line_end_flag or left_not_close_num >= 2)
and not multiple_para_flag and not multiple_para_flag
# and block_weight_radio > 0.27 # and block_weight_radio > 0.27
): ):
# 处理一种特殊的没有缩进的list,所有行都贴左边,通过右边的空隙判断是否是item尾 # 处理一种特殊的没有缩进的list,所有行都贴左边,通过右边的空隙判断是否是item尾
if left_close_num / len(block['lines']) > 0.8: if left_close_num / len(block['lines']) > 0.8:
...@@ -189,10 +209,15 @@ def __is_list_or_index_block(block): ...@@ -189,10 +209,15 @@ def __is_list_or_index_block(block):
# 这种是大部分line item 都有结束标识符的情况,按结束标识符区分不同item # 这种是大部分line item 都有结束标识符的情况,按结束标识符区分不同item
elif line_end_flag: elif line_end_flag:
for i, line in enumerate(block['lines']): for i, line in enumerate(block['lines']):
if len(lines_text_list[i]) > 0 and lines_text_list[i][-1] in LIST_END_FLAG: if (
len(lines_text_list[i]) > 0
and lines_text_list[i][-1] in LIST_END_FLAG
):
line[ListLineTag.IS_LIST_END_LINE] = True line[ListLineTag.IS_LIST_END_LINE] = True
if i + 1 < len(block['lines']): if i + 1 < len(block['lines']):
block['lines'][i + 1][ListLineTag.IS_LIST_START_LINE] = True block['lines'][i + 1][
ListLineTag.IS_LIST_START_LINE
] = True
# line item基本没有结束标识符,而且也没有缩进,按右侧空隙判断哪些是item end # line item基本没有结束标识符,而且也没有缩进,按右侧空隙判断哪些是item end
else: else:
line_start_flag = False line_start_flag = False
...@@ -201,7 +226,10 @@ def __is_list_or_index_block(block): ...@@ -201,7 +226,10 @@ def __is_list_or_index_block(block):
line[ListLineTag.IS_LIST_START_LINE] = True line[ListLineTag.IS_LIST_START_LINE] = True
line_start_flag = False line_start_flag = False
if abs(block['bbox_fs'][2] - line['bbox'][2]) > 0.1 * block_weight: if (
abs(block['bbox_fs'][2] - line['bbox'][2])
> 0.1 * block_weight
):
line[ListLineTag.IS_LIST_END_LINE] = True line[ListLineTag.IS_LIST_END_LINE] = True
line_start_flag = True line_start_flag = True
# 一种有缩进的特殊有序list,start line 左侧不贴边且以数字开头,end line 以 IS_LIST_END_FLAG 结尾且数量和start line 一致 # 一种有缩进的特殊有序list,start line 左侧不贴边且以数字开头,end line 以 IS_LIST_END_FLAG 结尾且数量和start line 一致
...@@ -243,11 +271,13 @@ def __merge_2_text_blocks(block1, block2): ...@@ -243,11 +271,13 @@ def __merge_2_text_blocks(block1, block2):
first_span = first_line['spans'][0] first_span = first_line['spans'][0]
if len(first_span['content']) > 0: if len(first_span['content']) > 0:
span_start_with_num = first_span['content'][0].isdigit() span_start_with_num = first_span['content'][0].isdigit()
if (abs(block2['bbox_fs'][2] - last_line['bbox'][2]) < line_height if (
and not last_span['content'].endswith(LINE_STOP_FLAG) abs(block2['bbox_fs'][2] - last_line['bbox'][2])
# 两个block宽度差距超过2倍也不合并 < line_height
and abs(block1_weight - block2_weight) < min_block_weight and not last_span['content'].endswith(LINE_STOP_FLAG)
and not span_start_with_num # 两个block宽度差距超过2倍也不合并
and abs(block1_weight - block2_weight) < min_block_weight
and not span_start_with_num
): ):
if block1['page_num'] != block2['page_num']: if block1['page_num'] != block2['page_num']:
for line in block1['lines']: for line in block1['lines']:
...@@ -284,7 +314,6 @@ def __is_list_group(text_blocks_group): ...@@ -284,7 +314,6 @@ def __is_list_group(text_blocks_group):
def __para_merge_page(blocks): def __para_merge_page(blocks):
page_text_blocks_groups = __process_blocks(blocks) page_text_blocks_groups = __process_blocks(blocks)
for text_blocks_group in page_text_blocks_groups: for text_blocks_group in page_text_blocks_groups:
if len(text_blocks_group) > 0: if len(text_blocks_group) > 0:
# 需要先在合并前对所有block判断是否为list or index block # 需要先在合并前对所有block判断是否为list or index block
for block in text_blocks_group: for block in text_blocks_group:
...@@ -293,7 +322,6 @@ def __para_merge_page(blocks): ...@@ -293,7 +322,6 @@ def __para_merge_page(blocks):
# logger.info(f"{block['type']}:{block}") # logger.info(f"{block['type']}:{block}")
if len(text_blocks_group) > 1: if len(text_blocks_group) > 1:
# 在合并前判断这个group 是否是一个 list group # 在合并前判断这个group 是否是一个 list group
is_list_group = __is_list_group(text_blocks_group) is_list_group = __is_list_group(text_blocks_group)
...@@ -305,11 +333,18 @@ def __para_merge_page(blocks): ...@@ -305,11 +333,18 @@ def __para_merge_page(blocks):
if i - 1 >= 0: if i - 1 >= 0:
prev_block = text_blocks_group[i - 1] prev_block = text_blocks_group[i - 1]
if current_block['type'] == 'text' and prev_block['type'] == 'text' and not is_list_group: if (
current_block['type'] == 'text'
and prev_block['type'] == 'text'
and not is_list_group
):
__merge_2_text_blocks(current_block, prev_block) __merge_2_text_blocks(current_block, prev_block)
elif ( elif (
(current_block['type'] == BlockType.List and prev_block['type'] == BlockType.List) or current_block['type'] == BlockType.List
(current_block['type'] == BlockType.Index and prev_block['type'] == BlockType.Index) and prev_block['type'] == BlockType.List
) or (
current_block['type'] == BlockType.Index
and prev_block['type'] == BlockType.Index
): ):
__merge_2_list_blocks(current_block, prev_block) __merge_2_list_blocks(current_block, prev_block)
...@@ -339,4 +374,4 @@ if __name__ == '__main__': ...@@ -339,4 +374,4 @@ if __name__ == '__main__':
# 调用函数 # 调用函数
groups = __process_blocks(input_blocks) groups = __process_blocks(input_blocks)
for group_index, group in enumerate(groups): for group_index, group in enumerate(groups):
print(f"Group {group_index}: {group}") print(f'Group {group_index}: {group}')
...@@ -2,38 +2,47 @@ import time ...@@ -2,38 +2,47 @@ import time
from loguru import logger from loguru import logger
from magic_pdf.config.drop_reason import DropReason
from magic_pdf.config.ocr_content_type import ContentType
from magic_pdf.layout.layout_sort import (LAYOUT_UNPROC, get_bboxes_layout,
get_columns_cnt_of_layout)
from magic_pdf.libs.commons import fitz, get_delta_time from magic_pdf.libs.commons import fitz, get_delta_time
from magic_pdf.layout.layout_sort import get_bboxes_layout, LAYOUT_UNPROC, get_columns_cnt_of_layout
from magic_pdf.libs.convert_utils import dict_to_list from magic_pdf.libs.convert_utils import dict_to_list
from magic_pdf.libs.drop_reason import DropReason
from magic_pdf.libs.hash_utils import compute_md5 from magic_pdf.libs.hash_utils import compute_md5
from magic_pdf.libs.local_math import float_equal from magic_pdf.libs.local_math import float_equal
from magic_pdf.libs.ocr_content_type import ContentType
from magic_pdf.model.magic_model import MagicModel from magic_pdf.model.magic_model import MagicModel
from magic_pdf.para.para_split_v2 import para_split from magic_pdf.para.para_split_v2 import para_split
from magic_pdf.pre_proc.citationmarker_remove import remove_citation_marker from magic_pdf.pre_proc.citationmarker_remove import remove_citation_marker
from magic_pdf.pre_proc.construct_page_dict import ocr_construct_page_component_v2 from magic_pdf.pre_proc.construct_page_dict import \
ocr_construct_page_component_v2
from magic_pdf.pre_proc.cut_image import ocr_cut_image_and_table from magic_pdf.pre_proc.cut_image import ocr_cut_image_and_table
from magic_pdf.pre_proc.equations_replace import remove_chars_in_text_blocks, replace_equations_in_textblock, \ from magic_pdf.pre_proc.equations_replace import (
combine_chars_to_pymudict combine_chars_to_pymudict, remove_chars_in_text_blocks,
from magic_pdf.pre_proc.ocr_detect_all_bboxes import ocr_prepare_bboxes_for_layout_split replace_equations_in_textblock)
from magic_pdf.pre_proc.ocr_dict_merge import sort_blocks_by_layout, fill_spans_in_blocks, fix_block_spans, \ from magic_pdf.pre_proc.ocr_detect_all_bboxes import \
fix_discarded_block ocr_prepare_bboxes_for_layout_split
from magic_pdf.pre_proc.ocr_span_list_modify import remove_overlaps_min_spans, get_qa_need_list_v2, \ from magic_pdf.pre_proc.ocr_dict_merge import (fill_spans_in_blocks,
remove_overlaps_low_confidence_spans fix_block_spans,
from magic_pdf.pre_proc.resolve_bbox_conflict import check_useful_block_horizontal_overlap fix_discarded_block,
sort_blocks_by_layout)
from magic_pdf.pre_proc.ocr_span_list_modify import (
get_qa_need_list_v2, remove_overlaps_low_confidence_spans,
remove_overlaps_min_spans)
from magic_pdf.pre_proc.resolve_bbox_conflict import \
check_useful_block_horizontal_overlap
def remove_horizontal_overlap_block_which_smaller(all_bboxes): def remove_horizontal_overlap_block_which_smaller(all_bboxes):
useful_blocks = [] useful_blocks = []
for bbox in all_bboxes: for bbox in all_bboxes:
useful_blocks.append({ useful_blocks.append({'bbox': bbox[:4]})
"bbox": bbox[:4] is_useful_block_horz_overlap, smaller_bbox, bigger_bbox = (
}) check_useful_block_horizontal_overlap(useful_blocks)
is_useful_block_horz_overlap, smaller_bbox, bigger_bbox = check_useful_block_horizontal_overlap(useful_blocks) )
if is_useful_block_horz_overlap: if is_useful_block_horz_overlap:
logger.warning( logger.warning(
f"skip this page, reason: {DropReason.USEFUL_BLOCK_HOR_OVERLAP}, smaller bbox is {smaller_bbox}, bigger bbox is {bigger_bbox}") f'skip this page, reason: {DropReason.USEFUL_BLOCK_HOR_OVERLAP}, smaller bbox is {smaller_bbox}, bigger bbox is {bigger_bbox}'
)
for bbox in all_bboxes.copy(): for bbox in all_bboxes.copy():
if smaller_bbox == bbox[:4]: if smaller_bbox == bbox[:4]:
all_bboxes.remove(bbox) all_bboxes.remove(bbox)
...@@ -41,27 +50,27 @@ def remove_horizontal_overlap_block_which_smaller(all_bboxes): ...@@ -41,27 +50,27 @@ def remove_horizontal_overlap_block_which_smaller(all_bboxes):
return is_useful_block_horz_overlap, all_bboxes return is_useful_block_horz_overlap, all_bboxes
def __replace_STX_ETX(text_str:str): def __replace_STX_ETX(text_str: str):
""" Replace \u0002 and \u0003, as these characters become garbled when extracted using pymupdf. In fact, they were originally quotation marks. """Replace \u0002 and \u0003, as these characters become garbled when extracted using pymupdf. In fact, they were originally quotation marks.
Drawback: This issue is only observed in English text; it has not been found in Chinese text so far. Drawback: This issue is only observed in English text; it has not been found in Chinese text so far.
Args: Args:
text_str (str): raw text text_str (str): raw text
Returns: Returns:
_type_: replaced text _type_: replaced text
""" """
if text_str: if text_str:
s = text_str.replace('\u0002', "'") s = text_str.replace('\u0002', "'")
s = s.replace("\u0003", "'") s = s.replace('\u0003', "'")
return s return s
return text_str return text_str
def txt_spans_extract(pdf_page, inline_equations, interline_equations): def txt_spans_extract(pdf_page, inline_equations, interline_equations):
text_raw_blocks = pdf_page.get_text("dict", flags=fitz.TEXTFLAGS_TEXT)["blocks"] text_raw_blocks = pdf_page.get_text('dict', flags=fitz.TEXTFLAGS_TEXT)['blocks']
char_level_text_blocks = pdf_page.get_text("rawdict", flags=fitz.TEXTFLAGS_TEXT)[ char_level_text_blocks = pdf_page.get_text('rawdict', flags=fitz.TEXTFLAGS_TEXT)[
"blocks" 'blocks'
] ]
text_blocks = combine_chars_to_pymudict(text_raw_blocks, char_level_text_blocks) text_blocks = combine_chars_to_pymudict(text_raw_blocks, char_level_text_blocks)
text_blocks = replace_equations_in_textblock( text_blocks = replace_equations_in_textblock(
...@@ -71,189 +80,254 @@ def txt_spans_extract(pdf_page, inline_equations, interline_equations): ...@@ -71,189 +80,254 @@ def txt_spans_extract(pdf_page, inline_equations, interline_equations):
text_blocks = remove_chars_in_text_blocks(text_blocks) text_blocks = remove_chars_in_text_blocks(text_blocks)
spans = [] spans = []
for v in text_blocks: for v in text_blocks:
for line in v["lines"]: for line in v['lines']:
for span in line["spans"]: for span in line['spans']:
bbox = span["bbox"] bbox = span['bbox']
if float_equal(bbox[0], bbox[2]) or float_equal(bbox[1], bbox[3]): if float_equal(bbox[0], bbox[2]) or float_equal(bbox[1], bbox[3]):
continue continue
if span.get('type') not in (ContentType.InlineEquation, ContentType.InterlineEquation): if span.get('type') not in (
ContentType.InlineEquation,
ContentType.InterlineEquation,
):
spans.append( spans.append(
{ {
"bbox": list(span["bbox"]), 'bbox': list(span['bbox']),
"content": __replace_STX_ETX(span["text"]), 'content': __replace_STX_ETX(span['text']),
"type": ContentType.Text, 'type': ContentType.Text,
"score": 1.0, 'score': 1.0,
} }
) )
return spans return spans
def replace_text_span(pymu_spans, ocr_spans): def replace_text_span(pymu_spans, ocr_spans):
return list(filter(lambda x: x["type"] != ContentType.Text, ocr_spans)) + pymu_spans return list(filter(lambda x: x['type'] != ContentType.Text, ocr_spans)) + pymu_spans
def parse_page_core(pdf_docs, magic_model, page_id, pdf_bytes_md5, imageWriter, parse_mode): def parse_page_core(
pdf_docs, magic_model, page_id, pdf_bytes_md5, imageWriter, parse_mode
):
need_drop = False need_drop = False
drop_reason = [] drop_reason = []
'''从magic_model对象中获取后面会用到的区块信息''' """从magic_model对象中获取后面会用到的区块信息"""
img_blocks = magic_model.get_imgs(page_id) img_blocks = magic_model.get_imgs(page_id)
table_blocks = magic_model.get_tables(page_id) table_blocks = magic_model.get_tables(page_id)
discarded_blocks = magic_model.get_discarded(page_id) discarded_blocks = magic_model.get_discarded(page_id)
text_blocks = magic_model.get_text_blocks(page_id) text_blocks = magic_model.get_text_blocks(page_id)
title_blocks = magic_model.get_title_blocks(page_id) title_blocks = magic_model.get_title_blocks(page_id)
inline_equations, interline_equations, interline_equation_blocks = magic_model.get_equations(page_id) inline_equations, interline_equations, interline_equation_blocks = (
magic_model.get_equations(page_id)
)
page_w, page_h = magic_model.get_page_size(page_id) page_w, page_h = magic_model.get_page_size(page_id)
spans = magic_model.get_all_spans(page_id) spans = magic_model.get_all_spans(page_id)
'''根据parse_mode,构造spans''' """根据parse_mode,构造spans"""
if parse_mode == "txt": if parse_mode == 'txt':
"""ocr 中文本类的 span 用 pymu spans 替换!""" """ocr 中文本类的 span 用 pymu spans 替换!"""
pymu_spans = txt_spans_extract( pymu_spans = txt_spans_extract(
pdf_docs[page_id], inline_equations, interline_equations pdf_docs[page_id], inline_equations, interline_equations
) )
spans = replace_text_span(pymu_spans, spans) spans = replace_text_span(pymu_spans, spans)
elif parse_mode == "ocr": elif parse_mode == 'ocr':
pass pass
else: else:
raise Exception("parse_mode must be txt or ocr") raise Exception('parse_mode must be txt or ocr')
'''删除重叠spans中置信度较低的那些''' """删除重叠spans中置信度较低的那些"""
spans, dropped_spans_by_confidence = remove_overlaps_low_confidence_spans(spans) spans, dropped_spans_by_confidence = remove_overlaps_low_confidence_spans(spans)
'''删除重叠spans中较小的那些''' """删除重叠spans中较小的那些"""
spans, dropped_spans_by_span_overlap = remove_overlaps_min_spans(spans) spans, dropped_spans_by_span_overlap = remove_overlaps_min_spans(spans)
'''对image和table截图''' """对image和table截图"""
spans = ocr_cut_image_and_table(spans, pdf_docs[page_id], page_id, pdf_bytes_md5, imageWriter) spans = ocr_cut_image_and_table(
spans, pdf_docs[page_id], page_id, pdf_bytes_md5, imageWriter
)
'''将所有区块的bbox整理到一起''' """将所有区块的bbox整理到一起"""
# interline_equation_blocks参数不够准,后面切换到interline_equations上 # interline_equation_blocks参数不够准,后面切换到interline_equations上
interline_equation_blocks = [] interline_equation_blocks = []
if len(interline_equation_blocks) > 0: if len(interline_equation_blocks) > 0:
all_bboxes, all_discarded_blocks, drop_reasons = ocr_prepare_bboxes_for_layout_split( all_bboxes, all_discarded_blocks, drop_reasons = (
img_blocks, table_blocks, discarded_blocks, text_blocks, title_blocks, ocr_prepare_bboxes_for_layout_split(
interline_equation_blocks, page_w, page_h) img_blocks,
table_blocks,
discarded_blocks,
text_blocks,
title_blocks,
interline_equation_blocks,
page_w,
page_h,
)
)
else: else:
all_bboxes, all_discarded_blocks, drop_reasons = ocr_prepare_bboxes_for_layout_split( all_bboxes, all_discarded_blocks, drop_reasons = (
img_blocks, table_blocks, discarded_blocks, text_blocks, title_blocks, ocr_prepare_bboxes_for_layout_split(
interline_equations, page_w, page_h) img_blocks,
table_blocks,
discarded_blocks,
text_blocks,
title_blocks,
interline_equations,
page_w,
page_h,
)
)
if len(drop_reasons) > 0: if len(drop_reasons) > 0:
need_drop = True need_drop = True
drop_reason.append(DropReason.OVERLAP_BLOCKS_CAN_NOT_SEPARATION) drop_reason.append(DropReason.OVERLAP_BLOCKS_CAN_NOT_SEPARATION)
'''先处理不需要排版的discarded_blocks''' """先处理不需要排版的discarded_blocks"""
discarded_block_with_spans, spans = fill_spans_in_blocks(all_discarded_blocks, spans, 0.4) discarded_block_with_spans, spans = fill_spans_in_blocks(
all_discarded_blocks, spans, 0.4
)
fix_discarded_blocks = fix_discarded_block(discarded_block_with_spans) fix_discarded_blocks = fix_discarded_block(discarded_block_with_spans)
'''如果当前页面没有bbox则跳过''' """如果当前页面没有bbox则跳过"""
if len(all_bboxes) == 0: if len(all_bboxes) == 0:
logger.warning(f"skip this page, not found useful bbox, page_id: {page_id}") logger.warning(f'skip this page, not found useful bbox, page_id: {page_id}')
return ocr_construct_page_component_v2([], [], page_id, page_w, page_h, [], return ocr_construct_page_component_v2(
[], [], interline_equations, fix_discarded_blocks, [],
need_drop, drop_reason) [],
page_id,
page_w,
page_h,
[],
[],
[],
interline_equations,
fix_discarded_blocks,
need_drop,
drop_reason,
)
"""在切分之前,先检查一下bbox是否有左右重叠的情况,如果有,那么就认为这个pdf暂时没有能力处理好,这种左右重叠的情况大概率是由于pdf里的行间公式、表格没有被正确识别出来造成的 """ """在切分之前,先检查一下bbox是否有左右重叠的情况,如果有,那么就认为这个pdf暂时没有能力处理好,这种左右重叠的情况大概率是由于pdf里的行间公式、表格没有被正确识别出来造成的 """
while True: # 循环检查左右重叠的情况,如果存在就删除掉较小的那个bbox,直到不存在左右重叠的情况 while True: # 循环检查左右重叠的情况,如果存在就删除掉较小的那个bbox,直到不存在左右重叠的情况
is_useful_block_horz_overlap, all_bboxes = remove_horizontal_overlap_block_which_smaller(all_bboxes) is_useful_block_horz_overlap, all_bboxes = (
remove_horizontal_overlap_block_which_smaller(all_bboxes)
)
if is_useful_block_horz_overlap: if is_useful_block_horz_overlap:
need_drop = True need_drop = True
drop_reason.append(DropReason.USEFUL_BLOCK_HOR_OVERLAP) drop_reason.append(DropReason.USEFUL_BLOCK_HOR_OVERLAP)
else: else:
break break
'''根据区块信息计算layout''' """根据区块信息计算layout"""
page_boundry = [0, 0, page_w, page_h] page_boundry = [0, 0, page_w, page_h]
layout_bboxes, layout_tree = get_bboxes_layout(all_bboxes, page_boundry, page_id) layout_bboxes, layout_tree = get_bboxes_layout(all_bboxes, page_boundry, page_id)
if len(text_blocks) > 0 and len(all_bboxes) > 0 and len(layout_bboxes) == 0: if len(text_blocks) > 0 and len(all_bboxes) > 0 and len(layout_bboxes) == 0:
logger.warning( logger.warning(
f"skip this page, page_id: {page_id}, reason: {DropReason.CAN_NOT_DETECT_PAGE_LAYOUT}") f'skip this page, page_id: {page_id}, reason: {DropReason.CAN_NOT_DETECT_PAGE_LAYOUT}'
)
need_drop = True need_drop = True
drop_reason.append(DropReason.CAN_NOT_DETECT_PAGE_LAYOUT) drop_reason.append(DropReason.CAN_NOT_DETECT_PAGE_LAYOUT)
"""以下去掉复杂的布局和超过2列的布局""" """以下去掉复杂的布局和超过2列的布局"""
if any([lay["layout_label"] == LAYOUT_UNPROC for lay in layout_bboxes]): # 复杂的布局 if any(
[lay['layout_label'] == LAYOUT_UNPROC for lay in layout_bboxes]
): # 复杂的布局
logger.warning( logger.warning(
f"skip this page, page_id: {page_id}, reason: {DropReason.COMPLICATED_LAYOUT}") f'skip this page, page_id: {page_id}, reason: {DropReason.COMPLICATED_LAYOUT}'
)
need_drop = True need_drop = True
drop_reason.append(DropReason.COMPLICATED_LAYOUT) drop_reason.append(DropReason.COMPLICATED_LAYOUT)
layout_column_width = get_columns_cnt_of_layout(layout_tree) layout_column_width = get_columns_cnt_of_layout(layout_tree)
if layout_column_width > 2: # 去掉超过2列的布局pdf if layout_column_width > 2: # 去掉超过2列的布局pdf
logger.warning( logger.warning(
f"skip this page, page_id: {page_id}, reason: {DropReason.TOO_MANY_LAYOUT_COLUMNS}") f'skip this page, page_id: {page_id}, reason: {DropReason.TOO_MANY_LAYOUT_COLUMNS}'
)
need_drop = True need_drop = True
drop_reason.append(DropReason.TOO_MANY_LAYOUT_COLUMNS) drop_reason.append(DropReason.TOO_MANY_LAYOUT_COLUMNS)
'''根据layout顺序,对当前页面所有需要留下的block进行排序''' """根据layout顺序,对当前页面所有需要留下的block进行排序"""
sorted_blocks = sort_blocks_by_layout(all_bboxes, layout_bboxes) sorted_blocks = sort_blocks_by_layout(all_bboxes, layout_bboxes)
'''将span填入排好序的blocks中''' """将span填入排好序的blocks中"""
block_with_spans, spans = fill_spans_in_blocks(sorted_blocks, spans, 0.3) block_with_spans, spans = fill_spans_in_blocks(sorted_blocks, spans, 0.3)
'''对block进行fix操作''' """对block进行fix操作"""
fix_blocks = fix_block_spans(block_with_spans, img_blocks, table_blocks) fix_blocks = fix_block_spans(block_with_spans, img_blocks, table_blocks)
'''获取QA需要外置的list''' """获取QA需要外置的list"""
images, tables, interline_equations = get_qa_need_list_v2(fix_blocks) images, tables, interline_equations = get_qa_need_list_v2(fix_blocks)
'''构造pdf_info_dict''' """构造pdf_info_dict"""
page_info = ocr_construct_page_component_v2(fix_blocks, layout_bboxes, page_id, page_w, page_h, layout_tree, page_info = ocr_construct_page_component_v2(
images, tables, interline_equations, fix_discarded_blocks, fix_blocks,
need_drop, drop_reason) layout_bboxes,
page_id,
page_w,
page_h,
layout_tree,
images,
tables,
interline_equations,
fix_discarded_blocks,
need_drop,
drop_reason,
)
return page_info return page_info
def pdf_parse_union(pdf_bytes, def pdf_parse_union(
model_list, pdf_bytes,
imageWriter, model_list,
parse_mode, imageWriter,
start_page_id=0, parse_mode,
end_page_id=None, start_page_id=0,
debug_mode=False, end_page_id=None,
): debug_mode=False,
):
pdf_bytes_md5 = compute_md5(pdf_bytes) pdf_bytes_md5 = compute_md5(pdf_bytes)
pdf_docs = fitz.open("pdf", pdf_bytes) pdf_docs = fitz.open('pdf', pdf_bytes)
'''初始化空的pdf_info_dict''' """初始化空的pdf_info_dict"""
pdf_info_dict = {} pdf_info_dict = {}
'''用model_list和docs对象初始化magic_model''' """用model_list和docs对象初始化magic_model"""
magic_model = MagicModel(model_list, pdf_docs) magic_model = MagicModel(model_list, pdf_docs)
'''根据输入的起始范围解析pdf''' """根据输入的起始范围解析pdf"""
# end_page_id = end_page_id if end_page_id else len(pdf_docs) - 1 # end_page_id = end_page_id if end_page_id else len(pdf_docs) - 1
end_page_id = end_page_id if end_page_id is not None and end_page_id >= 0 else len(pdf_docs) - 1 end_page_id = (
end_page_id
if end_page_id is not None and end_page_id >= 0
else len(pdf_docs) - 1
)
if end_page_id > len(pdf_docs) - 1: if end_page_id > len(pdf_docs) - 1:
logger.warning("end_page_id is out of range, use pdf_docs length") logger.warning('end_page_id is out of range, use pdf_docs length')
end_page_id = len(pdf_docs) - 1 end_page_id = len(pdf_docs) - 1
'''初始化启动时间''' """初始化启动时间"""
start_time = time.time() start_time = time.time()
for page_id, page in enumerate(pdf_docs): for page_id, page in enumerate(pdf_docs):
'''debug时输出每页解析的耗时''' """debug时输出每页解析的耗时."""
if debug_mode: if debug_mode:
time_now = time.time() time_now = time.time()
logger.info( logger.info(
f"page_id: {page_id}, last_page_cost_time: {get_delta_time(start_time)}" f'page_id: {page_id}, last_page_cost_time: {get_delta_time(start_time)}'
) )
start_time = time_now start_time = time_now
'''解析pdf中的每一页''' """解析pdf中的每一页"""
if start_page_id <= page_id <= end_page_id: if start_page_id <= page_id <= end_page_id:
page_info = parse_page_core(pdf_docs, magic_model, page_id, pdf_bytes_md5, imageWriter, parse_mode) page_info = parse_page_core(
pdf_docs, magic_model, page_id, pdf_bytes_md5, imageWriter, parse_mode
)
else: else:
page_w = page.rect.width page_w = page.rect.width
page_h = page.rect.height page_h = page.rect.height
page_info = ocr_construct_page_component_v2([], [], page_id, page_w, page_h, [], page_info = ocr_construct_page_component_v2(
[], [], [], [], [], [], page_id, page_w, page_h, [], [], [], [], [], True, 'skip page'
True, "skip page") )
pdf_info_dict[f"page_{page_id}"] = page_info pdf_info_dict[f'page_{page_id}'] = page_info
"""分段""" """分段"""
para_split(pdf_info_dict, debug_mode=debug_mode) para_split(pdf_info_dict, debug_mode=debug_mode)
...@@ -261,7 +335,7 @@ def pdf_parse_union(pdf_bytes, ...@@ -261,7 +335,7 @@ def pdf_parse_union(pdf_bytes,
"""dict转list""" """dict转list"""
pdf_info_list = dict_to_list(pdf_info_dict) pdf_info_list = dict_to_list(pdf_info_dict)
new_pdf_info_dict = { new_pdf_info_dict = {
"pdf_info": pdf_info_list, 'pdf_info': pdf_info_list,
} }
return new_pdf_info_dict return new_pdf_info_dict
......
...@@ -7,17 +7,17 @@ from typing import List ...@@ -7,17 +7,17 @@ from typing import List
import torch import torch
from loguru import logger from loguru import logger
from magic_pdf.config.drop_reason import DropReason
from magic_pdf.config.enums import SupportedPdfParseMethod from magic_pdf.config.enums import SupportedPdfParseMethod
from magic_pdf.config.ocr_content_type import BlockType, ContentType
from magic_pdf.data.dataset import Dataset, PageableData from magic_pdf.data.dataset import Dataset, PageableData
from magic_pdf.libs.boxbase import calculate_overlap_area_in_bbox1_area_ratio from magic_pdf.libs.boxbase import calculate_overlap_area_in_bbox1_area_ratio
from magic_pdf.libs.clean_memory import clean_memory from magic_pdf.libs.clean_memory import clean_memory
from magic_pdf.libs.commons import fitz, get_delta_time from magic_pdf.libs.commons import fitz, get_delta_time
from magic_pdf.libs.config_reader import get_local_layoutreader_model_dir from magic_pdf.libs.config_reader import get_local_layoutreader_model_dir
from magic_pdf.libs.convert_utils import dict_to_list from magic_pdf.libs.convert_utils import dict_to_list
from magic_pdf.libs.drop_reason import DropReason
from magic_pdf.libs.hash_utils import compute_md5 from magic_pdf.libs.hash_utils import compute_md5
from magic_pdf.libs.local_math import float_equal from magic_pdf.libs.local_math import float_equal
from magic_pdf.libs.ocr_content_type import ContentType, BlockType
from magic_pdf.model.magic_model import MagicModel from magic_pdf.model.magic_model import MagicModel
from magic_pdf.para.para_split_v3 import para_split from magic_pdf.para.para_split_v3 import para_split
from magic_pdf.pre_proc.citationmarker_remove import remove_citation_marker from magic_pdf.pre_proc.citationmarker_remove import remove_citation_marker
...@@ -30,8 +30,8 @@ from magic_pdf.pre_proc.equations_replace import ( ...@@ -30,8 +30,8 @@ from magic_pdf.pre_proc.equations_replace import (
from magic_pdf.pre_proc.ocr_detect_all_bboxes import \ from magic_pdf.pre_proc.ocr_detect_all_bboxes import \
ocr_prepare_bboxes_for_layout_split_v2 ocr_prepare_bboxes_for_layout_split_v2
from magic_pdf.pre_proc.ocr_dict_merge import (fill_spans_in_blocks, from magic_pdf.pre_proc.ocr_dict_merge import (fill_spans_in_blocks,
fix_discarded_block, fix_block_spans_v2,
fix_block_spans_v2) fix_discarded_block)
from magic_pdf.pre_proc.ocr_span_list_modify import ( from magic_pdf.pre_proc.ocr_span_list_modify import (
get_qa_need_list_v2, remove_overlaps_low_confidence_spans, get_qa_need_list_v2, remove_overlaps_low_confidence_spans,
remove_overlaps_min_spans) remove_overlaps_min_spans)
...@@ -164,8 +164,8 @@ class ModelSingleton: ...@@ -164,8 +164,8 @@ class ModelSingleton:
def do_predict(boxes: List[List[int]], model) -> List[int]: def do_predict(boxes: List[List[int]], model) -> List[int]:
from magic_pdf.model.sub_modules.reading_oreder.layoutreader.helpers import (boxes2inputs, parse_logits, from magic_pdf.model.sub_modules.reading_oreder.layoutreader.helpers import (
prepare_inputs) boxes2inputs, parse_logits, prepare_inputs)
inputs = boxes2inputs(boxes) inputs = boxes2inputs(boxes)
inputs = prepare_inputs(inputs, model) inputs = prepare_inputs(inputs, model)
...@@ -206,7 +206,9 @@ def cal_block_index(fix_blocks, sorted_bboxes): ...@@ -206,7 +206,9 @@ def cal_block_index(fix_blocks, sorted_bboxes):
del block['real_lines'] del block['real_lines']
import numpy as np import numpy as np
from magic_pdf.model.sub_modules.reading_oreder.layoutreader.xycut import recursive_xy_cut
from magic_pdf.model.sub_modules.reading_oreder.layoutreader.xycut import \
recursive_xy_cut
random_boxes = np.array(block_bboxes) random_boxes = np.array(block_bboxes)
np.random.shuffle(random_boxes) np.random.shuffle(random_boxes)
...@@ -291,7 +293,7 @@ def sort_lines_by_model(fix_blocks, page_w, page_h, line_height): ...@@ -291,7 +293,7 @@ def sort_lines_by_model(fix_blocks, page_w, page_h, line_height):
page_line_list.append(bbox) page_line_list.append(bbox)
elif block['type'] in [BlockType.ImageBody, BlockType.TableBody]: elif block['type'] in [BlockType.ImageBody, BlockType.TableBody]:
bbox = block['bbox'] bbox = block['bbox']
block["real_lines"] = copy.deepcopy(block['lines']) block['real_lines'] = copy.deepcopy(block['lines'])
lines = insert_lines_into_block(bbox, line_height, page_w, page_h) lines = insert_lines_into_block(bbox, line_height, page_w, page_h)
block['lines'] = [] block['lines'] = []
for line in lines: for line in lines:
......
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from magic_pdf.config.drop_reason import DropReason
from magic_pdf.config.make_content_config import DropMode, MakeMode
from magic_pdf.data.data_reader_writer import DataWriter from magic_pdf.data.data_reader_writer import DataWriter
from magic_pdf.dict2md.ocr_mkcontent import union_make from magic_pdf.dict2md.ocr_mkcontent import union_make
from magic_pdf.filter.pdf_classify_by_type import classify from magic_pdf.filter.pdf_classify_by_type import classify
from magic_pdf.filter.pdf_meta_scan import pdf_meta_scan from magic_pdf.filter.pdf_meta_scan import pdf_meta_scan
from magic_pdf.libs.drop_reason import DropReason
from magic_pdf.libs.json_compressor import JsonCompressor from magic_pdf.libs.json_compressor import JsonCompressor
from magic_pdf.libs.MakeContentConfig import DropMode, MakeMode
class AbsPipe(ABC): class AbsPipe(ABC):
......
from loguru import logger from loguru import logger
from magic_pdf.config.make_content_config import DropMode, MakeMode
from magic_pdf.data.data_reader_writer import DataWriter from magic_pdf.data.data_reader_writer import DataWriter
from magic_pdf.libs.MakeContentConfig import DropMode, MakeMode
from magic_pdf.model.doc_analyze_by_custom_model import doc_analyze from magic_pdf.model.doc_analyze_by_custom_model import doc_analyze
from magic_pdf.pipe.AbsPipe import AbsPipe from magic_pdf.pipe.AbsPipe import AbsPipe
from magic_pdf.user_api import parse_ocr_pdf from magic_pdf.user_api import parse_ocr_pdf
......
from loguru import logger from loguru import logger
from magic_pdf.config.make_content_config import DropMode, MakeMode
from magic_pdf.data.data_reader_writer import DataWriter from magic_pdf.data.data_reader_writer import DataWriter
from magic_pdf.libs.MakeContentConfig import DropMode, MakeMode
from magic_pdf.model.doc_analyze_by_custom_model import doc_analyze from magic_pdf.model.doc_analyze_by_custom_model import doc_analyze
from magic_pdf.pipe.AbsPipe import AbsPipe from magic_pdf.pipe.AbsPipe import AbsPipe
from magic_pdf.user_api import parse_txt_pdf from magic_pdf.user_api import parse_txt_pdf
......
...@@ -2,9 +2,9 @@ import json ...@@ -2,9 +2,9 @@ import json
from loguru import logger from loguru import logger
from magic_pdf.config.make_content_config import DropMode, MakeMode
from magic_pdf.data.data_reader_writer import DataWriter from magic_pdf.data.data_reader_writer import DataWriter
from magic_pdf.libs.commons import join_path from magic_pdf.libs.commons import join_path
from magic_pdf.libs.MakeContentConfig import DropMode, MakeMode
from magic_pdf.model.doc_analyze_by_custom_model import doc_analyze from magic_pdf.model.doc_analyze_by_custom_model import doc_analyze
from magic_pdf.pipe.AbsPipe import AbsPipe from magic_pdf.pipe.AbsPipe import AbsPipe
from magic_pdf.user_api import parse_ocr_pdf, parse_union_pdf from magic_pdf.user_api import parse_ocr_pdf, parse_union_pdf
......
from loguru import logger from loguru import logger
from magic_pdf.config.drop_reason import DropReason
from magic_pdf.layout.layout_sort import get_columns_cnt_of_layout from magic_pdf.layout.layout_sort import get_columns_cnt_of_layout
from magic_pdf.libs.drop_reason import DropReason
def __is_pseudo_single_column(page_info) -> bool: def __is_pseudo_single_column(page_info) -> bool:
""" """判断一个页面是否伪单列。
判断一个页面是否伪单列。
Args: Args:
page_info (dict): 页面信息字典,包括'_layout_tree'和'preproc_blocks'。 page_info (dict): 页面信息字典,包括'_layout_tree'和'preproc_blocks'。
Returns: Returns:
Tuple[bool, Optional[str]]: 如果页面伪单列返回(True, extra_info),否则返回(False, None)。 Tuple[bool, Optional[str]]: 如果页面伪单列返回(True, extra_info),否则返回(False, None)。
""" """
layout_tree = page_info['_layout_tree'] layout_tree = page_info['_layout_tree']
layout_column_width = get_columns_cnt_of_layout(layout_tree) layout_column_width = get_columns_cnt_of_layout(layout_tree)
...@@ -41,27 +39,22 @@ def __is_pseudo_single_column(page_info) -> bool: ...@@ -41,27 +39,22 @@ def __is_pseudo_single_column(page_info) -> bool:
if num_lines > 20: if num_lines > 20:
radio = num_satisfying_lines / num_lines radio = num_satisfying_lines / num_lines
if radio >= 0.5: if radio >= 0.5:
extra_info = f"{{num_lines: {num_lines}, num_satisfying_lines: {num_satisfying_lines}}}" extra_info = f'{{num_lines: {num_lines}, num_satisfying_lines: {num_satisfying_lines}}}'
block_text = [] block_text = []
for line in lines: for line in lines:
if line['spans']: if line['spans']:
for span in line['spans']: for span in line['spans']:
block_text.append(span['text']) block_text.append(span['text'])
logger.warning(f"pseudo_single_column block_text: {block_text}") logger.warning(f'pseudo_single_column block_text: {block_text}')
return True, extra_info return True, extra_info
return False, None return False, None
def pdf_post_filter(page_info) -> tuple: def pdf_post_filter(page_info) -> tuple:
""" """return:(True|False, err_msg) True, 如果pdf符合要求 False, 如果pdf不符合要求."""
return:(True|False, err_msg)
True, 如果pdf符合要求
False, 如果pdf不符合要求
"""
bool_is_pseudo_single_column, extra_info = __is_pseudo_single_column(page_info) bool_is_pseudo_single_column, extra_info = __is_pseudo_single_column(page_info)
if bool_is_pseudo_single_column: if bool_is_pseudo_single_column:
return False, {"_need_drop": True, "_drop_reason": DropReason.PSEUDO_SINGLE_COLUMN, "extra_info": extra_info} return False, {'_need_drop': True, '_drop_reason': DropReason.PSEUDO_SINGLE_COLUMN, 'extra_info': extra_info}
return True, None return True, None
\ No newline at end of file
from loguru import logger from loguru import logger
from magic_pdf.config.ocr_content_type import ContentType
from magic_pdf.libs.commons import join_path from magic_pdf.libs.commons import join_path
from magic_pdf.libs.ocr_content_type import ContentType
from magic_pdf.libs.pdf_image_tools import cut_image from magic_pdf.libs.pdf_image_tools import cut_image
...@@ -29,9 +29,7 @@ def txt_save_images_by_bboxes(page_num: int, page, pdf_bytes_md5: str, ...@@ -29,9 +29,7 @@ def txt_save_images_by_bboxes(page_num: int, page, pdf_bytes_md5: str,
image_bboxes: list, images_overlap_backup: list, table_bboxes: list, image_bboxes: list, images_overlap_backup: list, table_bboxes: list,
equation_inline_bboxes: list, equation_inline_bboxes: list,
equation_interline_bboxes: list, imageWriter) -> dict: equation_interline_bboxes: list, imageWriter) -> dict:
""" """返回一个dict, key为bbox, 值是图片地址."""
返回一个dict, key为bbox, 值是图片地址
"""
image_info = [] image_info = []
image_backup_info = [] image_backup_info = []
table_info = [] table_info = []
...@@ -46,26 +44,26 @@ def txt_save_images_by_bboxes(page_num: int, page, pdf_bytes_md5: str, ...@@ -46,26 +44,26 @@ def txt_save_images_by_bboxes(page_num: int, page, pdf_bytes_md5: str,
for bbox in image_bboxes: for bbox in image_bboxes:
if not check_img_bbox(bbox): if not check_img_bbox(bbox):
continue continue
image_path = cut_image(bbox, page_num, page, return_path("images"), imageWriter) image_path = cut_image(bbox, page_num, page, return_path('images'), imageWriter)
image_info.append({"bbox": bbox, "image_path": image_path}) image_info.append({'bbox': bbox, 'image_path': image_path})
for bbox in images_overlap_backup: for bbox in images_overlap_backup:
if not check_img_bbox(bbox): if not check_img_bbox(bbox):
continue continue
image_path = cut_image(bbox, page_num, page, return_path("images"), imageWriter) image_path = cut_image(bbox, page_num, page, return_path('images'), imageWriter)
image_backup_info.append({"bbox": bbox, "image_path": image_path}) image_backup_info.append({'bbox': bbox, 'image_path': image_path})
for bbox in table_bboxes: for bbox in table_bboxes:
if not check_img_bbox(bbox): if not check_img_bbox(bbox):
continue continue
image_path = cut_image(bbox, page_num, page, return_path("tables"), imageWriter) image_path = cut_image(bbox, page_num, page, return_path('tables'), imageWriter)
table_info.append({"bbox": bbox, "image_path": image_path}) table_info.append({'bbox': bbox, 'image_path': image_path})
return image_info, image_backup_info, table_info, inline_eq_info, interline_eq_info return image_info, image_backup_info, table_info, inline_eq_info, interline_eq_info
def check_img_bbox(bbox) -> bool: def check_img_bbox(bbox) -> bool:
if any([bbox[0] >= bbox[2], bbox[1] >= bbox[3]]): if any([bbox[0] >= bbox[2], bbox[1] >= bbox[3]]):
logger.warning(f"image_bboxes: 错误的box, {bbox}") logger.warning(f'image_bboxes: 错误的box, {bbox}')
return False return False
return True return True
""" """对pymupdf返回的结构里的公式进行替换,替换为模型识别的公式结果."""
对pymupdf返回的结构里的公式进行替换,替换为模型识别的公式结果
"""
from magic_pdf.libs.commons import fitz
import json import json
import os import os
from pathlib import Path from pathlib import Path
from loguru import logger from loguru import logger
from magic_pdf.libs.ocr_content_type import ContentType
from magic_pdf.config.ocr_content_type import ContentType
from magic_pdf.libs.commons import fitz
TYPE_INLINE_EQUATION = ContentType.InlineEquation TYPE_INLINE_EQUATION = ContentType.InlineEquation
TYPE_INTERLINE_EQUATION = ContentType.InterlineEquation TYPE_INTERLINE_EQUATION = ContentType.InterlineEquation
def combine_chars_to_pymudict(block_dict, char_dict): def combine_chars_to_pymudict(block_dict, char_dict):
""" """把block级别的pymupdf 结构里加入char结构."""
把block级别的pymupdf 结构里加入char结构
"""
# 因为block_dict 被裁剪过,因此先把他和char_dict文字块对齐,才能进行补充 # 因为block_dict 被裁剪过,因此先把他和char_dict文字块对齐,才能进行补充
char_map = {tuple(item["bbox"]): item for item in char_dict} char_map = {tuple(item['bbox']): item for item in char_dict}
for i in range(len(block_dict)): # blcok for i in range(len(block_dict)): # block
block = block_dict[i] block = block_dict[i]
key = block["bbox"] key = block['bbox']
char_dict_item = char_map[tuple(key)] char_dict_item = char_map[tuple(key)]
char_dict_map = {tuple(item["bbox"]): item for item in char_dict_item["lines"]} char_dict_map = {tuple(item['bbox']): item for item in char_dict_item['lines']}
for j in range(len(block["lines"])): for j in range(len(block['lines'])):
lines = block["lines"][j] lines = block['lines'][j]
with_char_lines = char_dict_map[lines["bbox"]] with_char_lines = char_dict_map[lines['bbox']]
for k in range(len(lines["spans"])): for k in range(len(lines['spans'])):
spans = lines["spans"][k] spans = lines['spans'][k]
try: try:
chars = with_char_lines["spans"][k]["chars"] chars = with_char_lines['spans'][k]['chars']
except Exception as e: except Exception:
logger.error(char_dict[i]["lines"][j]) logger.error(char_dict[i]['lines'][j])
spans["chars"] = chars spans['chars'] = chars
return block_dict return block_dict
def calculate_overlap_area_2_minbox_area_ratio(bbox1, min_bbox): def calculate_overlap_area_2_minbox_area_ratio(bbox1, min_bbox):
""" """计算box1和box2的重叠面积占最小面积的box的比例."""
计算box1和box2的重叠面积占最小面积的box的比例
"""
# Determine the coordinates of the intersection rectangle # Determine the coordinates of the intersection rectangle
x_left = max(bbox1[0], min_bbox[0]) x_left = max(bbox1[0], min_bbox[0])
y_top = max(bbox1[1], min_bbox[1]) y_top = max(bbox1[1], min_bbox[1])
...@@ -74,13 +70,13 @@ def _is_xin(bbox1, bbox2): ...@@ -74,13 +70,13 @@ def _is_xin(bbox1, bbox2):
def remove_text_block_in_interline_equation_bbox(interline_bboxes, text_blocks): def remove_text_block_in_interline_equation_bbox(interline_bboxes, text_blocks):
"""消除掉整个块都在行间公式块内部的文本块""" """消除掉整个块都在行间公式块内部的文本块."""
for eq_bbox in interline_bboxes: for eq_bbox in interline_bboxes:
removed_txt_blk = [] removed_txt_blk = []
for text_blk in text_blocks: for text_blk in text_blocks:
text_bbox = text_blk["bbox"] text_bbox = text_blk['bbox']
if ( if (
calculate_overlap_area_2_minbox_area_ratio(eq_bbox["bbox"], text_bbox) calculate_overlap_area_2_minbox_area_ratio(eq_bbox['bbox'], text_bbox)
>= 0.7 >= 0.7
): ):
removed_txt_blk.append(text_blk) removed_txt_blk.append(text_blk)
...@@ -91,9 +87,7 @@ def remove_text_block_in_interline_equation_bbox(interline_bboxes, text_blocks): ...@@ -91,9 +87,7 @@ def remove_text_block_in_interline_equation_bbox(interline_bboxes, text_blocks):
def _is_in_or_part_overlap(box1, box2) -> bool: def _is_in_or_part_overlap(box1, box2) -> bool:
""" """两个bbox是否有部分重叠或者包含."""
两个bbox是否有部分重叠或者包含
"""
if box1 is None or box2 is None: if box1 is None or box2 is None:
return False return False
...@@ -111,62 +105,65 @@ def _is_in_or_part_overlap(box1, box2) -> bool: ...@@ -111,62 +105,65 @@ def _is_in_or_part_overlap(box1, box2) -> bool:
def remove_text_block_overlap_interline_equation_bbox( def remove_text_block_overlap_interline_equation_bbox(
interline_eq_bboxes, pymu_block_list interline_eq_bboxes, pymu_block_list
): ):
"""消除掉行行内公式有部分重叠的文本块的内容。 同时重新计算消除重叠之后文本块的大小."""
"""消除掉行行内公式有部分重叠的文本块的内容。
同时重新计算消除重叠之后文本块的大小"""
deleted_block = [] deleted_block = []
for text_block in pymu_block_list: for text_block in pymu_block_list:
deleted_line = [] deleted_line = []
for line in text_block["lines"]: for line in text_block['lines']:
deleted_span = [] deleted_span = []
for span in line["spans"]: for span in line['spans']:
deleted_chars = [] deleted_chars = []
for char in span["chars"]: for char in span['chars']:
if any( if any(
[ [
(calculate_overlap_area_2_minbox_area_ratio(eq_bbox["bbox"], char["bbox"]) > 0.5) (
for eq_bbox in interline_eq_bboxes calculate_overlap_area_2_minbox_area_ratio(
] eq_bbox['bbox'], char['bbox']
)
> 0.5
)
for eq_bbox in interline_eq_bboxes
]
): ):
deleted_chars.append(char) deleted_chars.append(char)
# 检查span里没有char则删除这个span # 检查span里没有char则删除这个span
for char in deleted_chars: for char in deleted_chars:
span["chars"].remove(char) span['chars'].remove(char)
# 重新计算这个span的大小 # 重新计算这个span的大小
if len(span["chars"]) == 0: # 删除这个span if len(span['chars']) == 0: # 删除这个span
deleted_span.append(span) deleted_span.append(span)
else: else:
span["bbox"] = ( span['bbox'] = (
min([b["bbox"][0] for b in span["chars"]]), min([b['bbox'][0] for b in span['chars']]),
min([b["bbox"][1] for b in span["chars"]]), min([b['bbox'][1] for b in span['chars']]),
max([b["bbox"][2] for b in span["chars"]]), max([b['bbox'][2] for b in span['chars']]),
max([b["bbox"][3] for b in span["chars"]]), max([b['bbox'][3] for b in span['chars']]),
) )
# 检查这个span # 检查这个span
for span in deleted_span: for span in deleted_span:
line["spans"].remove(span) line['spans'].remove(span)
if len(line["spans"]) == 0: # 删除这个line if len(line['spans']) == 0: # 删除这个line
deleted_line.append(line) deleted_line.append(line)
else: else:
line["bbox"] = ( line['bbox'] = (
min([b["bbox"][0] for b in line["spans"]]), min([b['bbox'][0] for b in line['spans']]),
min([b["bbox"][1] for b in line["spans"]]), min([b['bbox'][1] for b in line['spans']]),
max([b["bbox"][2] for b in line["spans"]]), max([b['bbox'][2] for b in line['spans']]),
max([b["bbox"][3] for b in line["spans"]]), max([b['bbox'][3] for b in line['spans']]),
) )
# 检查这个block是否可以删除 # 检查这个block是否可以删除
for line in deleted_line: for line in deleted_line:
text_block["lines"].remove(line) text_block['lines'].remove(line)
if len(text_block["lines"]) == 0: # 删除block if len(text_block['lines']) == 0: # 删除block
deleted_block.append(text_block) deleted_block.append(text_block)
else: else:
text_block["bbox"] = ( text_block['bbox'] = (
min([b["bbox"][0] for b in text_block["lines"]]), min([b['bbox'][0] for b in text_block['lines']]),
min([b["bbox"][1] for b in text_block["lines"]]), min([b['bbox'][1] for b in text_block['lines']]),
max([b["bbox"][2] for b in text_block["lines"]]), max([b['bbox'][2] for b in text_block['lines']]),
max([b["bbox"][3] for b in text_block["lines"]]), max([b['bbox'][3] for b in text_block['lines']]),
) )
# 检查text block删除 # 检查text block删除
...@@ -179,33 +176,33 @@ def remove_text_block_overlap_interline_equation_bbox( ...@@ -179,33 +176,33 @@ def remove_text_block_overlap_interline_equation_bbox(
def insert_interline_equations_textblock(interline_eq_bboxes, pymu_block_list): def insert_interline_equations_textblock(interline_eq_bboxes, pymu_block_list):
"""在行间公式对应的地方插上一个伪造的block""" """在行间公式对应的地方插上一个伪造的block."""
for eq in interline_eq_bboxes: for eq in interline_eq_bboxes:
bbox = eq["bbox"] bbox = eq['bbox']
latex_content = eq["latex"] latex_content = eq['latex']
text_block = { text_block = {
"number": len(pymu_block_list), 'number': len(pymu_block_list),
"type": 0, 'type': 0,
"bbox": bbox, 'bbox': bbox,
"lines": [ 'lines': [
{ {
"spans": [ 'spans': [
{ {
"size": 9.962599754333496, 'size': 9.962599754333496,
"type": TYPE_INTERLINE_EQUATION, 'type': TYPE_INTERLINE_EQUATION,
"flags": 4, 'flags': 4,
"font": TYPE_INTERLINE_EQUATION, 'font': TYPE_INTERLINE_EQUATION,
"color": 0, 'color': 0,
"ascender": 0.9409999847412109, 'ascender': 0.9409999847412109,
"descender": -0.3050000071525574, 'descender': -0.3050000071525574,
"latex": latex_content, 'latex': latex_content,
"origin": [bbox[0], bbox[1]], 'origin': [bbox[0], bbox[1]],
"bbox": bbox, 'bbox': bbox,
} }
], ],
"wmode": 0, 'wmode': 0,
"dir": [1.0, 0.0], 'dir': [1.0, 0.0],
"bbox": bbox, 'bbox': bbox,
} }
], ],
} }
...@@ -250,53 +247,52 @@ def __y_overlap_ratio(box1, box2): ...@@ -250,53 +247,52 @@ def __y_overlap_ratio(box1, box2):
def replace_line_v2(eqinfo, line): def replace_line_v2(eqinfo, line):
""" """扫描这一行所有的和公式框X方向重叠的char,然后计算char的左、右x0, x1,位于这个区间内的span删除掉。
扫描这一行所有的和公式框X方向重叠的char,然后计算char的左、右x0, x1,位于这个区间内的span删除掉。 最后与这个x0,x1有相交的span0, span1内部进行分割。"""
最后与这个x0,x1有相交的span0, span1内部进行分割。
"""
first_overlap_span = -1 first_overlap_span = -1
first_overlap_span_idx = -1 first_overlap_span_idx = -1
last_overlap_span = -1 last_overlap_span = -1
delete_chars = [] delete_chars = []
for i in range(0, len(line["spans"])): for i in range(0, len(line['spans'])):
if "chars" not in line["spans"][i]: if 'chars' not in line['spans'][i]:
continue continue
if line["spans"][i].get("_type", None) is not None: if line['spans'][i].get('_type', None) is not None:
continue # 忽略,因为已经是插入的伪造span公式了 continue # 忽略,因为已经是插入的伪造span公式了
for char in line["spans"][i]["chars"]: for char in line['spans'][i]['chars']:
if __is_x_dir_overlap(eqinfo["bbox"], char["bbox"]): if __is_x_dir_overlap(eqinfo['bbox'], char['bbox']):
line_txt = "" line_txt = ''
for span in line["spans"]: for span in line['spans']:
span_txt = "<span>" span_txt = '<span>'
for ch in span["chars"]: for ch in span['chars']:
span_txt = span_txt + ch["c"] span_txt = span_txt + ch['c']
span_txt = span_txt + "</span>" span_txt = span_txt + '</span>'
line_txt = line_txt + span_txt line_txt = line_txt + span_txt
if first_overlap_span_idx == -1: if first_overlap_span_idx == -1:
first_overlap_span = line["spans"][i] first_overlap_span = line['spans'][i]
first_overlap_span_idx = i first_overlap_span_idx = i
last_overlap_span = line["spans"][i] last_overlap_span = line['spans'][i]
delete_chars.append(char) delete_chars.append(char)
# 第一个和最后一个char要进行检查,到底属于公式多还是属于正常span多 # 第一个和最后一个char要进行检查,到底属于公式多还是属于正常span多
if len(delete_chars) > 0: if len(delete_chars) > 0:
ch0_bbox = delete_chars[0]["bbox"] ch0_bbox = delete_chars[0]['bbox']
if x_overlap_ratio(eqinfo["bbox"], ch0_bbox) < 0.51: if x_overlap_ratio(eqinfo['bbox'], ch0_bbox) < 0.51:
delete_chars.remove(delete_chars[0]) delete_chars.remove(delete_chars[0])
if len(delete_chars) > 0: if len(delete_chars) > 0:
ch0_bbox = delete_chars[-1]["bbox"] ch0_bbox = delete_chars[-1]['bbox']
if x_overlap_ratio(eqinfo["bbox"], ch0_bbox) < 0.51: if x_overlap_ratio(eqinfo['bbox'], ch0_bbox) < 0.51:
delete_chars.remove(delete_chars[-1]) delete_chars.remove(delete_chars[-1])
# 计算x方向上被删除区间内的char的真实x0, x1 # 计算x方向上被删除区间内的char的真实x0, x1
if len(delete_chars): if len(delete_chars):
x0, x1 = min([b["bbox"][0] for b in delete_chars]), max( x0, x1 = (
[b["bbox"][2] for b in delete_chars] min([b['bbox'][0] for b in delete_chars]),
max([b['bbox'][2] for b in delete_chars]),
) )
else: else:
# logger.debug(f"行内公式替换没有发生,尝试下一行匹配, eqinfo={eqinfo}") # logger.debug(f"行内公式替换没有发生,尝试下一行匹配, eqinfo={eqinfo}")
...@@ -304,101 +300,101 @@ def replace_line_v2(eqinfo, line): ...@@ -304,101 +300,101 @@ def replace_line_v2(eqinfo, line):
# 删除位于x0, x1这两个中间的span # 删除位于x0, x1这两个中间的span
delete_span = [] delete_span = []
for span in line["spans"]: for span in line['spans']:
span_box = span["bbox"] span_box = span['bbox']
if x0 <= span_box[0] and span_box[2] <= x1: if x0 <= span_box[0] and span_box[2] <= x1:
delete_span.append(span) delete_span.append(span)
for span in delete_span: for span in delete_span:
line["spans"].remove(span) line['spans'].remove(span)
equation_span = { equation_span = {
"size": 9.962599754333496, 'size': 9.962599754333496,
"type": TYPE_INLINE_EQUATION, 'type': TYPE_INLINE_EQUATION,
"flags": 4, 'flags': 4,
"font": TYPE_INLINE_EQUATION, 'font': TYPE_INLINE_EQUATION,
"color": 0, 'color': 0,
"ascender": 0.9409999847412109, 'ascender': 0.9409999847412109,
"descender": -0.3050000071525574, 'descender': -0.3050000071525574,
"latex": "", 'latex': '',
"origin": [337.1410153102337, 216.0205245153934], 'origin': [337.1410153102337, 216.0205245153934],
"bbox": eqinfo["bbox"] 'bbox': eqinfo['bbox'],
} }
# equation_span = line['spans'][0].copy() # equation_span = line['spans'][0].copy()
equation_span["latex"] = eqinfo['latex'] equation_span['latex'] = eqinfo['latex']
equation_span["bbox"] = [x0, equation_span["bbox"][1], x1, equation_span["bbox"][3]] equation_span['bbox'] = [x0, equation_span['bbox'][1], x1, equation_span['bbox'][3]]
equation_span["origin"] = [equation_span["bbox"][0], equation_span["bbox"][1]] equation_span['origin'] = [equation_span['bbox'][0], equation_span['bbox'][1]]
equation_span["chars"] = delete_chars equation_span['chars'] = delete_chars
equation_span["type"] = TYPE_INLINE_EQUATION equation_span['type'] = TYPE_INLINE_EQUATION
equation_span["_eq_bbox"] = eqinfo["bbox"] equation_span['_eq_bbox'] = eqinfo['bbox']
line["spans"].insert(first_overlap_span_idx + 1, equation_span) # 放入公式 line['spans'].insert(first_overlap_span_idx + 1, equation_span) # 放入公式
# logger.info(f"==>text is 【{line_txt}】, equation is 【{eqinfo['latex_text']}】") # logger.info(f"==>text is 【{line_txt}】, equation is 【{eqinfo['latex_text']}】")
# 第一个、和最后一个有overlap的span进行分割,然后插入对应的位置 # 第一个、和最后一个有overlap的span进行分割,然后插入对应的位置
first_span_chars = [ first_span_chars = [
char char
for char in first_overlap_span["chars"] for char in first_overlap_span['chars']
if (char["bbox"][2] + char["bbox"][0]) / 2 < x0 if (char['bbox'][2] + char['bbox'][0]) / 2 < x0
] ]
tail_span_chars = [ tail_span_chars = [
char char
for char in last_overlap_span["chars"] for char in last_overlap_span['chars']
if (char["bbox"][0] + char["bbox"][2]) / 2 > x1 if (char['bbox'][0] + char['bbox'][2]) / 2 > x1
] ]
if len(first_span_chars) > 0: if len(first_span_chars) > 0:
first_overlap_span["chars"] = first_span_chars first_overlap_span['chars'] = first_span_chars
first_overlap_span["text"] = "".join([char["c"] for char in first_span_chars]) first_overlap_span['text'] = ''.join([char['c'] for char in first_span_chars])
first_overlap_span["bbox"] = ( first_overlap_span['bbox'] = (
first_overlap_span["bbox"][0], first_overlap_span['bbox'][0],
first_overlap_span["bbox"][1], first_overlap_span['bbox'][1],
max([chr["bbox"][2] for chr in first_span_chars]), max([chr['bbox'][2] for chr in first_span_chars]),
first_overlap_span["bbox"][3], first_overlap_span['bbox'][3],
) )
# first_overlap_span['_type'] = "first" # first_overlap_span['_type'] = "first"
else: else:
# 删掉 # 删掉
if first_overlap_span not in delete_span: if first_overlap_span not in delete_span:
line["spans"].remove(first_overlap_span) line['spans'].remove(first_overlap_span)
if len(tail_span_chars) > 0: if len(tail_span_chars) > 0:
min_of_tail_span_x0 = min([chr["bbox"][0] for chr in tail_span_chars]) min_of_tail_span_x0 = min([chr['bbox'][0] for chr in tail_span_chars])
min_of_tail_span_y0 = min([chr["bbox"][1] for chr in tail_span_chars]) min_of_tail_span_y0 = min([chr['bbox'][1] for chr in tail_span_chars])
max_of_tail_span_x1 = max([chr["bbox"][2] for chr in tail_span_chars]) max_of_tail_span_x1 = max([chr['bbox'][2] for chr in tail_span_chars])
max_of_tail_span_y1 = max([chr["bbox"][3] for chr in tail_span_chars]) max_of_tail_span_y1 = max([chr['bbox'][3] for chr in tail_span_chars])
if last_overlap_span == first_overlap_span: # 这个时候应该插入一个新的 if last_overlap_span == first_overlap_span: # 这个时候应该插入一个新的
tail_span_txt = "".join([char["c"] for char in tail_span_chars]) tail_span_txt = ''.join([char['c'] for char in tail_span_chars]) # noqa: F841
last_span_to_insert = last_overlap_span.copy() last_span_to_insert = last_overlap_span.copy()
last_span_to_insert["chars"] = tail_span_chars last_span_to_insert['chars'] = tail_span_chars
last_span_to_insert["text"] = "".join( last_span_to_insert['text'] = ''.join(
[char["c"] for char in tail_span_chars] [char['c'] for char in tail_span_chars]
) )
if equation_span["bbox"][2] >= last_overlap_span["bbox"][2]: if equation_span['bbox'][2] >= last_overlap_span['bbox'][2]:
last_span_to_insert["bbox"] = ( last_span_to_insert['bbox'] = (
min_of_tail_span_x0, min_of_tail_span_x0,
min_of_tail_span_y0, min_of_tail_span_y0,
max_of_tail_span_x1, max_of_tail_span_x1,
max_of_tail_span_y1 max_of_tail_span_y1,
) )
else: else:
last_span_to_insert["bbox"] = ( last_span_to_insert['bbox'] = (
min([chr["bbox"][0] for chr in tail_span_chars]), min([chr['bbox'][0] for chr in tail_span_chars]),
last_overlap_span["bbox"][1], last_overlap_span['bbox'][1],
last_overlap_span["bbox"][2], last_overlap_span['bbox'][2],
last_overlap_span["bbox"][3], last_overlap_span['bbox'][3],
) )
# 插入到公式对象之后 # 插入到公式对象之后
equation_idx = line["spans"].index(equation_span) equation_idx = line['spans'].index(equation_span)
line["spans"].insert(equation_idx + 1, last_span_to_insert) # 放入公式 line['spans'].insert(equation_idx + 1, last_span_to_insert) # 放入公式
else: # 直接修改原来的span else: # 直接修改原来的span
last_overlap_span["chars"] = tail_span_chars last_overlap_span['chars'] = tail_span_chars
last_overlap_span["text"] = "".join([char["c"] for char in tail_span_chars]) last_overlap_span['text'] = ''.join([char['c'] for char in tail_span_chars])
last_overlap_span["bbox"] = ( last_overlap_span['bbox'] = (
min([chr["bbox"][0] for chr in tail_span_chars]), min([chr['bbox'][0] for chr in tail_span_chars]),
last_overlap_span["bbox"][1], last_overlap_span['bbox'][1],
last_overlap_span["bbox"][2], last_overlap_span['bbox'][2],
last_overlap_span["bbox"][3], last_overlap_span['bbox'][3],
) )
else: else:
# 删掉 # 删掉
...@@ -406,15 +402,15 @@ def replace_line_v2(eqinfo, line): ...@@ -406,15 +402,15 @@ def replace_line_v2(eqinfo, line):
last_overlap_span not in delete_span last_overlap_span not in delete_span
and last_overlap_span != first_overlap_span and last_overlap_span != first_overlap_span
): ):
line["spans"].remove(last_overlap_span) line['spans'].remove(last_overlap_span)
remain_txt = "" remain_txt = ''
for span in line["spans"]: for span in line['spans']:
span_txt = "<span>" span_txt = '<span>'
for char in span["chars"]: for char in span['chars']:
span_txt = span_txt + char["c"] span_txt = span_txt + char['c']
span_txt = span_txt + "</span>" span_txt = span_txt + '</span>'
remain_txt = remain_txt + span_txt remain_txt = remain_txt + span_txt
...@@ -424,17 +420,15 @@ def replace_line_v2(eqinfo, line): ...@@ -424,17 +420,15 @@ def replace_line_v2(eqinfo, line):
def replace_eq_blk(eqinfo, text_block): def replace_eq_blk(eqinfo, text_block):
"""替换行内公式""" """替换行内公式."""
for line in text_block["lines"]: for line in text_block['lines']:
line_bbox = line["bbox"] line_bbox = line['bbox']
if ( if (
_is_xin(eqinfo["bbox"], line_bbox) _is_xin(eqinfo['bbox'], line_bbox)
or __y_overlap_ratio(eqinfo["bbox"], line_bbox) > 0.6 or __y_overlap_ratio(eqinfo['bbox'], line_bbox) > 0.6
): # 定位到行, 使用y方向重合率是因为有的时候,一个行的宽度会小于公式位置宽度:行很高,公式很窄, ): # 定位到行, 使用y方向重合率是因为有的时候,一个行的宽度会小于公式位置宽度:行很高,公式很窄,
replace_succ = replace_line_v2(eqinfo, line) replace_succ = replace_line_v2(eqinfo, line)
if ( if not replace_succ: # 有的时候,一个pdf的line高度从API里会计算的有问题,因此在行内span级别会替换不成功,这就需要继续重试下一行
not replace_succ
): # 有的时候,一个pdf的line高度从API里会计算的有问题,因此在行内span级别会替换不成功,这就需要继续重试下一行
continue continue
else: else:
break break
...@@ -444,13 +438,13 @@ def replace_eq_blk(eqinfo, text_block): ...@@ -444,13 +438,13 @@ def replace_eq_blk(eqinfo, text_block):
def replace_inline_equations(inline_equation_bboxes, raw_text_blocks): def replace_inline_equations(inline_equation_bboxes, raw_text_blocks):
"""替换行内公式""" """替换行内公式."""
for eqinfo in inline_equation_bboxes: for eqinfo in inline_equation_bboxes:
eqbox = eqinfo["bbox"] eqbox = eqinfo['bbox']
for blk in raw_text_blocks: for blk in raw_text_blocks:
if _is_xin(eqbox, blk["bbox"]): if _is_xin(eqbox, blk['bbox']):
if not replace_eq_blk(eqinfo, blk): if not replace_eq_blk(eqinfo, blk):
logger.warning(f"行内公式没有替换成功:{eqinfo} ") logger.warning(f'行内公式没有替换成功:{eqinfo} ')
else: else:
break break
...@@ -458,20 +452,18 @@ def replace_inline_equations(inline_equation_bboxes, raw_text_blocks): ...@@ -458,20 +452,18 @@ def replace_inline_equations(inline_equation_bboxes, raw_text_blocks):
def remove_chars_in_text_blocks(text_blocks): def remove_chars_in_text_blocks(text_blocks):
"""删除text_blocks里的char""" """删除text_blocks里的char."""
for blk in text_blocks: for blk in text_blocks:
for line in blk["lines"]: for line in blk['lines']:
for span in line["spans"]: for span in line['spans']:
_ = span.pop("chars", "no such key") _ = span.pop('chars', 'no such key')
return text_blocks return text_blocks
def replace_equations_in_textblock( def replace_equations_in_textblock(
raw_text_blocks, inline_equation_bboxes, interline_equation_bboxes raw_text_blocks, inline_equation_bboxes, interline_equation_bboxes
): ):
""" """替换行间和和行内公式为latex."""
替换行间和和行内公式为latex
"""
raw_text_blocks = remove_text_block_in_interline_equation_bbox( raw_text_blocks = remove_text_block_in_interline_equation_bbox(
interline_equation_bboxes, raw_text_blocks interline_equation_bboxes, raw_text_blocks
) # 消除重叠:第一步,在公式内部的 ) # 消除重叠:第一步,在公式内部的
...@@ -486,22 +478,22 @@ def replace_equations_in_textblock( ...@@ -486,22 +478,22 @@ def replace_equations_in_textblock(
def draw_block_on_pdf_with_txt_replace_eq_bbox(json_path, pdf_path): def draw_block_on_pdf_with_txt_replace_eq_bbox(json_path, pdf_path):
""" """ """"""
new_pdf = f"{Path(pdf_path).parent}/{Path(pdf_path).stem}.step3-消除行内公式text_block.pdf" new_pdf = f'{Path(pdf_path).parent}/{Path(pdf_path).stem}.step3-消除行内公式text_block.pdf'
with open(json_path, "r", encoding="utf-8") as f: with open(json_path, 'r', encoding='utf-8') as f:
obj = json.loads(f.read()) obj = json.loads(f.read())
if os.path.exists(new_pdf): if os.path.exists(new_pdf):
os.remove(new_pdf) os.remove(new_pdf)
new_doc = fitz.open("") new_doc = fitz.open('')
doc = fitz.open(pdf_path) doc = fitz.open(pdf_path) # noqa: F841
new_doc = fitz.open(pdf_path) new_doc = fitz.open(pdf_path)
for i in range(len(new_doc)): for i in range(len(new_doc)):
page = new_doc[i] page = new_doc[i]
inline_equation_bboxes = obj[f"page_{i}"]["inline_equations"] inline_equation_bboxes = obj[f'page_{i}']['inline_equations']
interline_equation_bboxes = obj[f"page_{i}"]["interline_equations"] interline_equation_bboxes = obj[f'page_{i}']['interline_equations']
raw_text_blocks = obj[f"page_{i}"]["preproc_blocks"] raw_text_blocks = obj[f'page_{i}']['preproc_blocks']
raw_text_blocks = remove_text_block_in_interline_equation_bbox( raw_text_blocks = remove_text_block_in_interline_equation_bbox(
interline_equation_bboxes, raw_text_blocks interline_equation_bboxes, raw_text_blocks
) # 消除重叠:第一步,在公式内部的 ) # 消除重叠:第一步,在公式内部的
...@@ -514,11 +506,10 @@ def draw_block_on_pdf_with_txt_replace_eq_bbox(json_path, pdf_path): ...@@ -514,11 +506,10 @@ def draw_block_on_pdf_with_txt_replace_eq_bbox(json_path, pdf_path):
) )
# 为了检验公式是否重复,把每一行里,含有公式的span背景改成黄色的 # 为了检验公式是否重复,把每一行里,含有公式的span背景改成黄色的
color_map = [fitz.pdfcolor["blue"], fitz.pdfcolor["green"]] color_map = [fitz.pdfcolor['blue'], fitz.pdfcolor['green']] # noqa: F841
j = 0 j = 0 # noqa: F841
for blk in raw_text_blocks: for blk in raw_text_blocks:
for i, line in enumerate(blk["lines"]): for i, line in enumerate(blk['lines']):
# line_box = line['bbox'] # line_box = line['bbox']
# shape = page.new_shape() # shape = page.new_shape()
# shape.draw_rect(line_box) # shape.draw_rect(line_box)
...@@ -526,34 +517,34 @@ def draw_block_on_pdf_with_txt_replace_eq_bbox(json_path, pdf_path): ...@@ -526,34 +517,34 @@ def draw_block_on_pdf_with_txt_replace_eq_bbox(json_path, pdf_path):
# shape.commit() # shape.commit()
# j = j+1 # j = j+1
for i, span in enumerate(line["spans"]): for i, span in enumerate(line['spans']):
shape_page = page.new_shape() shape_page = page.new_shape()
span_type = span.get("_type") span_type = span.get('_type')
color = fitz.pdfcolor["blue"] color = fitz.pdfcolor['blue']
if span_type == "first": if span_type == 'first':
color = fitz.pdfcolor["blue"] color = fitz.pdfcolor['blue']
elif span_type == "tail": elif span_type == 'tail':
color = fitz.pdfcolor["green"] color = fitz.pdfcolor['green']
elif span_type == TYPE_INLINE_EQUATION: elif span_type == TYPE_INLINE_EQUATION:
color = fitz.pdfcolor["black"] color = fitz.pdfcolor['black']
else: else:
color = None color = None
b = span["bbox"] b = span['bbox']
shape_page.draw_rect(b) shape_page.draw_rect(b)
shape_page.finish(color=None, fill=color, fill_opacity=0.3) shape_page.finish(color=None, fill=color, fill_opacity=0.3)
shape_page.commit() shape_page.commit()
new_doc.save(new_pdf) new_doc.save(new_pdf)
logger.info(f"save ok {new_pdf}") logger.info(f'save ok {new_pdf}')
final_json = json.dumps(obj, ensure_ascii=False, indent=2) final_json = json.dumps(obj, ensure_ascii=False, indent=2)
with open("equations_test/final_json.json", "w") as f: with open('equations_test/final_json.json', 'w') as f:
f.write(final_json) f.write(final_json)
return new_pdf return new_pdf
if __name__ == "__main__": if __name__ == '__main__':
# draw_block_on_pdf_with_txt_replace_eq_bbox(new_json_path, equation_color_pdf) # draw_block_on_pdf_with_txt_replace_eq_bbox(new_json_path, equation_color_pdf)
pass pass
from loguru import logger
from magic_pdf.libs.boxbase import get_minbox_if_overlap_by_ratio, calculate_overlap_area_in_bbox1_area_ratio, \ from magic_pdf.config.ocr_content_type import BlockType
calculate_iou, calculate_vertical_projection_overlap_ratio from magic_pdf.libs.boxbase import (
from magic_pdf.libs.drop_tag import DropTag calculate_iou, calculate_overlap_area_in_bbox1_area_ratio,
from magic_pdf.libs.ocr_content_type import BlockType calculate_vertical_projection_overlap_ratio,
from magic_pdf.pre_proc.remove_bbox_overlap import remove_overlap_between_bbox_for_block get_minbox_if_overlap_by_ratio)
from magic_pdf.pre_proc.remove_bbox_overlap import \
remove_overlap_between_bbox_for_block
def ocr_prepare_bboxes_for_layout_split(img_blocks, table_blocks, discarded_blocks, text_blocks,
title_blocks, interline_equation_blocks, page_w, page_h):
def ocr_prepare_bboxes_for_layout_split(
img_blocks,
table_blocks,
discarded_blocks,
text_blocks,
title_blocks,
interline_equation_blocks,
page_w,
page_h,
):
all_bboxes = [] all_bboxes = []
all_discarded_blocks = [] all_discarded_blocks = []
for image in img_blocks: for image in img_blocks:
x0, y0, x1, y1 = image['bbox'] x0, y0, x1, y1 = image['bbox']
all_bboxes.append([x0, y0, x1, y1, None, None, None, BlockType.Image, None, None, None, None, image["score"]]) all_bboxes.append(
[
x0,
y0,
x1,
y1,
None,
None,
None,
BlockType.Image,
None,
None,
None,
None,
image['score'],
]
)
for table in table_blocks: for table in table_blocks:
x0, y0, x1, y1 = table['bbox'] x0, y0, x1, y1 = table['bbox']
all_bboxes.append([x0, y0, x1, y1, None, None, None, BlockType.Table, None, None, None, None, table["score"]]) all_bboxes.append(
[
x0,
y0,
x1,
y1,
None,
None,
None,
BlockType.Table,
None,
None,
None,
None,
table['score'],
]
)
for text in text_blocks: for text in text_blocks:
x0, y0, x1, y1 = text['bbox'] x0, y0, x1, y1 = text['bbox']
all_bboxes.append([x0, y0, x1, y1, None, None, None, BlockType.Text, None, None, None, None, text["score"]]) all_bboxes.append(
[
x0,
y0,
x1,
y1,
None,
None,
None,
BlockType.Text,
None,
None,
None,
None,
text['score'],
]
)
for title in title_blocks: for title in title_blocks:
x0, y0, x1, y1 = title['bbox'] x0, y0, x1, y1 = title['bbox']
all_bboxes.append([x0, y0, x1, y1, None, None, None, BlockType.Title, None, None, None, None, title["score"]]) all_bboxes.append(
[
x0,
y0,
x1,
y1,
None,
None,
None,
BlockType.Title,
None,
None,
None,
None,
title['score'],
]
)
for interline_equation in interline_equation_blocks: for interline_equation in interline_equation_blocks:
x0, y0, x1, y1 = interline_equation['bbox'] x0, y0, x1, y1 = interline_equation['bbox']
all_bboxes.append([x0, y0, x1, y1, None, None, None, BlockType.InterlineEquation, None, None, None, None, interline_equation["score"]]) all_bboxes.append(
[
'''block嵌套问题解决''' x0,
'''文本框与标题框重叠,优先信任文本框''' y0,
x1,
y1,
None,
None,
None,
BlockType.InterlineEquation,
None,
None,
None,
None,
interline_equation['score'],
]
)
"""block嵌套问题解决"""
"""文本框与标题框重叠,优先信任文本框"""
all_bboxes = fix_text_overlap_title_blocks(all_bboxes) all_bboxes = fix_text_overlap_title_blocks(all_bboxes)
'''任何框体与舍弃框重叠,优先信任舍弃框''' """任何框体与舍弃框重叠,优先信任舍弃框"""
all_bboxes = remove_need_drop_blocks(all_bboxes, discarded_blocks) all_bboxes = remove_need_drop_blocks(all_bboxes, discarded_blocks)
# interline_equation 与title或text框冲突的情况,分两种情况处理 # interline_equation 与title或text框冲突的情况,分两种情况处理
'''interline_equation框与文本类型框iou比较接近1的时候,信任行间公式框''' """interline_equation框与文本类型框iou比较接近1的时候,信任行间公式框"""
all_bboxes = fix_interline_equation_overlap_text_blocks_with_hi_iou(all_bboxes) all_bboxes = fix_interline_equation_overlap_text_blocks_with_hi_iou(all_bboxes)
'''interline_equation框被包含在文本类型框内,且interline_equation比文本区块小很多时信任文本框,这时需要舍弃公式框''' """interline_equation框被包含在文本类型框内,且interline_equation比文本区块小很多时信任文本框,这时需要舍弃公式框"""
# 通过后续大框套小框逻辑删除 # 通过后续大框套小框逻辑删除
'''discarded_blocks中只保留宽度超过1/3页面宽度的,高度超过10的,处于页面下半50%区域的(限定footnote)''' """discarded_blocks中只保留宽度超过1/3页面宽度的,高度超过10的,处于页面下半50%区域的(限定footnote)"""
for discarded in discarded_blocks: for discarded in discarded_blocks:
x0, y0, x1, y1 = discarded['bbox'] x0, y0, x1, y1 = discarded['bbox']
all_discarded_blocks.append([x0, y0, x1, y1, None, None, None, BlockType.Discarded, None, None, None, None, discarded["score"]]) all_discarded_blocks.append(
[
x0,
y0,
x1,
y1,
None,
None,
None,
BlockType.Discarded,
None,
None,
None,
None,
discarded['score'],
]
)
# 将footnote加入到all_bboxes中,用来计算layout # 将footnote加入到all_bboxes中,用来计算layout
if (x1 - x0) > (page_w / 3) and (y1 - y0) > 10 and y0 > (page_h / 2): if (x1 - x0) > (page_w / 3) and (y1 - y0) > 10 and y0 > (page_h / 2):
all_bboxes.append([x0, y0, x1, y1, None, None, None, BlockType.Footnote, None, None, None, None, discarded["score"]]) all_bboxes.append(
[
'''经过以上处理后,还存在大框套小框的情况,则删除小框''' x0,
y0,
x1,
y1,
None,
None,
None,
BlockType.Footnote,
None,
None,
None,
None,
discarded['score'],
]
)
"""经过以上处理后,还存在大框套小框的情况,则删除小框"""
all_bboxes = remove_overlaps_min_blocks(all_bboxes) all_bboxes = remove_overlaps_min_blocks(all_bboxes)
all_discarded_blocks = remove_overlaps_min_blocks(all_discarded_blocks) all_discarded_blocks = remove_overlaps_min_blocks(all_discarded_blocks)
'''将剩余的bbox做分离处理,防止后面分layout时出错''' """将剩余的bbox做分离处理,防止后面分layout时出错"""
all_bboxes, drop_reasons = remove_overlap_between_bbox_for_block(all_bboxes) all_bboxes, drop_reasons = remove_overlap_between_bbox_for_block(all_bboxes)
return all_bboxes, all_discarded_blocks, drop_reasons return all_bboxes, all_discarded_blocks, drop_reasons
...@@ -64,18 +185,64 @@ def add_bboxes(blocks, block_type, bboxes): ...@@ -64,18 +185,64 @@ def add_bboxes(blocks, block_type, bboxes):
for block in blocks: for block in blocks:
x0, y0, x1, y1 = block['bbox'] x0, y0, x1, y1 = block['bbox']
if block_type in [ if block_type in [
BlockType.ImageBody, BlockType.ImageCaption, BlockType.ImageFootnote, BlockType.ImageBody,
BlockType.TableBody, BlockType.TableCaption, BlockType.TableFootnote BlockType.ImageCaption,
BlockType.ImageFootnote,
BlockType.TableBody,
BlockType.TableCaption,
BlockType.TableFootnote,
]: ]:
bboxes.append([x0, y0, x1, y1, None, None, None, block_type, None, None, None, None, block["score"], block["group_id"]]) bboxes.append(
[
x0,
y0,
x1,
y1,
None,
None,
None,
block_type,
None,
None,
None,
None,
block['score'],
block['group_id'],
]
)
else: else:
bboxes.append([x0, y0, x1, y1, None, None, None, block_type, None, None, None, None, block["score"]]) bboxes.append(
[
x0,
y0,
x1,
y1,
None,
None,
None,
block_type,
None,
None,
None,
None,
block['score'],
]
)
def ocr_prepare_bboxes_for_layout_split_v2( def ocr_prepare_bboxes_for_layout_split_v2(
img_body_blocks, img_caption_blocks, img_footnote_blocks, img_body_blocks,
table_body_blocks, table_caption_blocks, table_footnote_blocks, img_caption_blocks,
discarded_blocks, text_blocks, title_blocks, interline_equation_blocks, page_w, page_h img_footnote_blocks,
table_body_blocks,
table_caption_blocks,
table_footnote_blocks,
discarded_blocks,
text_blocks,
title_blocks,
interline_equation_blocks,
page_w,
page_h,
): ):
all_bboxes = [] all_bboxes = []
...@@ -89,40 +256,40 @@ def ocr_prepare_bboxes_for_layout_split_v2( ...@@ -89,40 +256,40 @@ def ocr_prepare_bboxes_for_layout_split_v2(
add_bboxes(title_blocks, BlockType.Title, all_bboxes) add_bboxes(title_blocks, BlockType.Title, all_bboxes)
add_bboxes(interline_equation_blocks, BlockType.InterlineEquation, all_bboxes) add_bboxes(interline_equation_blocks, BlockType.InterlineEquation, all_bboxes)
'''block嵌套问题解决''' """block嵌套问题解决"""
'''文本框与标题框重叠,优先信任文本框''' """文本框与标题框重叠,优先信任文本框"""
all_bboxes = fix_text_overlap_title_blocks(all_bboxes) all_bboxes = fix_text_overlap_title_blocks(all_bboxes)
'''任何框体与舍弃框重叠,优先信任舍弃框''' """任何框体与舍弃框重叠,优先信任舍弃框"""
all_bboxes = remove_need_drop_blocks(all_bboxes, discarded_blocks) all_bboxes = remove_need_drop_blocks(all_bboxes, discarded_blocks)
# interline_equation 与title或text框冲突的情况,分两种情况处理 # interline_equation 与title或text框冲突的情况,分两种情况处理
'''interline_equation框与文本类型框iou比较接近1的时候,信任行间公式框''' """interline_equation框与文本类型框iou比较接近1的时候,信任行间公式框"""
all_bboxes = fix_interline_equation_overlap_text_blocks_with_hi_iou(all_bboxes) all_bboxes = fix_interline_equation_overlap_text_blocks_with_hi_iou(all_bboxes)
'''interline_equation框被包含在文本类型框内,且interline_equation比文本区块小很多时信任文本框,这时需要舍弃公式框''' """interline_equation框被包含在文本类型框内,且interline_equation比文本区块小很多时信任文本框,这时需要舍弃公式框"""
# 通过后续大框套小框逻辑删除 # 通过后续大框套小框逻辑删除
'''discarded_blocks''' """discarded_blocks"""
all_discarded_blocks = [] all_discarded_blocks = []
add_bboxes(discarded_blocks, BlockType.Discarded, all_discarded_blocks) add_bboxes(discarded_blocks, BlockType.Discarded, all_discarded_blocks)
'''footnote识别:宽度超过1/3页面宽度的,高度超过10的,处于页面下半50%区域的''' """footnote识别:宽度超过1/3页面宽度的,高度超过10的,处于页面下半50%区域的"""
footnote_blocks = [] footnote_blocks = []
for discarded in discarded_blocks: for discarded in discarded_blocks:
x0, y0, x1, y1 = discarded['bbox'] x0, y0, x1, y1 = discarded['bbox']
if (x1 - x0) > (page_w / 3) and (y1 - y0) > 10 and y0 > (page_h / 2): if (x1 - x0) > (page_w / 3) and (y1 - y0) > 10 and y0 > (page_h / 2):
footnote_blocks.append([x0, y0, x1, y1]) footnote_blocks.append([x0, y0, x1, y1])
'''移除在footnote下面的任何框''' """移除在footnote下面的任何框"""
need_remove_blocks = find_blocks_under_footnote(all_bboxes, footnote_blocks) need_remove_blocks = find_blocks_under_footnote(all_bboxes, footnote_blocks)
if len(need_remove_blocks) > 0: if len(need_remove_blocks) > 0:
for block in need_remove_blocks: for block in need_remove_blocks:
all_bboxes.remove(block) all_bboxes.remove(block)
all_discarded_blocks.append(block) all_discarded_blocks.append(block)
'''经过以上处理后,还存在大框套小框的情况,则删除小框''' """经过以上处理后,还存在大框套小框的情况,则删除小框"""
all_bboxes = remove_overlaps_min_blocks(all_bboxes) all_bboxes = remove_overlaps_min_blocks(all_bboxes)
all_discarded_blocks = remove_overlaps_min_blocks(all_discarded_blocks) all_discarded_blocks = remove_overlaps_min_blocks(all_discarded_blocks)
'''将剩余的bbox做分离处理,防止后面分layout时出错''' """将剩余的bbox做分离处理,防止后面分layout时出错"""
all_bboxes, drop_reasons = remove_overlap_between_bbox_for_block(all_bboxes) all_bboxes, drop_reasons = remove_overlap_between_bbox_for_block(all_bboxes)
return all_bboxes, all_discarded_blocks return all_bboxes, all_discarded_blocks
...@@ -135,7 +302,13 @@ def find_blocks_under_footnote(all_bboxes, footnote_blocks): ...@@ -135,7 +302,13 @@ def find_blocks_under_footnote(all_bboxes, footnote_blocks):
for footnote_bbox in footnote_blocks: for footnote_bbox in footnote_blocks:
footnote_x0, footnote_y0, footnote_x1, footnote_y1 = footnote_bbox footnote_x0, footnote_y0, footnote_x1, footnote_y1 = footnote_bbox
# 如果footnote的纵向投影覆盖了block的纵向投影的80%且block的y0大于等于footnote的y1 # 如果footnote的纵向投影覆盖了block的纵向投影的80%且block的y0大于等于footnote的y1
if block_y0 >= footnote_y1 and calculate_vertical_projection_overlap_ratio((block_x0, block_y0, block_x1, block_y1), footnote_bbox) >= 0.8: if (
block_y0 >= footnote_y1
and calculate_vertical_projection_overlap_ratio(
(block_x0, block_y0, block_x1, block_y1), footnote_bbox
)
>= 0.8
):
if block not in need_remove_blocks: if block not in need_remove_blocks:
need_remove_blocks.append(block) need_remove_blocks.append(block)
break break
...@@ -203,7 +376,12 @@ def remove_need_drop_blocks(all_bboxes, discarded_blocks): ...@@ -203,7 +376,12 @@ def remove_need_drop_blocks(all_bboxes, discarded_blocks):
for block in all_bboxes: for block in all_bboxes:
for discarded_block in discarded_blocks: for discarded_block in discarded_blocks:
block_bbox = block[:4] block_bbox = block[:4]
if calculate_overlap_area_in_bbox1_area_ratio(block_bbox, discarded_block['bbox']) > 0.6: if (
calculate_overlap_area_in_bbox1_area_ratio(
block_bbox, discarded_block['bbox']
)
> 0.6
):
if block not in need_remove: if block not in need_remove:
need_remove.append(block) need_remove.append(block)
break break
...@@ -223,10 +401,18 @@ def remove_overlaps_min_blocks(all_bboxes): ...@@ -223,10 +401,18 @@ def remove_overlaps_min_blocks(all_bboxes):
if block1 != block2: if block1 != block2:
block1_bbox = block1[:4] block1_bbox = block1[:4]
block2_bbox = block2[:4] block2_bbox = block2[:4]
overlap_box = get_minbox_if_overlap_by_ratio(block1_bbox, block2_bbox, 0.8) overlap_box = get_minbox_if_overlap_by_ratio(
block1_bbox, block2_bbox, 0.8
)
if overlap_box is not None: if overlap_box is not None:
block_to_remove = next((block for block in all_bboxes if block[:4] == overlap_box), None) block_to_remove = next(
if block_to_remove is not None and block_to_remove not in need_remove: (block for block in all_bboxes if block[:4] == overlap_box),
None,
)
if (
block_to_remove is not None
and block_to_remove not in need_remove
):
large_block = block1 if block1 != block_to_remove else block2 large_block = block1 if block1 != block_to_remove else block2
x1, y1, x2, y2 = large_block[:4] x1, y1, x2, y2 = large_block[:4]
sx1, sy1, sx2, sy2 = block_to_remove[:4] sx1, sy1, sx2, sy2 = block_to_remove[:4]
......
from magic_pdf.config.drop_tag import DropTag
from magic_pdf.config.ocr_content_type import BlockType, ContentType
from magic_pdf.libs.boxbase import (__is_overlaps_y_exceeds_threshold, from magic_pdf.libs.boxbase import (__is_overlaps_y_exceeds_threshold,
_is_in_or_part_overlap_with_area_ratio, _is_in_or_part_overlap_with_area_ratio,
calculate_overlap_area_in_bbox1_area_ratio) calculate_overlap_area_in_bbox1_area_ratio)
from magic_pdf.libs.drop_tag import DropTag
from magic_pdf.libs.ocr_content_type import BlockType, ContentType
# 将每一个line中的span从左到右排序 # 将每一个line中的span从左到右排序
...@@ -157,7 +157,7 @@ def fill_spans_in_blocks(blocks, spans, radio): ...@@ -157,7 +157,7 @@ def fill_spans_in_blocks(blocks, spans, radio):
BlockType.ImageBody, BlockType.ImageCaption, BlockType.ImageFootnote, BlockType.ImageBody, BlockType.ImageCaption, BlockType.ImageFootnote,
BlockType.TableBody, BlockType.TableCaption, BlockType.TableFootnote BlockType.TableBody, BlockType.TableCaption, BlockType.TableFootnote
]: ]:
block_dict["group_id"] = block[-1] block_dict['group_id'] = block[-1]
block_spans = [] block_spans = []
for span in spans: for span in spans:
span_bbox = span['bbox'] span_bbox = span['bbox']
......
from loguru import logger
from magic_pdf.libs.boxbase import calculate_overlap_area_in_bbox1_area_ratio, get_minbox_if_overlap_by_ratio, \ from magic_pdf.config.drop_tag import DropTag
__is_overlaps_y_exceeds_threshold, calculate_iou from magic_pdf.config.ocr_content_type import BlockType, ContentType
from magic_pdf.libs.drop_tag import DropTag from magic_pdf.libs.boxbase import (__is_overlaps_y_exceeds_threshold,
from magic_pdf.libs.ocr_content_type import ContentType, BlockType calculate_iou,
calculate_overlap_area_in_bbox1_area_ratio,
get_minbox_if_overlap_by_ratio)
def remove_overlaps_low_confidence_spans(spans): def remove_overlaps_low_confidence_spans(spans):
...@@ -21,7 +22,10 @@ def remove_overlaps_low_confidence_spans(spans): ...@@ -21,7 +22,10 @@ def remove_overlaps_low_confidence_spans(spans):
span_need_remove = span1 span_need_remove = span1
else: else:
span_need_remove = span2 span_need_remove = span2
if span_need_remove is not None and span_need_remove not in dropped_spans: if (
span_need_remove is not None
and span_need_remove not in dropped_spans
):
dropped_spans.append(span_need_remove) dropped_spans.append(span_need_remove)
if len(dropped_spans) > 0: if len(dropped_spans) > 0:
...@@ -38,10 +42,17 @@ def remove_overlaps_min_spans(spans): ...@@ -38,10 +42,17 @@ def remove_overlaps_min_spans(spans):
for span1 in spans: for span1 in spans:
for span2 in spans: for span2 in spans:
if span1 != span2: if span1 != span2:
overlap_box = get_minbox_if_overlap_by_ratio(span1['bbox'], span2['bbox'], 0.65) overlap_box = get_minbox_if_overlap_by_ratio(
span1['bbox'], span2['bbox'], 0.65
)
if overlap_box is not None: if overlap_box is not None:
span_need_remove = next((span for span in spans if span['bbox'] == overlap_box), None) span_need_remove = next(
if span_need_remove is not None and span_need_remove not in dropped_spans: (span for span in spans if span['bbox'] == overlap_box), None
)
if (
span_need_remove is not None
and span_need_remove not in dropped_spans
):
dropped_spans.append(span_need_remove) dropped_spans.append(span_need_remove)
if len(dropped_spans) > 0: if len(dropped_spans) > 0:
...@@ -58,7 +69,10 @@ def remove_spans_by_bboxes(spans, need_remove_spans_bboxes): ...@@ -58,7 +69,10 @@ def remove_spans_by_bboxes(spans, need_remove_spans_bboxes):
need_remove_spans = [] need_remove_spans = []
for span in spans: for span in spans:
for removed_bbox in need_remove_spans_bboxes: for removed_bbox in need_remove_spans_bboxes:
if calculate_overlap_area_in_bbox1_area_ratio(span['bbox'], removed_bbox) > 0.5: if (
calculate_overlap_area_in_bbox1_area_ratio(span['bbox'], removed_bbox)
> 0.5
):
if span not in need_remove_spans: if span not in need_remove_spans:
need_remove_spans.append(span) need_remove_spans.append(span)
break break
...@@ -78,12 +92,22 @@ def remove_spans_by_bboxes_dict(spans, need_remove_spans_bboxes_dict): ...@@ -78,12 +92,22 @@ def remove_spans_by_bboxes_dict(spans, need_remove_spans_bboxes_dict):
for span in spans: for span in spans:
# 通过判断span的bbox是否在removed_bboxes中, 判断是否需要删除该span # 通过判断span的bbox是否在removed_bboxes中, 判断是否需要删除该span
for removed_bbox in removed_bboxes: for removed_bbox in removed_bboxes:
if calculate_overlap_area_in_bbox1_area_ratio(span['bbox'], removed_bbox) > 0.5: if (
calculate_overlap_area_in_bbox1_area_ratio(
span['bbox'], removed_bbox
)
> 0.5
):
need_remove_spans.append(span) need_remove_spans.append(span)
break break
# 当drop_tag为DropTag.FOOTNOTE时, 判断span是否在removed_bboxes中任意一个的下方,如果是,则删除该span # 当drop_tag为DropTag.FOOTNOTE时, 判断span是否在removed_bboxes中任意一个的下方,如果是,则删除该span
elif drop_tag == DropTag.FOOTNOTE and (span['bbox'][1] + span['bbox'][3]) / 2 > removed_bbox[3] and \ elif (
removed_bbox[0] < (span['bbox'][0] + span['bbox'][2]) / 2 < removed_bbox[2]: drop_tag == DropTag.FOOTNOTE
and (span['bbox'][1] + span['bbox'][3]) / 2 > removed_bbox[3]
and removed_bbox[0]
< (span['bbox'][0] + span['bbox'][2]) / 2
< removed_bbox[2]
):
need_remove_spans.append(span) need_remove_spans.append(span)
break break
...@@ -98,11 +122,18 @@ def remove_spans_by_bboxes_dict(spans, need_remove_spans_bboxes_dict): ...@@ -98,11 +122,18 @@ def remove_spans_by_bboxes_dict(spans, need_remove_spans_bboxes_dict):
def adjust_bbox_for_standalone_block(spans): def adjust_bbox_for_standalone_block(spans):
# 对tpye=["interline_equation", "image", "table"]进行额外处理,如果左边有字的话,将该span的bbox中y0调整至不高于文字的y0 # 对tpye=["interline_equation", "image", "table"]进行额外处理,如果左边有字的话,将该span的bbox中y0调整至不高于文字的y0
for sb_span in spans: for sb_span in spans:
if sb_span['type'] in [ContentType.InterlineEquation, ContentType.Image, ContentType.Table]: if sb_span['type'] in [
ContentType.InterlineEquation,
ContentType.Image,
ContentType.Table,
]:
for text_span in spans: for text_span in spans:
if text_span['type'] in [ContentType.Text, ContentType.InlineEquation]: if text_span['type'] in [ContentType.Text, ContentType.InlineEquation]:
# 判断span2的纵向高度是否被span所覆盖 # 判断span2的纵向高度是否被span所覆盖
if sb_span['bbox'][1] < text_span['bbox'][1] and sb_span['bbox'][3] > text_span['bbox'][3]: if (
sb_span['bbox'][1] < text_span['bbox'][1]
and sb_span['bbox'][3] > text_span['bbox'][3]
):
# 判断span2是否在span左边 # 判断span2是否在span左边
if text_span['bbox'][0] < sb_span['bbox'][0]: if text_span['bbox'][0] < sb_span['bbox'][0]:
# 调整span的y0和span2的y0一致 # 调整span的y0和span2的y0一致
...@@ -120,11 +151,15 @@ def modify_y_axis(spans: list, displayed_list: list, text_inline_lines: list): ...@@ -120,11 +151,15 @@ def modify_y_axis(spans: list, displayed_list: list, text_inline_lines: list):
lines = [] lines = []
current_line = [spans[0]] current_line = [spans[0]]
if spans[0]["type"] in [ContentType.InterlineEquation, ContentType.Image, ContentType.Table]: if spans[0]['type'] in [
ContentType.InterlineEquation,
ContentType.Image,
ContentType.Table,
]:
displayed_list.append(spans[0]) displayed_list.append(spans[0])
line_first_y0 = spans[0]["bbox"][1] line_first_y0 = spans[0]['bbox'][1]
line_first_y = spans[0]["bbox"][3] line_first_y = spans[0]['bbox'][3]
# 用于给行间公式搜索 # 用于给行间公式搜索
# text_inline_lines = [] # text_inline_lines = []
for span in spans[1:]: for span in spans[1:]:
...@@ -132,26 +167,43 @@ def modify_y_axis(spans: list, displayed_list: list, text_inline_lines: list): ...@@ -132,26 +167,43 @@ def modify_y_axis(spans: list, displayed_list: list, text_inline_lines: list):
# print("debug") # print("debug")
# 如果当前的span类型为"interline_equation" 或者 当前行中已经有"interline_equation" # 如果当前的span类型为"interline_equation" 或者 当前行中已经有"interline_equation"
# image和table类型,同上 # image和table类型,同上
if span['type'] in [ContentType.InterlineEquation, ContentType.Image, ContentType.Table] or any( if span['type'] in [
s['type'] in [ContentType.InterlineEquation, ContentType.Image, ContentType.Table] for s in ContentType.InterlineEquation,
current_line): ContentType.Image,
ContentType.Table,
] or any(
s['type']
in [ContentType.InterlineEquation, ContentType.Image, ContentType.Table]
for s in current_line
):
# 传入 # 传入
if span["type"] in [ContentType.InterlineEquation, ContentType.Image, ContentType.Table]: if span['type'] in [
ContentType.InterlineEquation,
ContentType.Image,
ContentType.Table,
]:
displayed_list.append(span) displayed_list.append(span)
# 则开始新行 # 则开始新行
lines.append(current_line) lines.append(current_line)
if len(current_line) > 1 or current_line[0]["type"] in [ContentType.Text, ContentType.InlineEquation]: if len(current_line) > 1 or current_line[0]['type'] in [
text_inline_lines.append((current_line, (line_first_y0, line_first_y))) ContentType.Text,
ContentType.InlineEquation,
]:
text_inline_lines.append(
(current_line, (line_first_y0, line_first_y))
)
current_line = [span] current_line = [span]
line_first_y0 = span["bbox"][1] line_first_y0 = span['bbox'][1]
line_first_y = span["bbox"][3] line_first_y = span['bbox'][3]
continue continue
# 如果当前的span与当前行的最后一个span在y轴上重叠,则添加到当前行 # 如果当前的span与当前行的最后一个span在y轴上重叠,则添加到当前行
if __is_overlaps_y_exceeds_threshold(span['bbox'], current_line[-1]['bbox']): if __is_overlaps_y_exceeds_threshold(
if span["type"] == "text": span['bbox'], current_line[-1]['bbox']
line_first_y0 = span["bbox"][1] ):
line_first_y = span["bbox"][3] if span['type'] == 'text':
line_first_y0 = span['bbox'][1]
line_first_y = span['bbox'][3]
current_line.append(span) current_line.append(span)
else: else:
...@@ -159,13 +211,16 @@ def modify_y_axis(spans: list, displayed_list: list, text_inline_lines: list): ...@@ -159,13 +211,16 @@ def modify_y_axis(spans: list, displayed_list: list, text_inline_lines: list):
lines.append(current_line) lines.append(current_line)
text_inline_lines.append((current_line, (line_first_y0, line_first_y))) text_inline_lines.append((current_line, (line_first_y0, line_first_y)))
current_line = [span] current_line = [span]
line_first_y0 = span["bbox"][1] line_first_y0 = span['bbox'][1]
line_first_y = span["bbox"][3] line_first_y = span['bbox'][3]
# 添加最后一行 # 添加最后一行
if current_line: if current_line:
lines.append(current_line) lines.append(current_line)
if len(current_line) > 1 or current_line[0]["type"] in [ContentType.Text, ContentType.InlineEquation]: if len(current_line) > 1 or current_line[0]['type'] in [
ContentType.Text,
ContentType.InlineEquation,
]:
text_inline_lines.append((current_line, (line_first_y0, line_first_y))) text_inline_lines.append((current_line, (line_first_y0, line_first_y)))
for line in text_inline_lines: for line in text_inline_lines:
# 按照x0坐标排序 # 按照x0坐标排序
...@@ -176,8 +231,8 @@ def modify_y_axis(spans: list, displayed_list: list, text_inline_lines: list): ...@@ -176,8 +231,8 @@ def modify_y_axis(spans: list, displayed_list: list, text_inline_lines: list):
for line in text_inline_lines: for line in text_inline_lines:
current_line, (line_first_y0, line_first_y) = line current_line, (line_first_y0, line_first_y) = line
for span in current_line: for span in current_line:
span["bbox"][1] = line_first_y0 span['bbox'][1] = line_first_y0
span["bbox"][3] = line_first_y span['bbox'][3] = line_first_y
# return spans, displayed_list, text_inline_lines # return spans, displayed_list, text_inline_lines
...@@ -189,34 +244,42 @@ def modify_inline_equation(spans: list, displayed_list: list, text_inline_lines: ...@@ -189,34 +244,42 @@ def modify_inline_equation(spans: list, displayed_list: list, text_inline_lines:
# if i == 8: # if i == 8:
# print("debug") # print("debug")
span = displayed_list[i] span = displayed_list[i]
span_y0, span_y = span["bbox"][1], span["bbox"][3] span_y0, span_y = span['bbox'][1], span['bbox'][3]
while j < len(text_inline_lines): while j < len(text_inline_lines):
text_line = text_inline_lines[j] text_line = text_inline_lines[j]
y0, y1 = text_line[1] y0, y1 = text_line[1]
if ( if (
span_y0 < y0 < span_y or span_y0 < y1 < span_y or span_y0 < y0 and span_y > y1 span_y0 < y0 < span_y
) and __is_overlaps_y_exceeds_threshold( or span_y0 < y1 < span_y
span['bbox'], (0, y0, 0, y1) or span_y0 < y0
): and span_y > y1
) and __is_overlaps_y_exceeds_threshold(span['bbox'], (0, y0, 0, y1)):
# 调整公式类型 # 调整公式类型
if span["type"] == ContentType.InterlineEquation: if span['type'] == ContentType.InterlineEquation:
# 最后一行是行间公式 # 最后一行是行间公式
if j + 1 >= len(text_inline_lines): if j + 1 >= len(text_inline_lines):
span["type"] = ContentType.InlineEquation span['type'] = ContentType.InlineEquation
span["bbox"][1] = y0 span['bbox'][1] = y0
span["bbox"][3] = y1 span['bbox'][3] = y1
else: else:
# 行间公式旁边有多行文字或者行间公式比文字高3倍则不转换 # 行间公式旁边有多行文字或者行间公式比文字高3倍则不转换
y0_next, y1_next = text_inline_lines[j + 1][1] y0_next, y1_next = text_inline_lines[j + 1][1]
if not __is_overlaps_y_exceeds_threshold(span['bbox'], (0, y0_next, 0, y1_next)) and 3 * ( if (
y1 - y0) > span_y - span_y0: not __is_overlaps_y_exceeds_threshold(
span["type"] = ContentType.InlineEquation span['bbox'], (0, y0_next, 0, y1_next)
span["bbox"][1] = y0 )
span["bbox"][3] = y1 and 3 * (y1 - y0) > span_y - span_y0
):
span['type'] = ContentType.InlineEquation
span['bbox'][1] = y0
span['bbox'][3] = y1
break break
elif span_y < y0 or span_y0 < y0 < span_y and not __is_overlaps_y_exceeds_threshold(span['bbox'], elif (
(0, y0, 0, y1)): span_y < y0
or span_y0 < y0 < span_y
and not __is_overlaps_y_exceeds_threshold(span['bbox'], (0, y0, 0, y1))
):
break break
else: else:
j += 1 j += 1
...@@ -232,15 +295,15 @@ def get_qa_need_list(blocks): ...@@ -232,15 +295,15 @@ def get_qa_need_list(blocks):
inline_equations = [] inline_equations = []
for block in blocks: for block in blocks:
for line in block["lines"]: for line in block['lines']:
for span in line["spans"]: for span in line['spans']:
if span["type"] == ContentType.Image: if span['type'] == ContentType.Image:
images.append(span) images.append(span)
elif span["type"] == ContentType.Table: elif span['type'] == ContentType.Table:
tables.append(span) tables.append(span)
elif span["type"] == ContentType.InlineEquation: elif span['type'] == ContentType.InlineEquation:
inline_equations.append(span) inline_equations.append(span)
elif span["type"] == ContentType.InterlineEquation: elif span['type'] == ContentType.InterlineEquation:
interline_equations.append(span) interline_equations.append(span)
else: else:
continue continue
...@@ -254,10 +317,10 @@ def get_qa_need_list_v2(blocks): ...@@ -254,10 +317,10 @@ def get_qa_need_list_v2(blocks):
interline_equations = [] interline_equations = []
for block in blocks: for block in blocks:
if block["type"] == BlockType.Image: if block['type'] == BlockType.Image:
images.append(block) images.append(block)
elif block["type"] == BlockType.Table: elif block['type'] == BlockType.Table:
tables.append(block) tables.append(block)
elif block["type"] == BlockType.InterlineEquation: elif block['type'] == BlockType.InterlineEquation:
interline_equations.append(block) interline_equations.append(block)
return images, tables, interline_equations return images, tables, interline_equations
from magic_pdf.libs.commons import fitz from magic_pdf.config.drop_reason import DropReason
from magic_pdf.libs.boxbase import _is_in, _is_in_or_part_overlap from magic_pdf.libs.boxbase import _is_in, _is_in_or_part_overlap
from magic_pdf.libs.drop_reason import DropReason from magic_pdf.libs.commons import fitz
def __area(box): def __area(box):
return (box[2] - box[0]) * (box[3] - box[1]) return (box[2] - box[0]) * (box[3] - box[1])
def __is_contain_color_background_rect(page:fitz.Page, text_blocks, image_bboxes) -> bool:
""" def __is_contain_color_background_rect(
检查page是包含有颜色背景的矩形 page: fitz.Page, text_blocks, image_bboxes
""" ) -> bool:
"""检查page是包含有颜色背景的矩形."""
color_bg_rect = [] color_bg_rect = []
p_width, p_height = page.rect.width, page.rect.height p_width, p_height = page.rect.width, page.rect.height
# 先找到最大的带背景矩形 # 先找到最大的带背景矩形
blocks = page.get_cdrawings() blocks = page.get_cdrawings()
for block in blocks: for block in blocks:
if 'fill' in block and block['fill']: # 过滤掉透明的
if 'fill' in block and block['fill']: # 过滤掉透明的
fill = list(block['fill']) fill = list(block['fill'])
fill[0], fill[1], fill[2] = int(fill[0]), int(fill[1]), int(fill[2]) fill[0], fill[1], fill[2] = int(fill[0]), int(fill[1]), int(fill[2])
if fill==(1.0,1.0,1.0): if fill == (1.0, 1.0, 1.0):
continue continue
rect = block['rect'] rect = block['rect']
# 过滤掉特别小的矩形 # 过滤掉特别小的矩形
if __area(rect) < 10*10: if __area(rect) < 10 * 10:
continue continue
# 为了防止是svg图片上的色块,这里过滤掉这类 # 为了防止是svg图片上的色块,这里过滤掉这类
if any([_is_in_or_part_overlap(rect, img_bbox) for img_bbox in image_bboxes]): if any(
[_is_in_or_part_overlap(rect, img_bbox) for img_bbox in image_bboxes]
):
continue continue
color_bg_rect.append(rect) color_bg_rect.append(rect)
# 找到最大的背景矩形 # 找到最大的背景矩形
if len(color_bg_rect) > 0: if len(color_bg_rect) > 0:
max_rect = max(color_bg_rect, key=lambda x:__area(x)) max_rect = max(color_bg_rect, key=lambda x: __area(x))
max_rect_int = (int(max_rect[0]), int(max_rect[1]), int(max_rect[2]), int(max_rect[3])) max_rect_int = (
int(max_rect[0]),
int(max_rect[1]),
int(max_rect[2]),
int(max_rect[3]),
)
# 判断最大的背景矩形是否包含超过3行文字,或者50个字 TODO # 判断最大的背景矩形是否包含超过3行文字,或者50个字 TODO
if max_rect[2]-max_rect[0] > 0.2*p_width and max_rect[3]-max_rect[1] > 0.1*p_height:#宽度符合 if (
#看是否有文本块落入到这个矩形中 max_rect[2] - max_rect[0] > 0.2 * p_width
and max_rect[3] - max_rect[1] > 0.1 * p_height
): # 宽度符合
# 看是否有文本块落入到这个矩形中
for text_block in text_blocks: for text_block in text_blocks:
box = text_block['bbox'] box = text_block['bbox']
box_int = (int(box[0]), int(box[1]), int(box[2]), int(box[3])) box_int = (int(box[0]), int(box[1]), int(box[2]), int(box[3]))
if _is_in(box_int, max_rect_int): if _is_in(box_int, max_rect_int):
return True return True
return False return False
def __is_table_overlap_text_block(text_blocks, table_bbox): def __is_table_overlap_text_block(text_blocks, table_bbox):
""" """检查table_bbox是否覆盖了text_blocks里的文本块 TODO."""
检查table_bbox是否覆盖了text_blocks里的文本块
TODO
"""
for text_block in text_blocks: for text_block in text_blocks:
box = text_block['bbox'] box = text_block['bbox']
if _is_in_or_part_overlap(table_bbox, box): if _is_in_or_part_overlap(table_bbox, box):
...@@ -60,15 +67,12 @@ def __is_table_overlap_text_block(text_blocks, table_bbox): ...@@ -60,15 +67,12 @@ def __is_table_overlap_text_block(text_blocks, table_bbox):
return False return False
def pdf_filter(page:fitz.Page, text_blocks, table_bboxes, image_bboxes) -> tuple: def pdf_filter(page: fitz.Page, text_blocks, table_bboxes, image_bboxes) -> tuple:
""" """return:(True|False, err_msg) True, 如果pdf符合要求 False, 如果pdf不符合要求."""
return:(True|False, err_msg)
True, 如果pdf符合要求
False, 如果pdf不符合要求
"""
if __is_contain_color_background_rect(page, text_blocks, image_bboxes): if __is_contain_color_background_rect(page, text_blocks, image_bboxes):
return False, {"_need_drop": True, "_drop_reason": DropReason.COLOR_BACKGROUND_TEXT_BOX} return False, {
'_need_drop': True,
'_drop_reason': DropReason.COLOR_BACKGROUND_TEXT_BOX,
}
return True, None
return True, None
\ No newline at end of file
from magic_pdf.libs.boxbase import _is_in_or_part_overlap, _is_in, _is_part_overlap from magic_pdf.config.drop_reason import DropReason
from magic_pdf.libs.drop_reason import DropReason from magic_pdf.libs.boxbase import _is_in, _is_part_overlap
def _remove_overlap_between_bbox(bbox1, bbox2): def _remove_overlap_between_bbox(bbox1, bbox2):
if _is_part_overlap(bbox1, bbox2): if _is_part_overlap(bbox1, bbox2):
ix0, iy0, ix1, iy1 = bbox1 ix0, iy0, ix1, iy1 = bbox1
x0, y0, x1, y1 = bbox2 x0, y0, x1, y1 = bbox2
...@@ -22,10 +23,10 @@ def _remove_overlap_between_bbox(bbox1, bbox2): ...@@ -22,10 +23,10 @@ def _remove_overlap_between_bbox(bbox1, bbox2):
if y1 >= iy1: if y1 >= iy1:
mid = (y0 + iy1) // 2 mid = (y0 + iy1) // 2
y0 = max(mid + 0.25, y0) y0 = max(mid + 0.25, y0)
iy1 = min(iy1, mid-0.25) iy1 = min(iy1, mid - 0.25)
else: else:
mid = (iy0 + y1) // 2 mid = (iy0 + y1) // 2
y1 = min(y1, mid-0.25) y1 = min(y1, mid - 0.25)
iy0 = max(mid + 0.25, iy0) iy0 = max(mid + 0.25, iy0)
if ix1 > ix0 and iy1 > iy0 and y1 > y0 and x1 > x0: if ix1 > ix0 and iy1 > iy0 and y1 > y0 and x1 > x0:
...@@ -34,8 +35,8 @@ def _remove_overlap_between_bbox(bbox1, bbox2): ...@@ -34,8 +35,8 @@ def _remove_overlap_between_bbox(bbox1, bbox2):
return bbox1, bbox2, None return bbox1, bbox2, None
else: else:
return bbox1, bbox2, DropReason.NEGATIVE_BBOX_AREA return bbox1, bbox2, DropReason.NEGATIVE_BBOX_AREA
else: else:
return bbox1, bbox2, None return bbox1, bbox2, None
def _remove_overlap_between_bboxes(arr): def _remove_overlap_between_bboxes(arr):
...@@ -47,7 +48,7 @@ def _remove_overlap_between_bboxes(arr): ...@@ -47,7 +48,7 @@ def _remove_overlap_between_bboxes(arr):
for j in range(N): for j in range(N):
if i == j: if i == j:
continue continue
if _is_in(arr[i]["bbox"], arr[j]["bbox"]): if _is_in(arr[i]['bbox'], arr[j]['bbox']):
keeps[i] = False keeps[i] = False
for idx, v in enumerate(arr): for idx, v in enumerate(arr):
...@@ -56,13 +57,15 @@ def _remove_overlap_between_bboxes(arr): ...@@ -56,13 +57,15 @@ def _remove_overlap_between_bboxes(arr):
for i in range(N): for i in range(N):
if res[i] is None: if res[i] is None:
continue continue
bbox1, bbox2, drop_reason = _remove_overlap_between_bbox(v["bbox"], res[i]["bbox"]) bbox1, bbox2, drop_reason = _remove_overlap_between_bbox(
v['bbox'], res[i]['bbox']
)
if drop_reason is None: if drop_reason is None:
v["bbox"] = bbox1 v['bbox'] = bbox1
res[i]["bbox"] = bbox2 res[i]['bbox'] = bbox2
else: else:
if v["score"] > res[i]["score"]: if v['score'] > res[i]['score']:
keeps[i] = False keeps[i] = False
res[i] = None res[i] = None
else: else:
...@@ -74,25 +77,24 @@ def _remove_overlap_between_bboxes(arr): ...@@ -74,25 +77,24 @@ def _remove_overlap_between_bboxes(arr):
def remove_overlap_between_bbox_for_span(spans): def remove_overlap_between_bbox_for_span(spans):
arr = [{"bbox": span["bbox"], "score": span.get("score", 0.1)} for span in spans ] arr = [{'bbox': span['bbox'], 'score': span.get('score', 0.1)} for span in spans]
res, drop_reasons = _remove_overlap_between_bboxes(arr) res, drop_reasons = _remove_overlap_between_bboxes(arr)
ret = [] ret = []
for i in range(len(res)): for i in range(len(res)):
if res[i] is None: if res[i] is None:
continue continue
spans[i]["bbox"] = res[i]["bbox"] spans[i]['bbox'] = res[i]['bbox']
ret.append(spans[i]) ret.append(spans[i])
return ret, drop_reasons return ret, drop_reasons
def remove_overlap_between_bbox_for_block(all_bboxes): def remove_overlap_between_bbox_for_block(all_bboxes):
arr = [{"bbox": bbox[:4], "score": bbox[-1]} for bbox in all_bboxes ] arr = [{'bbox': bbox[:4], 'score': bbox[-1]} for bbox in all_bboxes]
res, drop_reasons = _remove_overlap_between_bboxes(arr) res, drop_reasons = _remove_overlap_between_bboxes(arr)
ret = [] ret = []
for i in range(len(res)): for i in range(len(res)):
if res[i] is None: if res[i] is None:
continue continue
all_bboxes[i][:4] = res[i]["bbox"] all_bboxes[i][:4] = res[i]['bbox']
ret.append(all_bboxes[i]) ret.append(all_bboxes[i])
return ret, drop_reasons return ret, drop_reasons
from magic_pdf.libs.boxbase import _is_in, _is_in_or_part_overlap, calculate_overlap_area_2_minbox_area_ratio
from loguru import logger from loguru import logger
from magic_pdf.libs.drop_tag import COLOR_BG_HEADER_TXT_BLOCK from magic_pdf.config.drop_tag import COLOR_BG_HEADER_TXT_BLOCK
from magic_pdf.libs.boxbase import (_is_in, _is_in_or_part_overlap,
calculate_overlap_area_2_minbox_area_ratio)
def __area(box): def __area(box):
...@@ -9,8 +10,7 @@ def __area(box): ...@@ -9,8 +10,7 @@ def __area(box):
def rectangle_position_determination(rect, p_width): def rectangle_position_determination(rect, p_width):
""" """判断矩形是否在页面中轴线附近。
判断矩形是否在页面中轴线附近。
Args: Args:
rect (list): 矩形坐标,格式为[x1, y1, x2, y2]。 rect (list): 矩形坐标,格式为[x1, y1, x2, y2]。
...@@ -34,9 +34,10 @@ def rectangle_position_determination(rect, p_width): ...@@ -34,9 +34,10 @@ def rectangle_position_determination(rect, p_width):
else: else:
return False return False
def remove_colored_strip_textblock(remain_text_blocks, page): def remove_colored_strip_textblock(remain_text_blocks, page):
""" """根据页面中特定颜色和大小过滤文本块,将符合条件的文本块从remain_text_blocks中移除,并返回移除的文本块列表colored_str
根据页面中特定颜色和大小过滤文本块,将符合条件的文本块从remain_text_blocks中移除,并返回移除的文本块列表colored_strip_textblock。 ip_textblock。
Args: Args:
remain_text_blocks (list): 剩余文本块列表。 remain_text_blocks (list): 剩余文本块列表。
...@@ -51,22 +52,44 @@ def remove_colored_strip_textblock(remain_text_blocks, page): ...@@ -51,22 +52,44 @@ def remove_colored_strip_textblock(remain_text_blocks, page):
blocks = page.get_cdrawings() blocks = page.get_cdrawings()
colored_strip_bg_rect = [] colored_strip_bg_rect = []
for block in blocks: for block in blocks:
is_filled = 'fill' in block and block['fill'] and block['fill'] != (1.0, 1.0, 1.0) # 过滤掉透明的 is_filled = (
'fill' in block and block['fill'] and block['fill'] != (1.0, 1.0, 1.0)
) # 过滤掉透明的
rect = block['rect'] rect = block['rect']
area_is_large_enough = __area(rect) > 100 # 过滤掉特别小的矩形 area_is_large_enough = __area(rect) > 100 # 过滤掉特别小的矩形
rectangle_position_determination_result = rectangle_position_determination(rect, p_width) rectangle_position_determination_result = rectangle_position_determination(
in_upper_half_page = rect[3] < p_height * 0.3 # 找到位于页面上半部分的矩形,下边界小于页面高度的30% rect, p_width
aspect_ratio_exceeds_4 = (rect[2] - rect[0]) > (rect[3] - rect[1]) * 4 # 找到长宽比超过4的矩形 )
in_upper_half_page = (
rect[3] < p_height * 0.3
) # 找到位于页面上半部分的矩形,下边界小于页面高度的30%
aspect_ratio_exceeds_4 = (rect[2] - rect[0]) > (
rect[3] - rect[1]
) * 4 # 找到长宽比超过4的矩形
if is_filled and area_is_large_enough and rectangle_position_determination_result and in_upper_half_page and aspect_ratio_exceeds_4: if (
is_filled
and area_is_large_enough
and rectangle_position_determination_result
and in_upper_half_page
and aspect_ratio_exceeds_4
):
colored_strip_bg_rect.append(rect) colored_strip_bg_rect.append(rect)
if len(colored_strip_bg_rect) > 0: if len(colored_strip_bg_rect) > 0:
for colored_strip_block_bbox in colored_strip_bg_rect: for colored_strip_block_bbox in colored_strip_bg_rect:
for text_block in remain_text_blocks: for text_block in remain_text_blocks:
text_bbox = text_block['bbox'] text_bbox = text_block['bbox']
if _is_in(text_bbox, colored_strip_block_bbox) or (_is_in_or_part_overlap(text_bbox, colored_strip_block_bbox) and calculate_overlap_area_2_minbox_area_ratio(text_bbox, colored_strip_block_bbox) > 0.6): if _is_in(text_bbox, colored_strip_block_bbox) or (
logger.info(f'remove_colored_strip_textblock: {text_bbox}, {colored_strip_block_bbox}') _is_in_or_part_overlap(text_bbox, colored_strip_block_bbox)
and calculate_overlap_area_2_minbox_area_ratio(
text_bbox, colored_strip_block_bbox
)
> 0.6
):
logger.info(
f'remove_colored_strip_textblock: {text_bbox}, {colored_strip_block_bbox}'
)
text_block['tag'] = COLOR_BG_HEADER_TXT_BLOCK text_block['tag'] = COLOR_BG_HEADER_TXT_BLOCK
colored_strip_textblocks.append(text_block) colored_strip_textblocks.append(text_block)
...@@ -76,4 +99,3 @@ def remove_colored_strip_textblock(remain_text_blocks, page): ...@@ -76,4 +99,3 @@ def remove_colored_strip_textblock(remain_text_blocks, page):
remain_text_blocks.remove(colored_strip_textblock) remain_text_blocks.remove(colored_strip_textblock)
return remain_text_blocks, colored_strip_textblocks return remain_text_blocks, colored_strip_textblocks
import re import re
from magic_pdf.config.drop_tag import CONTENT_IN_FOOT_OR_HEADER, PAGE_NO
from magic_pdf.libs.boxbase import _is_in_or_part_overlap from magic_pdf.libs.boxbase import _is_in_or_part_overlap
from magic_pdf.libs.drop_tag import CONTENT_IN_FOOT_OR_HEADER, PAGE_NO
def remove_headder_footer_one_page(text_raw_blocks, image_bboxes, table_bboxes, header_bboxs, footer_bboxs, def remove_headder_footer_one_page(text_raw_blocks, image_bboxes, table_bboxes, header_bboxs, footer_bboxs,
page_no_bboxs, page_w, page_h): page_no_bboxs, page_w, page_h):
""" """删除页眉页脚,页码 从line级别进行删除,删除之后观察这个text-block是否是空的,如果是空的,则移动到remove_list中."""
删除页眉页脚,页码
从line级别进行删除,删除之后观察这个text-block是否是空的,如果是空的,则移动到remove_list中
"""
header = [] header = []
footer = [] footer = []
if len(header) == 0: if len(header) == 0:
......
import math import math
import re
from magic_pdf.config.drop_tag import (EMPTY_SIDE_BLOCK, ROTATE_TEXT,
VERTICAL_TEXT)
from magic_pdf.libs.boxbase import is_vbox_on_side from magic_pdf.libs.boxbase import is_vbox_on_side
from magic_pdf.libs.drop_tag import EMPTY_SIDE_BLOCK, ROTATE_TEXT, VERTICAL_TEXT
def detect_non_horizontal_texts(result_dict): def detect_non_horizontal_texts(result_dict):
""" """This function detects watermarks and vertical margin notes in the
This function detects watermarks and vertical margin notes in the document. document.
Watermarks are identified by finding blocks with the same coordinates and frequently occurring identical texts across multiple pages. Watermarks are identified by finding blocks with the same coordinates and frequently occurring identical texts across multiple pages.
If these conditions are met, the blocks are highly likely to be watermarks, as opposed to headers or footers, which can change from page to page. If these conditions are met, the blocks are highly likely to be watermarks, as opposed to headers or footers, which can change from page to page.
If the direction of these blocks is not horizontal, they are definitely considered to be watermarks. If the direction of these blocks is not horizontal, they are definitely considered to be watermarks.
Vertical margin notes are identified by finding blocks with the same coordinates and frequently occurring identical texts across multiple pages. Vertical margin notes are identified by finding blocks with the same coordinates and frequently occurring identical texts across multiple pages.
If these conditions are met, the blocks are highly likely to be vertical margin notes, which typically appear on the left and right sides of the page. If these conditions are met, the blocks are highly likely to be vertical margin notes, which typically appear on the left and right sides of the page. # noqa: E501
If the direction of these blocks is vertical, they are definitely considered to be vertical margin notes. If the direction of these blocks is vertical, they are definitely considered to be vertical margin notes.
...@@ -32,13 +34,16 @@ def detect_non_horizontal_texts(result_dict): ...@@ -32,13 +34,16 @@ def detect_non_horizontal_texts(result_dict):
potential_margin_notes = {} potential_margin_notes = {}
for page_id, page_content in result_dict.items(): for page_id, page_content in result_dict.items():
if page_id.startswith("page_"): if page_id.startswith('page_'):
for block_id, block_data in page_content.items(): for block_id, block_data in page_content.items():
if block_id.startswith("block_"): if block_id.startswith('block_'):
if "dir" in block_data: if 'dir' in block_data:
coordinates_text = (block_data["bbox"], block_data["text"]) # Tuple of coordinates and text coordinates_text = (
block_data['bbox'],
angle = math.atan2(block_data["dir"][1], block_data["dir"][0]) block_data['text'],
) # Tuple of coordinates and text
angle = math.atan2(block_data['dir'][1], block_data['dir'][0])
angle = abs(math.degrees(angle)) angle = abs(math.degrees(angle))
if angle > 5 and angle < 85: # Check if direction is watermarks if angle > 5 and angle < 85: # Check if direction is watermarks
...@@ -49,32 +54,40 @@ def detect_non_horizontal_texts(result_dict): ...@@ -49,32 +54,40 @@ def detect_non_horizontal_texts(result_dict):
if angle > 85 and angle < 105: # Check if direction is vertical if angle > 85 and angle < 105: # Check if direction is vertical
if coordinates_text in potential_margin_notes: if coordinates_text in potential_margin_notes:
potential_margin_notes[coordinates_text] += 1 # Increment count potential_margin_notes[coordinates_text] += (
1 # Increment count
)
else: else:
potential_margin_notes[coordinates_text] = 1 # Initialize count potential_margin_notes[coordinates_text] = (
1 # Initialize count
)
# Identify watermarks by finding entries with counts higher than a threshold (e.g., appearing on more than half of the pages) # Identify watermarks by finding entries with counts higher than a threshold (e.g., appearing on more than half of the pages)
watermark_threshold = len(result_dict) // 2 watermark_threshold = len(result_dict) // 2
watermarks = {k: v for k, v in potential_watermarks.items() if v > watermark_threshold} watermarks = {
k: v for k, v in potential_watermarks.items() if v > watermark_threshold
}
# Identify margin notes by finding entries with counts higher than a threshold (e.g., appearing on more than half of the pages) # Identify margin notes by finding entries with counts higher than a threshold (e.g., appearing on more than half of the pages)
margin_note_threshold = len(result_dict) // 2 margin_note_threshold = len(result_dict) // 2
margin_notes = {k: v for k, v in potential_margin_notes.items() if v > margin_note_threshold} margin_notes = {
k: v for k, v in potential_margin_notes.items() if v > margin_note_threshold
}
# Add watermark information to the result dictionary # Add watermark information to the result dictionary
for page_id, blocks in result_dict.items(): for page_id, blocks in result_dict.items():
if page_id.startswith("page_"): if page_id.startswith('page_'):
for block_id, block_data in blocks.items(): for block_id, block_data in blocks.items():
coordinates_text = (block_data["bbox"], block_data["text"]) coordinates_text = (block_data['bbox'], block_data['text'])
if coordinates_text in watermarks: if coordinates_text in watermarks:
block_data["is_watermark"] = 1 block_data['is_watermark'] = 1
else: else:
block_data["is_watermark"] = 0 block_data['is_watermark'] = 0
if coordinates_text in margin_notes: if coordinates_text in margin_notes:
block_data["is_vertical_margin_note"] = 1 block_data['is_vertical_margin_note'] = 1
else: else:
block_data["is_vertical_margin_note"] = 0 block_data['is_vertical_margin_note'] = 0
return result_dict return result_dict
...@@ -83,21 +96,21 @@ def detect_non_horizontal_texts(result_dict): ...@@ -83,21 +96,21 @@ def detect_non_horizontal_texts(result_dict):
1. 当一个block里全部文字都不是dir=(1,0),这个block整体去掉 1. 当一个block里全部文字都不是dir=(1,0),这个block整体去掉
2. 当一个block里全部文字都是dir=(1,0),但是每行只有一个字,这个block整体去掉。这个block必须出现在页面的四周,否则不去掉 2. 当一个block里全部文字都是dir=(1,0),但是每行只有一个字,这个block整体去掉。这个block必须出现在页面的四周,否则不去掉
""" """
import re
def __is_a_word(sentence): def __is_a_word(sentence):
# 如果输入是中文并且长度为1,则返回True # 如果输入是中文并且长度为1,则返回True
if re.fullmatch(r'[\u4e00-\u9fa5]', sentence): if re.fullmatch(r'[\u4e00-\u9fa5]', sentence):
return True return True
# 判断是否为单个英文单词或字符(包括ASCII标点) # 判断是否为单个英文单词或字符(包括ASCII标点)
elif re.fullmatch(r'[a-zA-Z0-9]+', sentence) and len(sentence) <=2: elif re.fullmatch(r'[a-zA-Z0-9]+', sentence) and len(sentence) <= 2:
return True return True
else: else:
return False return False
def __get_text_color(num): def __get_text_color(num):
"""获取字体的颜色RGB值""" """获取字体的颜色RGB值."""
blue = num & 255 blue = num & 255
green = (num >> 8) & 255 green = (num >> 8) & 255
red = (num >> 16) & 255 red = (num >> 16) & 255
...@@ -105,84 +118,119 @@ def __get_text_color(num): ...@@ -105,84 +118,119 @@ def __get_text_color(num):
def __is_empty_side_box(text_block): def __is_empty_side_box(text_block):
""" """是否是边缘上的空白没有任何内容的block."""
是否是边缘上的空白没有任何内容的block
"""
for line in text_block['lines']: for line in text_block['lines']:
for span in line['spans']: for span in line['spans']:
font_color = span['color'] font_color = span['color']
r,g,b = __get_text_color(font_color) r, g, b = __get_text_color(font_color)
if len(span['text'].strip())>0 and (r,g,b)!=(255,255,255): if len(span['text'].strip()) > 0 and (r, g, b) != (255, 255, 255):
return False return False
return True return True
def remove_rotate_side_textblock(pymu_text_block, page_width, page_height): def remove_rotate_side_textblock(pymu_text_block, page_width, page_height):
""" """返回删除了垂直,水印,旋转的textblock 删除的内容打上tag返回."""
返回删除了垂直,水印,旋转的textblock
删除的内容打上tag返回
"""
removed_text_block = [] removed_text_block = []
for i, block in enumerate(pymu_text_block): # 格式参考test/assets/papre/pymu_textblocks.json for i, block in enumerate(
pymu_text_block
): # 格式参考test/assets/papre/pymu_textblocks.json
lines = block['lines'] lines = block['lines']
block_bbox = block['bbox'] block_bbox = block['bbox']
if not is_vbox_on_side(block_bbox, page_width, page_height, 0.2): # 保证这些box必须在页面的两边 if not is_vbox_on_side(
continue block_bbox, page_width, page_height, 0.2
): # 保证这些box必须在页面的两边
if all([__is_a_word(line['spans'][0]["text"]) for line in lines if len(line['spans'])>0]) and len(lines)>1 and all([len(line['spans'])==1 for line in lines]): continue
is_box_valign = (len(set([int(line['spans'][0]['bbox'][0] ) for line in lines if len(line['spans'])>0]))==1) and (len([int(line['spans'][0]['bbox'][0] ) for line in lines if len(line['spans'])>0])>1) # 测试bbox在垂直方向是不是x0都相等,也就是在垂直方向排列.同时必须大于等于2个字
if (
all(
[
__is_a_word(line['spans'][0]['text'])
for line in lines
if len(line['spans']) > 0
]
)
and len(lines) > 1
and all([len(line['spans']) == 1 for line in lines])
):
is_box_valign = (
(
len(
set(
[
int(line['spans'][0]['bbox'][0])
for line in lines
if len(line['spans']) > 0
]
)
)
== 1
)
and (
len(
[
int(line['spans'][0]['bbox'][0])
for line in lines
if len(line['spans']) > 0
]
)
> 1
)
) # 测试bbox在垂直方向是不是x0都相等,也就是在垂直方向排列.同时必须大于等于2个字
if is_box_valign: if is_box_valign:
block['tag'] = VERTICAL_TEXT block['tag'] = VERTICAL_TEXT
removed_text_block.append(block) removed_text_block.append(block)
continue continue
for line in lines: for line in lines:
if line['dir']!=(1,0): if line['dir'] != (1, 0):
block['tag'] = ROTATE_TEXT block['tag'] = ROTATE_TEXT
removed_text_block.append(block) # 只要有一个line不是dir=(1,0),就把整个block都删掉 removed_text_block.append(
block
) # 只要有一个line不是dir=(1,0),就把整个block都删掉
break break
for block in removed_text_block: for block in removed_text_block:
pymu_text_block.remove(block) pymu_text_block.remove(block)
return pymu_text_block, removed_text_block return pymu_text_block, removed_text_block
def get_side_boundry(rotate_bbox, page_width, page_height): def get_side_boundry(rotate_bbox, page_width, page_height):
""" """根据rotate_bbox,返回页面的左右正文边界."""
根据rotate_bbox,返回页面的左右正文边界
"""
left_x = 0 left_x = 0
right_x = page_width right_x = page_width
for x in rotate_bbox: for x in rotate_bbox:
box = x['bbox'] box = x['bbox']
if box[2]<page_width/2: if box[2] < page_width / 2:
left_x = max(left_x, box[2]) left_x = max(left_x, box[2])
else: else:
right_x = min(right_x, box[0]) right_x = min(right_x, box[0])
return left_x+1, right_x-1 return left_x + 1, right_x - 1
def remove_side_blank_block(pymu_text_block, page_width, page_height): def remove_side_blank_block(pymu_text_block, page_width, page_height):
""" """删除页面两侧的空白block."""
删除页面两侧的空白block
"""
removed_text_block = [] removed_text_block = []
for i, block in enumerate(pymu_text_block): # 格式参考test/assets/papre/pymu_textblocks.json for i, block in enumerate(
pymu_text_block
): # 格式参考test/assets/papre/pymu_textblocks.json
block_bbox = block['bbox'] block_bbox = block['bbox']
if not is_vbox_on_side(block_bbox, page_width, page_height, 0.2): # 保证这些box必须在页面的两边 if not is_vbox_on_side(
continue block_bbox, page_width, page_height, 0.2
): # 保证这些box必须在页面的两边
continue
if __is_empty_side_box(block): if __is_empty_side_box(block):
block['tag'] = EMPTY_SIDE_BLOCK block['tag'] = EMPTY_SIDE_BLOCK
removed_text_block.append(block) removed_text_block.append(block)
continue continue
for block in removed_text_block: for block in removed_text_block:
pymu_text_block.remove(block) pymu_text_block.remove(block)
return pymu_text_block, removed_text_block return pymu_text_block, removed_text_block
\ No newline at end of file
...@@ -4,8 +4,9 @@ ...@@ -4,8 +4,9 @@
2. 然后去掉出现在文字blcok上的图片bbox 2. 然后去掉出现在文字blcok上的图片bbox
""" """
from magic_pdf.libs.boxbase import _is_in, _is_in_or_part_overlap, _is_left_overlap from magic_pdf.config.drop_tag import ON_IMAGE_TEXT, ON_TABLE_TEXT
from magic_pdf.libs.drop_tag import ON_IMAGE_TEXT, ON_TABLE_TEXT from magic_pdf.libs.boxbase import (_is_in, _is_in_or_part_overlap,
_is_left_overlap)
def resolve_bbox_overlap_conflict(images: list, tables: list, interline_equations: list, inline_equations: list, def resolve_bbox_overlap_conflict(images: list, tables: list, interline_equations: list, inline_equations: list,
...@@ -26,14 +27,14 @@ def resolve_bbox_overlap_conflict(images: list, tables: list, interline_equation ...@@ -26,14 +27,14 @@ def resolve_bbox_overlap_conflict(images: list, tables: list, interline_equation
# 去掉位于图片上的文字block # 去掉位于图片上的文字block
for image_box in images: for image_box in images:
for text_block in text_raw_blocks: for text_block in text_raw_blocks:
text_bbox = text_block["bbox"] text_bbox = text_block['bbox']
if _is_in(text_bbox, image_box): if _is_in(text_bbox, image_box):
text_block['tag'] = ON_IMAGE_TEXT text_block['tag'] = ON_IMAGE_TEXT
text_block_removed.append(text_block) text_block_removed.append(text_block)
# 去掉table上的文字block # 去掉table上的文字block
for table_box in tables: for table_box in tables:
for text_block in text_raw_blocks: for text_block in text_raw_blocks:
text_bbox = text_block["bbox"] text_bbox = text_block['bbox']
if _is_in(text_bbox, table_box): if _is_in(text_bbox, table_box):
text_block['tag'] = ON_TABLE_TEXT text_block['tag'] = ON_TABLE_TEXT
text_block_removed.append(text_block) text_block_removed.append(text_block)
...@@ -77,7 +78,7 @@ def resolve_bbox_overlap_conflict(images: list, tables: list, interline_equation ...@@ -77,7 +78,7 @@ def resolve_bbox_overlap_conflict(images: list, tables: list, interline_equation
# 图片和文字重叠,丢掉图片 # 图片和文字重叠,丢掉图片
for image_box in images: for image_box in images:
for text_block in text_raw_blocks: for text_block in text_raw_blocks:
text_bbox = text_block["bbox"] text_bbox = text_block['bbox']
if _is_in_or_part_overlap(image_box, text_bbox): if _is_in_or_part_overlap(image_box, text_bbox):
images_backup.append(image_box) images_backup.append(image_box)
break break
...@@ -122,11 +123,7 @@ def resolve_bbox_overlap_conflict(images: list, tables: list, interline_equation ...@@ -122,11 +123,7 @@ def resolve_bbox_overlap_conflict(images: list, tables: list, interline_equation
def check_text_block_horizontal_overlap(text_blocks: list, header, footer) -> bool: def check_text_block_horizontal_overlap(text_blocks: list, header, footer) -> bool:
""" """检查文本block之间的水平重叠情况,这种情况如果发生,那么这个pdf就不再继续处理了。 因为这种情况大概率发生了公式没有被检测出来。"""
检查文本block之间的水平重叠情况,这种情况如果发生,那么这个pdf就不再继续处理了。
因为这种情况大概率发生了公式没有被检测出来。
"""
if len(text_blocks) == 0: if len(text_blocks) == 0:
return False return False
...@@ -148,7 +145,7 @@ def check_text_block_horizontal_overlap(text_blocks: list, header, footer) -> bo ...@@ -148,7 +145,7 @@ def check_text_block_horizontal_overlap(text_blocks: list, header, footer) -> bo
txt_bboxes = [] txt_bboxes = []
for text_block in text_blocks: for text_block in text_blocks:
bbox = text_block["bbox"] bbox = text_block['bbox']
if bbox[1] >= clip_y0 and bbox[3] <= clip_y1: if bbox[1] >= clip_y0 and bbox[3] <= clip_y1:
txt_bboxes.append(bbox) txt_bboxes.append(bbox)
...@@ -161,11 +158,7 @@ def check_text_block_horizontal_overlap(text_blocks: list, header, footer) -> bo ...@@ -161,11 +158,7 @@ def check_text_block_horizontal_overlap(text_blocks: list, header, footer) -> bo
def check_useful_block_horizontal_overlap(useful_blocks: list) -> bool: def check_useful_block_horizontal_overlap(useful_blocks: list) -> bool:
""" """检查文本block之间的水平重叠情况,这种情况如果发生,那么这个pdf就不再继续处理了。 因为这种情况大概率发生了公式没有被检测出来。"""
检查文本block之间的水平重叠情况,这种情况如果发生,那么这个pdf就不再继续处理了。
因为这种情况大概率发生了公式没有被检测出来。
"""
if len(useful_blocks) == 0: if len(useful_blocks) == 0:
return False return False
...@@ -174,7 +167,7 @@ def check_useful_block_horizontal_overlap(useful_blocks: list) -> bool: ...@@ -174,7 +167,7 @@ def check_useful_block_horizontal_overlap(useful_blocks: list) -> bool:
useful_bboxes = [] useful_bboxes = []
for text_block in useful_blocks: for text_block in useful_blocks:
bbox = text_block["bbox"] bbox = text_block['bbox']
if bbox[1] >= page_min_y and bbox[3] <= page_max_y: if bbox[1] >= page_min_y and bbox[3] <= page_max_y:
useful_bboxes.append(bbox) useful_bboxes.append(bbox)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment