magic-pdf 0.9.2__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- magic_pdf/config/constants.py +53 -0
- magic_pdf/config/drop_reason.py +35 -0
- magic_pdf/config/drop_tag.py +19 -0
- magic_pdf/config/make_content_config.py +11 -0
- magic_pdf/{libs/ModelBlockTypeEnum.py → config/model_block_type.py} +2 -1
- magic_pdf/data/read_api.py +1 -1
- magic_pdf/dict2md/mkcontent.py +226 -185
- magic_pdf/dict2md/ocr_mkcontent.py +12 -12
- magic_pdf/filter/pdf_meta_scan.py +101 -79
- magic_pdf/integrations/rag/utils.py +4 -5
- magic_pdf/libs/config_reader.py +6 -6
- magic_pdf/libs/draw_bbox.py +13 -6
- magic_pdf/libs/pdf_image_tools.py +36 -12
- magic_pdf/libs/version.py +1 -1
- magic_pdf/model/doc_analyze_by_custom_model.py +2 -0
- magic_pdf/model/magic_model.py +13 -13
- magic_pdf/model/pdf_extract_kit.py +142 -351
- magic_pdf/model/sub_modules/layout/doclayout_yolo/DocLayoutYOLO.py +21 -0
- magic_pdf/model/sub_modules/mfd/__init__.py +0 -0
- magic_pdf/model/sub_modules/mfd/yolov8/YOLOv8.py +12 -0
- magic_pdf/model/sub_modules/mfd/yolov8/__init__.py +0 -0
- magic_pdf/model/sub_modules/mfr/__init__.py +0 -0
- magic_pdf/model/sub_modules/mfr/unimernet/Unimernet.py +98 -0
- magic_pdf/model/sub_modules/mfr/unimernet/__init__.py +0 -0
- magic_pdf/model/sub_modules/model_init.py +149 -0
- magic_pdf/model/sub_modules/model_utils.py +51 -0
- magic_pdf/model/sub_modules/ocr/__init__.py +0 -0
- magic_pdf/model/sub_modules/ocr/paddleocr/__init__.py +0 -0
- magic_pdf/model/sub_modules/ocr/paddleocr/ocr_utils.py +285 -0
- magic_pdf/model/sub_modules/ocr/paddleocr/ppocr_273_mod.py +176 -0
- magic_pdf/model/sub_modules/ocr/paddleocr/ppocr_291_mod.py +213 -0
- magic_pdf/model/sub_modules/reading_oreder/__init__.py +0 -0
- magic_pdf/model/sub_modules/reading_oreder/layoutreader/__init__.py +0 -0
- magic_pdf/model/sub_modules/reading_oreder/layoutreader/xycut.py +242 -0
- magic_pdf/model/sub_modules/table/__init__.py +0 -0
- magic_pdf/model/sub_modules/table/rapidtable/__init__.py +0 -0
- magic_pdf/model/sub_modules/table/rapidtable/rapid_table.py +16 -0
- magic_pdf/model/sub_modules/table/structeqtable/__init__.py +0 -0
- magic_pdf/model/{pek_sub_modules/structeqtable/StructTableModel.py → sub_modules/table/structeqtable/struct_eqtable.py} +3 -11
- magic_pdf/model/sub_modules/table/table_utils.py +11 -0
- magic_pdf/model/sub_modules/table/tablemaster/__init__.py +0 -0
- magic_pdf/model/{ppTableModel.py → sub_modules/table/tablemaster/tablemaster_paddle.py} +31 -29
- magic_pdf/para/para_split.py +411 -248
- magic_pdf/para/para_split_v2.py +352 -182
- magic_pdf/para/para_split_v3.py +121 -66
- magic_pdf/pdf_parse_by_ocr.py +2 -0
- magic_pdf/pdf_parse_by_txt.py +2 -0
- magic_pdf/pdf_parse_union_core.py +174 -100
- magic_pdf/pdf_parse_union_core_v2.py +253 -50
- magic_pdf/pipe/AbsPipe.py +28 -44
- magic_pdf/pipe/OCRPipe.py +5 -5
- magic_pdf/pipe/TXTPipe.py +5 -6
- magic_pdf/pipe/UNIPipe.py +24 -25
- magic_pdf/post_proc/pdf_post_filter.py +7 -14
- magic_pdf/pre_proc/cut_image.py +9 -11
- magic_pdf/pre_proc/equations_replace.py +203 -212
- magic_pdf/pre_proc/ocr_detect_all_bboxes.py +235 -49
- magic_pdf/pre_proc/ocr_dict_merge.py +5 -5
- magic_pdf/pre_proc/ocr_span_list_modify.py +122 -63
- magic_pdf/pre_proc/pdf_pre_filter.py +37 -33
- magic_pdf/pre_proc/remove_bbox_overlap.py +20 -18
- magic_pdf/pre_proc/remove_colored_strip_bbox.py +36 -14
- magic_pdf/pre_proc/remove_footer_header.py +2 -5
- magic_pdf/pre_proc/remove_rotate_bbox.py +111 -63
- magic_pdf/pre_proc/resolve_bbox_conflict.py +10 -17
- magic_pdf/resources/model_config/model_configs.yaml +2 -1
- magic_pdf/spark/spark_api.py +15 -17
- magic_pdf/tools/cli.py +3 -4
- magic_pdf/tools/cli_dev.py +6 -9
- magic_pdf/tools/common.py +70 -36
- magic_pdf/user_api.py +29 -38
- {magic_pdf-0.9.2.dist-info → magic_pdf-0.10.0.dist-info}/METADATA +18 -13
- magic_pdf-0.10.0.dist-info/RECORD +198 -0
- {magic_pdf-0.9.2.dist-info → magic_pdf-0.10.0.dist-info}/WHEEL +1 -1
- magic_pdf/libs/Constants.py +0 -53
- magic_pdf/libs/MakeContentConfig.py +0 -11
- magic_pdf/libs/drop_reason.py +0 -27
- magic_pdf/libs/drop_tag.py +0 -19
- magic_pdf/model/pek_sub_modules/post_process.py +0 -36
- magic_pdf/model/pek_sub_modules/self_modify.py +0 -388
- magic_pdf/para/para_pipeline.py +0 -297
- magic_pdf-0.9.2.dist-info/RECORD +0 -178
- /magic_pdf/{libs → config}/ocr_content_type.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules}/__init__.py +0 -0
- /magic_pdf/model/{pek_sub_modules/layoutlmv3 → sub_modules/layout}/__init__.py +0 -0
- /magic_pdf/model/{pek_sub_modules/structeqtable → sub_modules/layout/doclayout_yolo}/__init__.py +0 -0
- /magic_pdf/model/{v3 → sub_modules/layout/layoutlmv3}/__init__.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/backbone.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/beit.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/deit.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/__init__.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/__init__.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/cord.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/data_collator.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/funsd.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/image_utils.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/xfund.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/__init__.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/__init__.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/configuration_layoutlmv3.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/modeling_layoutlmv3.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/tokenization_layoutlmv3.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/tokenization_layoutlmv3_fast.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/model_init.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/rcnn_vl.py +0 -0
- /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/visualizer.py +0 -0
- /magic_pdf/model/{v3 → sub_modules/reading_oreder/layoutreader}/helpers.py +0 -0
- {magic_pdf-0.9.2.dist-info → magic_pdf-0.10.0.dist-info}/LICENSE.md +0 -0
- {magic_pdf-0.9.2.dist-info → magic_pdf-0.10.0.dist-info}/entry_points.txt +0 -0
- {magic_pdf-0.9.2.dist-info → magic_pdf-0.10.0.dist-info}/top_level.txt +0 -0
magic_pdf/libs/Constants.py
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
span维度自定义字段
|
3
|
-
"""
|
4
|
-
# span是否是跨页合并的
|
5
|
-
CROSS_PAGE = "cross_page"
|
6
|
-
|
7
|
-
"""
|
8
|
-
block维度自定义字段
|
9
|
-
"""
|
10
|
-
# block中lines是否被删除
|
11
|
-
LINES_DELETED = "lines_deleted"
|
12
|
-
|
13
|
-
# table recognition max time default value
|
14
|
-
TABLE_MAX_TIME_VALUE = 400
|
15
|
-
|
16
|
-
# pp_table_result_max_length
|
17
|
-
TABLE_MAX_LEN = 480
|
18
|
-
|
19
|
-
# table master structure dict
|
20
|
-
TABLE_MASTER_DICT = "table_master_structure_dict.txt"
|
21
|
-
|
22
|
-
# table master dir
|
23
|
-
TABLE_MASTER_DIR = "table_structure_tablemaster_infer/"
|
24
|
-
|
25
|
-
# pp detect model dir
|
26
|
-
DETECT_MODEL_DIR = "ch_PP-OCRv4_det_infer"
|
27
|
-
|
28
|
-
# pp rec model dir
|
29
|
-
REC_MODEL_DIR = "ch_PP-OCRv4_rec_infer"
|
30
|
-
|
31
|
-
# pp rec char dict path
|
32
|
-
REC_CHAR_DICT = "ppocr_keys_v1.txt"
|
33
|
-
|
34
|
-
# pp rec copy rec directory
|
35
|
-
PP_REC_DIRECTORY = ".paddleocr/whl/rec/ch/ch_PP-OCRv4_rec_infer"
|
36
|
-
|
37
|
-
# pp rec copy det directory
|
38
|
-
PP_DET_DIRECTORY = ".paddleocr/whl/det/ch/ch_PP-OCRv4_det_infer"
|
39
|
-
|
40
|
-
|
41
|
-
class MODEL_NAME:
|
42
|
-
# pp table structure algorithm
|
43
|
-
TABLE_MASTER = "tablemaster"
|
44
|
-
# struct eqtable
|
45
|
-
STRUCT_EQTABLE = "struct_eqtable"
|
46
|
-
|
47
|
-
DocLayout_YOLO = "doclayout_yolo"
|
48
|
-
|
49
|
-
LAYOUTLMv3 = "layoutlmv3"
|
50
|
-
|
51
|
-
YOLO_V8_MFD = "yolo_v8_mfd"
|
52
|
-
|
53
|
-
UniMerNet_v2_Small = "unimernet_small"
|
magic_pdf/libs/drop_reason.py
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
|
2
|
-
class DropReason:
|
3
|
-
TEXT_BLCOK_HOR_OVERLAP = "text_block_horizontal_overlap" # 文字块有水平互相覆盖,导致无法准确定位文字顺序
|
4
|
-
USEFUL_BLOCK_HOR_OVERLAP = "useful_block_horizontal_overlap" # 需保留的block水平覆盖
|
5
|
-
COMPLICATED_LAYOUT = "complicated_layout" # 复杂的布局,暂时不支持
|
6
|
-
TOO_MANY_LAYOUT_COLUMNS = "too_many_layout_columns" # 目前不支持分栏超过2列的
|
7
|
-
COLOR_BACKGROUND_TEXT_BOX = "color_background_text_box" # 含有带色块的PDF,色块会改变阅读顺序,目前不支持带底色文字块的PDF。
|
8
|
-
HIGH_COMPUTATIONAL_lOAD_BY_IMGS = "high_computational_load_by_imgs" # 含特殊图片,计算量太大,从而丢弃
|
9
|
-
HIGH_COMPUTATIONAL_lOAD_BY_SVGS = "high_computational_load_by_svgs" # 特殊的SVG图,计算量太大,从而丢弃
|
10
|
-
HIGH_COMPUTATIONAL_lOAD_BY_TOTAL_PAGES = "high_computational_load_by_total_pages" # 计算量超过负荷,当前方法下计算量消耗过大
|
11
|
-
MISS_DOC_LAYOUT_RESULT = "missing doc_layout_result" # 版面分析失败
|
12
|
-
Exception = "_exception" # 解析中发生异常
|
13
|
-
ENCRYPTED = "encrypted" # PDF是加密的
|
14
|
-
EMPTY_PDF = "total_page=0" # PDF页面总数为0
|
15
|
-
NOT_IS_TEXT_PDF = "not_is_text_pdf" # 不是文字版PDF,无法直接解析
|
16
|
-
DENSE_SINGLE_LINE_BLOCK = "dense_single_line_block" # 无法清晰的分段
|
17
|
-
TITLE_DETECTION_FAILED = "title_detection_failed" # 探测标题失败
|
18
|
-
TITLE_LEVEL_FAILED = "title_level_failed" # 分析标题级别失败(例如一级、二级、三级标题)
|
19
|
-
PARA_SPLIT_FAILED = "para_split_failed" # 识别段落失败
|
20
|
-
PARA_MERGE_FAILED = "para_merge_failed" # 段落合并失败
|
21
|
-
NOT_ALLOW_LANGUAGE = "not_allow_language" # 不支持的语种
|
22
|
-
SPECIAL_PDF = "special_pdf"
|
23
|
-
PSEUDO_SINGLE_COLUMN = "pseudo_single_column" # 无法精确判断文字分栏
|
24
|
-
CAN_NOT_DETECT_PAGE_LAYOUT="can_not_detect_page_layout" # 无法分析页面的版面
|
25
|
-
NEGATIVE_BBOX_AREA = "negative_bbox_area" # 缩放导致 bbox 面积为负
|
26
|
-
OVERLAP_BLOCKS_CAN_NOT_SEPARATION = "overlap_blocks_can_t_separation" # 无法分离重叠的block
|
27
|
-
|
magic_pdf/libs/drop_tag.py
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
|
2
|
-
COLOR_BG_HEADER_TXT_BLOCK = "color_background_header_txt_block"
|
3
|
-
PAGE_NO = "page-no" # 页码
|
4
|
-
CONTENT_IN_FOOT_OR_HEADER = 'in-foot-header-area' # 页眉页脚内的文本
|
5
|
-
VERTICAL_TEXT = 'vertical-text' # 垂直文本
|
6
|
-
ROTATE_TEXT = 'rotate-text' # 旋转文本
|
7
|
-
EMPTY_SIDE_BLOCK = 'empty-side-block' # 边缘上的空白没有任何内容的block
|
8
|
-
ON_IMAGE_TEXT = 'on-image-text' # 文本在图片上
|
9
|
-
ON_TABLE_TEXT = 'on-table-text' # 文本在表格上
|
10
|
-
|
11
|
-
|
12
|
-
class DropTag:
|
13
|
-
PAGE_NUMBER = "page_no"
|
14
|
-
HEADER = "header"
|
15
|
-
FOOTER = "footer"
|
16
|
-
FOOTNOTE = "footnote"
|
17
|
-
NOT_IN_LAYOUT = "not_in_layout"
|
18
|
-
SPAN_OVERLAP = "span_overlap"
|
19
|
-
BLOCK_OVERLAP = "block_overlap"
|
@@ -1,36 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
|
3
|
-
def layout_rm_equation(layout_res):
|
4
|
-
rm_idxs = []
|
5
|
-
for idx, ele in enumerate(layout_res['layout_dets']):
|
6
|
-
if ele['category_id'] == 10:
|
7
|
-
rm_idxs.append(idx)
|
8
|
-
|
9
|
-
for idx in rm_idxs[::-1]:
|
10
|
-
del layout_res['layout_dets'][idx]
|
11
|
-
return layout_res
|
12
|
-
|
13
|
-
|
14
|
-
def get_croped_image(image_pil, bbox):
|
15
|
-
x_min, y_min, x_max, y_max = bbox
|
16
|
-
croped_img = image_pil.crop((x_min, y_min, x_max, y_max))
|
17
|
-
return croped_img
|
18
|
-
|
19
|
-
|
20
|
-
def latex_rm_whitespace(s: str):
|
21
|
-
"""Remove unnecessary whitespace from LaTeX code.
|
22
|
-
"""
|
23
|
-
text_reg = r'(\\(operatorname|mathrm|text|mathbf)\s?\*? {.*?})'
|
24
|
-
letter = '[a-zA-Z]'
|
25
|
-
noletter = '[\W_^\d]'
|
26
|
-
names = [x[0].replace(' ', '') for x in re.findall(text_reg, s)]
|
27
|
-
s = re.sub(text_reg, lambda match: str(names.pop(0)), s)
|
28
|
-
news = s
|
29
|
-
while True:
|
30
|
-
s = news
|
31
|
-
news = re.sub(r'(?!\\ )(%s)\s+?(%s)' % (noletter, noletter), r'\1\2', s)
|
32
|
-
news = re.sub(r'(?!\\ )(%s)\s+?(%s)' % (noletter, letter), r'\1\2', news)
|
33
|
-
news = re.sub(r'(%s)\s+?(%s)' % (letter, noletter), r'\1\2', news)
|
34
|
-
if news == s:
|
35
|
-
break
|
36
|
-
return s
|
@@ -1,388 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
import copy
|
3
|
-
import base64
|
4
|
-
import cv2
|
5
|
-
import numpy as np
|
6
|
-
from io import BytesIO
|
7
|
-
from PIL import Image
|
8
|
-
|
9
|
-
from paddleocr import PaddleOCR
|
10
|
-
from paddleocr.ppocr.utils.logging import get_logger
|
11
|
-
from paddleocr.ppocr.utils.utility import check_and_read, alpha_to_color, binarize_img
|
12
|
-
from paddleocr.tools.infer.utility import draw_ocr_box_txt, get_rotate_crop_image, get_minarea_rect_crop
|
13
|
-
|
14
|
-
from magic_pdf.libs.boxbase import __is_overlaps_y_exceeds_threshold
|
15
|
-
from magic_pdf.pre_proc.ocr_dict_merge import merge_spans_to_line
|
16
|
-
|
17
|
-
logger = get_logger()
|
18
|
-
|
19
|
-
|
20
|
-
def img_decode(content: bytes):
|
21
|
-
np_arr = np.frombuffer(content, dtype=np.uint8)
|
22
|
-
return cv2.imdecode(np_arr, cv2.IMREAD_UNCHANGED)
|
23
|
-
|
24
|
-
|
25
|
-
def check_img(img):
|
26
|
-
if isinstance(img, bytes):
|
27
|
-
img = img_decode(img)
|
28
|
-
if isinstance(img, str):
|
29
|
-
image_file = img
|
30
|
-
img, flag_gif, flag_pdf = check_and_read(image_file)
|
31
|
-
if not flag_gif and not flag_pdf:
|
32
|
-
with open(image_file, 'rb') as f:
|
33
|
-
img_str = f.read()
|
34
|
-
img = img_decode(img_str)
|
35
|
-
if img is None:
|
36
|
-
try:
|
37
|
-
buf = BytesIO()
|
38
|
-
image = BytesIO(img_str)
|
39
|
-
im = Image.open(image)
|
40
|
-
rgb = im.convert('RGB')
|
41
|
-
rgb.save(buf, 'jpeg')
|
42
|
-
buf.seek(0)
|
43
|
-
image_bytes = buf.read()
|
44
|
-
data_base64 = str(base64.b64encode(image_bytes),
|
45
|
-
encoding="utf-8")
|
46
|
-
image_decode = base64.b64decode(data_base64)
|
47
|
-
img_array = np.frombuffer(image_decode, np.uint8)
|
48
|
-
img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
|
49
|
-
except:
|
50
|
-
logger.error("error in loading image:{}".format(image_file))
|
51
|
-
return None
|
52
|
-
if img is None:
|
53
|
-
logger.error("error in loading image:{}".format(image_file))
|
54
|
-
return None
|
55
|
-
if isinstance(img, np.ndarray) and len(img.shape) == 2:
|
56
|
-
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
|
57
|
-
|
58
|
-
return img
|
59
|
-
|
60
|
-
|
61
|
-
def sorted_boxes(dt_boxes):
|
62
|
-
"""
|
63
|
-
Sort text boxes in order from top to bottom, left to right
|
64
|
-
args:
|
65
|
-
dt_boxes(array):detected text boxes with shape [4, 2]
|
66
|
-
return:
|
67
|
-
sorted boxes(array) with shape [4, 2]
|
68
|
-
"""
|
69
|
-
num_boxes = dt_boxes.shape[0]
|
70
|
-
sorted_boxes = sorted(dt_boxes, key=lambda x: (x[0][1], x[0][0]))
|
71
|
-
_boxes = list(sorted_boxes)
|
72
|
-
|
73
|
-
for i in range(num_boxes - 1):
|
74
|
-
for j in range(i, -1, -1):
|
75
|
-
if abs(_boxes[j + 1][0][1] - _boxes[j][0][1]) < 10 and \
|
76
|
-
(_boxes[j + 1][0][0] < _boxes[j][0][0]):
|
77
|
-
tmp = _boxes[j]
|
78
|
-
_boxes[j] = _boxes[j + 1]
|
79
|
-
_boxes[j + 1] = tmp
|
80
|
-
else:
|
81
|
-
break
|
82
|
-
return _boxes
|
83
|
-
|
84
|
-
|
85
|
-
def bbox_to_points(bbox):
|
86
|
-
""" 将bbox格式转换为四个顶点的数组 """
|
87
|
-
x0, y0, x1, y1 = bbox
|
88
|
-
return np.array([[x0, y0], [x1, y0], [x1, y1], [x0, y1]]).astype('float32')
|
89
|
-
|
90
|
-
|
91
|
-
def points_to_bbox(points):
|
92
|
-
""" 将四个顶点的数组转换为bbox格式 """
|
93
|
-
x0, y0 = points[0]
|
94
|
-
x1, _ = points[1]
|
95
|
-
_, y1 = points[2]
|
96
|
-
return [x0, y0, x1, y1]
|
97
|
-
|
98
|
-
|
99
|
-
def merge_intervals(intervals):
|
100
|
-
# Sort the intervals based on the start value
|
101
|
-
intervals.sort(key=lambda x: x[0])
|
102
|
-
|
103
|
-
merged = []
|
104
|
-
for interval in intervals:
|
105
|
-
# If the list of merged intervals is empty or if the current
|
106
|
-
# interval does not overlap with the previous, simply append it.
|
107
|
-
if not merged or merged[-1][1] < interval[0]:
|
108
|
-
merged.append(interval)
|
109
|
-
else:
|
110
|
-
# Otherwise, there is overlap, so we merge the current and previous intervals.
|
111
|
-
merged[-1][1] = max(merged[-1][1], interval[1])
|
112
|
-
|
113
|
-
return merged
|
114
|
-
|
115
|
-
|
116
|
-
def remove_intervals(original, masks):
|
117
|
-
# Merge all mask intervals
|
118
|
-
merged_masks = merge_intervals(masks)
|
119
|
-
|
120
|
-
result = []
|
121
|
-
original_start, original_end = original
|
122
|
-
|
123
|
-
for mask in merged_masks:
|
124
|
-
mask_start, mask_end = mask
|
125
|
-
|
126
|
-
# If the mask starts after the original range, ignore it
|
127
|
-
if mask_start > original_end:
|
128
|
-
continue
|
129
|
-
|
130
|
-
# If the mask ends before the original range starts, ignore it
|
131
|
-
if mask_end < original_start:
|
132
|
-
continue
|
133
|
-
|
134
|
-
# Remove the masked part from the original range
|
135
|
-
if original_start < mask_start:
|
136
|
-
result.append([original_start, mask_start - 1])
|
137
|
-
|
138
|
-
original_start = max(mask_end + 1, original_start)
|
139
|
-
|
140
|
-
# Add the remaining part of the original range, if any
|
141
|
-
if original_start <= original_end:
|
142
|
-
result.append([original_start, original_end])
|
143
|
-
|
144
|
-
return result
|
145
|
-
|
146
|
-
|
147
|
-
def update_det_boxes(dt_boxes, mfd_res):
|
148
|
-
new_dt_boxes = []
|
149
|
-
for text_box in dt_boxes:
|
150
|
-
text_bbox = points_to_bbox(text_box)
|
151
|
-
masks_list = []
|
152
|
-
for mf_box in mfd_res:
|
153
|
-
mf_bbox = mf_box['bbox']
|
154
|
-
if __is_overlaps_y_exceeds_threshold(text_bbox, mf_bbox):
|
155
|
-
masks_list.append([mf_bbox[0], mf_bbox[2]])
|
156
|
-
text_x_range = [text_bbox[0], text_bbox[2]]
|
157
|
-
text_remove_mask_range = remove_intervals(text_x_range, masks_list)
|
158
|
-
temp_dt_box = []
|
159
|
-
for text_remove_mask in text_remove_mask_range:
|
160
|
-
temp_dt_box.append(bbox_to_points([text_remove_mask[0], text_bbox[1], text_remove_mask[1], text_bbox[3]]))
|
161
|
-
if len(temp_dt_box) > 0:
|
162
|
-
new_dt_boxes.extend(temp_dt_box)
|
163
|
-
return new_dt_boxes
|
164
|
-
|
165
|
-
|
166
|
-
def merge_overlapping_spans(spans):
|
167
|
-
"""
|
168
|
-
Merges overlapping spans on the same line.
|
169
|
-
|
170
|
-
:param spans: A list of span coordinates [(x1, y1, x2, y2), ...]
|
171
|
-
:return: A list of merged spans
|
172
|
-
"""
|
173
|
-
# Return an empty list if the input spans list is empty
|
174
|
-
if not spans:
|
175
|
-
return []
|
176
|
-
|
177
|
-
# Sort spans by their starting x-coordinate
|
178
|
-
spans.sort(key=lambda x: x[0])
|
179
|
-
|
180
|
-
# Initialize the list of merged spans
|
181
|
-
merged = []
|
182
|
-
for span in spans:
|
183
|
-
# Unpack span coordinates
|
184
|
-
x1, y1, x2, y2 = span
|
185
|
-
# If the merged list is empty or there's no horizontal overlap, add the span directly
|
186
|
-
if not merged or merged[-1][2] < x1:
|
187
|
-
merged.append(span)
|
188
|
-
else:
|
189
|
-
# If there is horizontal overlap, merge the current span with the previous one
|
190
|
-
last_span = merged.pop()
|
191
|
-
# Update the merged span's top-left corner to the smaller (x1, y1) and bottom-right to the larger (x2, y2)
|
192
|
-
x1 = min(last_span[0], x1)
|
193
|
-
y1 = min(last_span[1], y1)
|
194
|
-
x2 = max(last_span[2], x2)
|
195
|
-
y2 = max(last_span[3], y2)
|
196
|
-
# Add the merged span back to the list
|
197
|
-
merged.append((x1, y1, x2, y2))
|
198
|
-
|
199
|
-
# Return the list of merged spans
|
200
|
-
return merged
|
201
|
-
|
202
|
-
|
203
|
-
def merge_det_boxes(dt_boxes):
|
204
|
-
"""
|
205
|
-
Merge detection boxes.
|
206
|
-
|
207
|
-
This function takes a list of detected bounding boxes, each represented by four corner points.
|
208
|
-
The goal is to merge these bounding boxes into larger text regions.
|
209
|
-
|
210
|
-
Parameters:
|
211
|
-
dt_boxes (list): A list containing multiple text detection boxes, where each box is defined by four corner points.
|
212
|
-
|
213
|
-
Returns:
|
214
|
-
list: A list containing the merged text regions, where each region is represented by four corner points.
|
215
|
-
"""
|
216
|
-
# Convert the detection boxes into a dictionary format with bounding boxes and type
|
217
|
-
dt_boxes_dict_list = []
|
218
|
-
for text_box in dt_boxes:
|
219
|
-
text_bbox = points_to_bbox(text_box)
|
220
|
-
text_box_dict = {
|
221
|
-
'bbox': text_bbox,
|
222
|
-
'type': 'text',
|
223
|
-
}
|
224
|
-
dt_boxes_dict_list.append(text_box_dict)
|
225
|
-
|
226
|
-
# Merge adjacent text regions into lines
|
227
|
-
lines = merge_spans_to_line(dt_boxes_dict_list)
|
228
|
-
|
229
|
-
# Initialize a new list for storing the merged text regions
|
230
|
-
new_dt_boxes = []
|
231
|
-
for line in lines:
|
232
|
-
line_bbox_list = []
|
233
|
-
for span in line:
|
234
|
-
line_bbox_list.append(span['bbox'])
|
235
|
-
|
236
|
-
# Merge overlapping text regions within the same line
|
237
|
-
merged_spans = merge_overlapping_spans(line_bbox_list)
|
238
|
-
|
239
|
-
# Convert the merged text regions back to point format and add them to the new detection box list
|
240
|
-
for span in merged_spans:
|
241
|
-
new_dt_boxes.append(bbox_to_points(span))
|
242
|
-
|
243
|
-
return new_dt_boxes
|
244
|
-
|
245
|
-
|
246
|
-
class ModifiedPaddleOCR(PaddleOCR):
|
247
|
-
def ocr(self, img, det=True, rec=True, cls=True, bin=False, inv=False, mfd_res=None, alpha_color=(255, 255, 255)):
|
248
|
-
"""
|
249
|
-
OCR with PaddleOCR
|
250
|
-
args:
|
251
|
-
img: img for OCR, support ndarray, img_path and list or ndarray
|
252
|
-
det: use text detection or not. If False, only rec will be exec. Default is True
|
253
|
-
rec: use text recognition or not. If False, only det will be exec. Default is True
|
254
|
-
cls: use angle classifier or not. Default is True. If True, the text with rotation of 180 degrees can be recognized. If no text is rotated by 180 degrees, use cls=False to get better performance. Text with rotation of 90 or 270 degrees can be recognized even if cls=False.
|
255
|
-
bin: binarize image to black and white. Default is False.
|
256
|
-
inv: invert image colors. Default is False.
|
257
|
-
alpha_color: set RGB color Tuple for transparent parts replacement. Default is pure white.
|
258
|
-
"""
|
259
|
-
assert isinstance(img, (np.ndarray, list, str, bytes))
|
260
|
-
if isinstance(img, list) and det == True:
|
261
|
-
logger.error('When input a list of images, det must be false')
|
262
|
-
exit(0)
|
263
|
-
if cls == True and self.use_angle_cls == False:
|
264
|
-
pass
|
265
|
-
# logger.warning(
|
266
|
-
# 'Since the angle classifier is not initialized, it will not be used during the forward process'
|
267
|
-
# )
|
268
|
-
|
269
|
-
img = check_img(img)
|
270
|
-
# for infer pdf file
|
271
|
-
if isinstance(img, list):
|
272
|
-
if self.page_num > len(img) or self.page_num == 0:
|
273
|
-
self.page_num = len(img)
|
274
|
-
imgs = img[:self.page_num]
|
275
|
-
else:
|
276
|
-
imgs = [img]
|
277
|
-
|
278
|
-
def preprocess_image(_image):
|
279
|
-
_image = alpha_to_color(_image, alpha_color)
|
280
|
-
if inv:
|
281
|
-
_image = cv2.bitwise_not(_image)
|
282
|
-
if bin:
|
283
|
-
_image = binarize_img(_image)
|
284
|
-
return _image
|
285
|
-
|
286
|
-
if det and rec:
|
287
|
-
ocr_res = []
|
288
|
-
for idx, img in enumerate(imgs):
|
289
|
-
img = preprocess_image(img)
|
290
|
-
dt_boxes, rec_res, _ = self.__call__(img, cls, mfd_res=mfd_res)
|
291
|
-
if not dt_boxes and not rec_res:
|
292
|
-
ocr_res.append(None)
|
293
|
-
continue
|
294
|
-
tmp_res = [[box.tolist(), res]
|
295
|
-
for box, res in zip(dt_boxes, rec_res)]
|
296
|
-
ocr_res.append(tmp_res)
|
297
|
-
return ocr_res
|
298
|
-
elif det and not rec:
|
299
|
-
ocr_res = []
|
300
|
-
for idx, img in enumerate(imgs):
|
301
|
-
img = preprocess_image(img)
|
302
|
-
dt_boxes, elapse = self.text_detector(img)
|
303
|
-
if not dt_boxes:
|
304
|
-
ocr_res.append(None)
|
305
|
-
continue
|
306
|
-
tmp_res = [box.tolist() for box in dt_boxes]
|
307
|
-
ocr_res.append(tmp_res)
|
308
|
-
return ocr_res
|
309
|
-
else:
|
310
|
-
ocr_res = []
|
311
|
-
cls_res = []
|
312
|
-
for idx, img in enumerate(imgs):
|
313
|
-
if not isinstance(img, list):
|
314
|
-
img = preprocess_image(img)
|
315
|
-
img = [img]
|
316
|
-
if self.use_angle_cls and cls:
|
317
|
-
img, cls_res_tmp, elapse = self.text_classifier(img)
|
318
|
-
if not rec:
|
319
|
-
cls_res.append(cls_res_tmp)
|
320
|
-
rec_res, elapse = self.text_recognizer(img)
|
321
|
-
ocr_res.append(rec_res)
|
322
|
-
if not rec:
|
323
|
-
return cls_res
|
324
|
-
return ocr_res
|
325
|
-
|
326
|
-
def __call__(self, img, cls=True, mfd_res=None):
|
327
|
-
time_dict = {'det': 0, 'rec': 0, 'cls': 0, 'all': 0}
|
328
|
-
|
329
|
-
if img is None:
|
330
|
-
logger.debug("no valid image provided")
|
331
|
-
return None, None, time_dict
|
332
|
-
|
333
|
-
start = time.time()
|
334
|
-
ori_im = img.copy()
|
335
|
-
dt_boxes, elapse = self.text_detector(img)
|
336
|
-
time_dict['det'] = elapse
|
337
|
-
|
338
|
-
if dt_boxes is None:
|
339
|
-
logger.debug("no dt_boxes found, elapsed : {}".format(elapse))
|
340
|
-
end = time.time()
|
341
|
-
time_dict['all'] = end - start
|
342
|
-
return None, None, time_dict
|
343
|
-
else:
|
344
|
-
logger.debug("dt_boxes num : {}, elapsed : {}".format(
|
345
|
-
len(dt_boxes), elapse))
|
346
|
-
img_crop_list = []
|
347
|
-
|
348
|
-
dt_boxes = sorted_boxes(dt_boxes)
|
349
|
-
|
350
|
-
dt_boxes = merge_det_boxes(dt_boxes)
|
351
|
-
|
352
|
-
if mfd_res:
|
353
|
-
bef = time.time()
|
354
|
-
dt_boxes = update_det_boxes(dt_boxes, mfd_res)
|
355
|
-
aft = time.time()
|
356
|
-
logger.debug("split text box by formula, new dt_boxes num : {}, elapsed : {}".format(
|
357
|
-
len(dt_boxes), aft - bef))
|
358
|
-
|
359
|
-
for bno in range(len(dt_boxes)):
|
360
|
-
tmp_box = copy.deepcopy(dt_boxes[bno])
|
361
|
-
if self.args.det_box_type == "quad":
|
362
|
-
img_crop = get_rotate_crop_image(ori_im, tmp_box)
|
363
|
-
else:
|
364
|
-
img_crop = get_minarea_rect_crop(ori_im, tmp_box)
|
365
|
-
img_crop_list.append(img_crop)
|
366
|
-
if self.use_angle_cls and cls:
|
367
|
-
img_crop_list, angle_list, elapse = self.text_classifier(
|
368
|
-
img_crop_list)
|
369
|
-
time_dict['cls'] = elapse
|
370
|
-
logger.debug("cls num : {}, elapsed : {}".format(
|
371
|
-
len(img_crop_list), elapse))
|
372
|
-
|
373
|
-
rec_res, elapse = self.text_recognizer(img_crop_list)
|
374
|
-
time_dict['rec'] = elapse
|
375
|
-
logger.debug("rec_res num : {}, elapsed : {}".format(
|
376
|
-
len(rec_res), elapse))
|
377
|
-
if self.args.save_crop_res:
|
378
|
-
self.draw_crop_rec_res(self.args.crop_res_save_dir, img_crop_list,
|
379
|
-
rec_res)
|
380
|
-
filter_boxes, filter_rec_res = [], []
|
381
|
-
for box, rec_result in zip(dt_boxes, rec_res):
|
382
|
-
text, score = rec_result
|
383
|
-
if score >= self.drop_score:
|
384
|
-
filter_boxes.append(box)
|
385
|
-
filter_rec_res.append(rec_result)
|
386
|
-
end = time.time()
|
387
|
-
time_dict['all'] = end - start
|
388
|
-
return filter_boxes, filter_rec_res, time_dict
|