magic-pdf 0.7.1__py3-none-any.whl → 0.8.1__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/dict2md/ocr_mkcontent.py +130 -76
- magic_pdf/integrations/__init__.py +0 -0
- magic_pdf/integrations/rag/__init__.py +0 -0
- magic_pdf/integrations/rag/api.py +82 -0
- magic_pdf/integrations/rag/type.py +82 -0
- magic_pdf/integrations/rag/utils.py +285 -0
- magic_pdf/layout/layout_sort.py +472 -283
- magic_pdf/libs/boxbase.py +188 -149
- magic_pdf/libs/draw_bbox.py +113 -87
- magic_pdf/libs/ocr_content_type.py +21 -18
- magic_pdf/libs/version.py +1 -1
- magic_pdf/model/doc_analyze_by_custom_model.py +14 -2
- magic_pdf/model/magic_model.py +283 -166
- magic_pdf/model/model_list.py +8 -0
- magic_pdf/model/pdf_extract_kit.py +105 -15
- magic_pdf/model/pek_sub_modules/self_modify.py +84 -0
- magic_pdf/para/para_split_v2.py +26 -27
- magic_pdf/pdf_parse_union_core.py +34 -6
- magic_pdf/pipe/AbsPipe.py +4 -1
- magic_pdf/pipe/OCRPipe.py +7 -4
- magic_pdf/pipe/TXTPipe.py +7 -4
- magic_pdf/pipe/UNIPipe.py +11 -6
- magic_pdf/pre_proc/ocr_detect_all_bboxes.py +12 -3
- magic_pdf/pre_proc/ocr_dict_merge.py +60 -59
- magic_pdf/tools/cli.py +56 -29
- magic_pdf/tools/cli_dev.py +61 -64
- magic_pdf/tools/common.py +57 -37
- magic_pdf/user_api.py +17 -9
- {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/METADATA +72 -27
- {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/RECORD +34 -29
- {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/LICENSE.md +0 -0
- {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/WHEEL +0 -0
- {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/entry_points.txt +0 -0
- {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/top_level.txt +0 -0
magic_pdf/layout/layout_sort.py
CHANGED
@@ -1,60 +1,66 @@
|
|
1
|
-
"""
|
2
|
-
对pdf上的box进行layout识别,并对内部组成的box进行排序
|
3
|
-
"""
|
1
|
+
"""对pdf上的box进行layout识别,并对内部组成的box进行排序."""
|
4
2
|
|
5
3
|
from loguru import logger
|
6
|
-
from magic_pdf.layout.bbox_sort import CONTENT_IDX, CONTENT_TYPE_IDX, X0_EXT_IDX, X0_IDX, X1_EXT_IDX, X1_IDX, Y0_EXT_IDX, Y0_IDX, Y1_EXT_IDX, Y1_IDX, paper_bbox_sort
|
7
|
-
from magic_pdf.layout.layout_det_utils import find_all_left_bbox_direct, find_all_right_bbox_direct, find_bottom_bbox_direct_from_left_edge, find_bottom_bbox_direct_from_right_edge, find_top_bbox_direct_from_left_edge, find_top_bbox_direct_from_right_edge, find_all_top_bbox_direct, find_all_bottom_bbox_direct, get_left_edge_bboxes, get_right_edge_bboxes
|
8
|
-
from magic_pdf.libs.boxbase import get_bbox_in_boundry
|
9
4
|
|
5
|
+
from magic_pdf.layout.bbox_sort import (CONTENT_IDX, CONTENT_TYPE_IDX,
|
6
|
+
X0_EXT_IDX, X0_IDX, X1_EXT_IDX, X1_IDX,
|
7
|
+
Y0_EXT_IDX, Y0_IDX, Y1_EXT_IDX, Y1_IDX,
|
8
|
+
paper_bbox_sort)
|
9
|
+
from magic_pdf.layout.layout_det_utils import (
|
10
|
+
find_all_bottom_bbox_direct, find_all_left_bbox_direct,
|
11
|
+
find_all_right_bbox_direct, find_all_top_bbox_direct,
|
12
|
+
find_bottom_bbox_direct_from_left_edge,
|
13
|
+
find_bottom_bbox_direct_from_right_edge,
|
14
|
+
find_top_bbox_direct_from_left_edge, find_top_bbox_direct_from_right_edge,
|
15
|
+
get_left_edge_bboxes, get_right_edge_bboxes)
|
16
|
+
from magic_pdf.libs.boxbase import get_bbox_in_boundary
|
17
|
+
|
18
|
+
LAYOUT_V = 'V'
|
19
|
+
LAYOUT_H = 'H'
|
20
|
+
LAYOUT_UNPROC = 'U'
|
21
|
+
LAYOUT_BAD = 'B'
|
10
22
|
|
11
|
-
LAYOUT_V = "V"
|
12
|
-
LAYOUT_H = "H"
|
13
|
-
LAYOUT_UNPROC = "U"
|
14
|
-
LAYOUT_BAD = "B"
|
15
23
|
|
16
24
|
def _is_single_line_text(bbox):
|
17
|
-
"""
|
18
|
-
|
19
|
-
"""
|
20
|
-
return True # TODO
|
25
|
+
"""检查bbox里面的文字是否只有一行."""
|
26
|
+
return True # TODO
|
21
27
|
box_type = bbox[CONTENT_TYPE_IDX]
|
22
28
|
if box_type != 'text':
|
23
29
|
return False
|
24
|
-
paras = bbox[CONTENT_IDX][
|
25
|
-
text_content =
|
30
|
+
paras = bbox[CONTENT_IDX]['paras']
|
31
|
+
text_content = ''
|
26
32
|
for para_id, para in paras.items(): # 拼装内部的段落文本
|
27
33
|
is_title = para['is_title']
|
28
|
-
if is_title!=0:
|
34
|
+
if is_title != 0:
|
29
35
|
text_content += f"## {para['text']}"
|
30
36
|
else:
|
31
|
-
text_content += para[
|
32
|
-
text_content +=
|
33
|
-
|
34
|
-
return bbox[CONTENT_TYPE_IDX] == 'text' and len(text_content.split("\n\n")) <= 1
|
37
|
+
text_content += para['text']
|
38
|
+
text_content += '\n\n'
|
35
39
|
|
40
|
+
return bbox[CONTENT_TYPE_IDX] == 'text' and len(text_content.split('\n\n')) <= 1
|
36
41
|
|
37
|
-
|
42
|
+
|
43
|
+
def _horizontal_split(bboxes: list, boundary: tuple, avg_font_size=20) -> list:
|
38
44
|
"""
|
39
45
|
对bboxes进行水平切割
|
40
46
|
方法是:找到左侧和右侧都没有被直接遮挡的box,然后进行扩展,之后进行切割
|
41
47
|
return:
|
42
48
|
返回几个大的Layout区域 [[x0, y0, x1, y1, "h|u|v"], ], h代表水平,u代表未探测的,v代表垂直布局
|
43
49
|
"""
|
44
|
-
sorted_layout_blocks = []
|
45
|
-
|
46
|
-
bound_x0, bound_y0, bound_x1, bound_y1 =
|
47
|
-
all_bboxes =
|
48
|
-
#all_bboxes = paper_bbox_sort(all_bboxes, abs(bound_x1-bound_x0), abs(bound_y1-bound_x0)) # 大致拍下序, 这个是基于直接遮挡的。
|
50
|
+
sorted_layout_blocks = [] # 这是要最终返回的值
|
51
|
+
|
52
|
+
bound_x0, bound_y0, bound_x1, bound_y1 = boundary
|
53
|
+
all_bboxes = get_bbox_in_boundary(bboxes, boundary)
|
54
|
+
# all_bboxes = paper_bbox_sort(all_bboxes, abs(bound_x1-bound_x0), abs(bound_y1-bound_x0)) # 大致拍下序, 这个是基于直接遮挡的。
|
49
55
|
"""
|
50
56
|
首先在水平方向上扩展独占一行的bbox
|
51
|
-
|
57
|
+
|
52
58
|
"""
|
53
|
-
last_h_split_line_y1 = bound_y0
|
59
|
+
last_h_split_line_y1 = bound_y0 # 记录下上次的水平分割线
|
54
60
|
for i, bbox in enumerate(all_bboxes):
|
55
|
-
left_nearest_bbox = find_all_left_bbox_direct(bbox, all_bboxes)
|
61
|
+
left_nearest_bbox = find_all_left_bbox_direct(bbox, all_bboxes) # 非扩展线
|
56
62
|
right_nearest_bbox = find_all_right_bbox_direct(bbox, all_bboxes)
|
57
|
-
if left_nearest_bbox is None and right_nearest_bbox is None:
|
63
|
+
if left_nearest_bbox is None and right_nearest_bbox is None: # 独占一行
|
58
64
|
"""
|
59
65
|
然而,如果只是孤立的一行文字,那么就还要满足以下几个条件才可以:
|
60
66
|
1. bbox和中心线相交。或者
|
@@ -62,16 +68,20 @@ def _horizontal_split(bboxes:list, boundry:tuple, avg_font_size=20)-> list:
|
|
62
68
|
3. TODO 加强条件:这个bbox上方和下方是同一列column,那么就不能算作独占一行
|
63
69
|
"""
|
64
70
|
# 先检查这个bbox里是否只包含一行文字
|
65
|
-
is_single_line =
|
71
|
+
# is_single_line = _is_single_line_text(bbox)
|
66
72
|
"""
|
67
|
-
这里有个点需要注意,当页面内容不是居中的时候,第一次调用传递的是page的
|
68
|
-
所以这里计算出最紧致的
|
73
|
+
这里有个点需要注意,当页面内容不是居中的时候,第一次调用传递的是page的boundary,这个时候mid_x就不是中心线了.
|
74
|
+
所以这里计算出最紧致的boundary,然后再计算mid_x
|
69
75
|
"""
|
70
|
-
|
71
|
-
|
76
|
+
boundary_real_x0, boundary_real_x1 = min(
|
77
|
+
[bbox[X0_IDX] for bbox in all_bboxes]
|
78
|
+
), max([bbox[X1_IDX] for bbox in all_bboxes])
|
79
|
+
mid_x = (boundary_real_x0 + boundary_real_x1) / 2
|
72
80
|
# 检查这个box是否内容在中心线有交
|
73
81
|
# 必须跨过去2个字符的宽度
|
74
|
-
|
82
|
+
is_cross_boundary_mid_line = (
|
83
|
+
min(mid_x - bbox[X0_IDX], bbox[X1_IDX] - mid_x) > avg_font_size * 2
|
84
|
+
)
|
75
85
|
"""
|
76
86
|
检查条件2
|
77
87
|
"""
|
@@ -84,50 +94,78 @@ def _horizontal_split(bboxes:list, boundry:tuple, avg_font_size=20)-> list:
|
|
84
94
|
"""
|
85
95
|
以迭代的方式向上找,查找范围是[bound_x0, last_h_sp, bound_x1, bbox[Y0_IDX]]
|
86
96
|
"""
|
87
|
-
|
97
|
+
# 先确定上方的y0, y0
|
88
98
|
b_y0, b_y1 = last_h_split_line_y1, bbox[Y0_IDX]
|
89
|
-
|
99
|
+
# 然后从box开始逐个向上找到所有与box在x上有交集的box
|
90
100
|
box_to_check = [bound_x0, b_y0, bound_x1, b_y1]
|
91
|
-
bbox_in_bound_check =
|
92
|
-
|
101
|
+
bbox_in_bound_check = get_bbox_in_boundary(all_bboxes, box_to_check)
|
102
|
+
|
93
103
|
bboxes_on_top = []
|
94
104
|
virtual_box = bbox
|
95
105
|
while True:
|
96
106
|
b_on_top = find_all_top_bbox_direct(virtual_box, bbox_in_bound_check)
|
97
107
|
if b_on_top is not None:
|
98
108
|
bboxes_on_top.append(b_on_top)
|
99
|
-
virtual_box = [
|
109
|
+
virtual_box = [
|
110
|
+
min([virtual_box[X0_IDX], b_on_top[X0_IDX]]),
|
111
|
+
min(virtual_box[Y0_IDX], b_on_top[Y0_IDX]),
|
112
|
+
max([virtual_box[X1_IDX], b_on_top[X1_IDX]]),
|
113
|
+
b_y1,
|
114
|
+
]
|
100
115
|
else:
|
101
116
|
break
|
102
117
|
|
103
118
|
# 随后确定这些box的最小x0, 最大x1
|
104
|
-
if len(bboxes_on_top)>0 and len(bboxes_on_top) != len(
|
119
|
+
if len(bboxes_on_top) > 0 and len(bboxes_on_top) != len(
|
120
|
+
bbox_in_bound_check
|
121
|
+
): # virtual_box可能会膨胀到占满整个区域,这实际上就不能属于一个col了。
|
105
122
|
min_x0, max_x1 = virtual_box[X0_IDX], virtual_box[X1_IDX]
|
106
123
|
# 然后采用一种比较粗糙的方法,看min_x0,max_x1是否与位于[bound_x0, last_h_sp, bound_x1, bbox[Y0_IDX]]之间的box有相交
|
107
|
-
|
108
|
-
if not any(
|
124
|
+
|
125
|
+
if not any(
|
126
|
+
[
|
127
|
+
b[X0_IDX] <= min_x0 - 1 <= b[X1_IDX]
|
128
|
+
or b[X0_IDX] <= max_x1 + 1 <= b[X1_IDX]
|
129
|
+
for b in bbox_in_bound_check
|
130
|
+
]
|
131
|
+
):
|
109
132
|
# 其上,下都不能被扩展成行,暂时只检查一下上方 TODO
|
110
133
|
top_nearest_bbox = find_all_top_bbox_direct(bbox, bboxes)
|
111
134
|
bottom_nearest_bbox = find_all_bottom_bbox_direct(bbox, bboxes)
|
112
|
-
if not any(
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
135
|
+
if not any(
|
136
|
+
[
|
137
|
+
top_nearest_bbox is not None
|
138
|
+
and (
|
139
|
+
find_all_left_bbox_direct(top_nearest_bbox, bboxes)
|
140
|
+
is None
|
141
|
+
and find_all_right_bbox_direct(top_nearest_bbox, bboxes)
|
142
|
+
is None
|
143
|
+
),
|
144
|
+
bottom_nearest_bbox is not None
|
145
|
+
and (
|
146
|
+
find_all_left_bbox_direct(bottom_nearest_bbox, bboxes)
|
147
|
+
is None
|
148
|
+
and find_all_right_bbox_direct(
|
149
|
+
bottom_nearest_bbox, bboxes
|
150
|
+
)
|
151
|
+
is None
|
152
|
+
),
|
153
|
+
top_nearest_bbox is None or bottom_nearest_bbox is None,
|
154
|
+
]
|
155
|
+
):
|
156
|
+
is_belong_to_col = True
|
157
|
+
|
119
158
|
# 检查是否能被下方col吸收 TODO
|
120
|
-
|
121
159
|
"""
|
122
|
-
这里为什么没有
|
160
|
+
这里为什么没有is_cross_boundary_mid_line的条件呢?
|
123
161
|
确实有些杂志左右两栏宽度不是对称的。
|
124
162
|
"""
|
125
|
-
if not is_belong_to_col or
|
163
|
+
if not is_belong_to_col or is_cross_boundary_mid_line:
|
126
164
|
bbox[X0_EXT_IDX] = bound_x0
|
127
165
|
bbox[Y0_EXT_IDX] = bbox[Y0_IDX]
|
128
166
|
bbox[X1_EXT_IDX] = bound_x1
|
129
167
|
bbox[Y1_EXT_IDX] = bbox[Y1_IDX]
|
130
|
-
last_h_split_line_y1 = bbox[Y1_IDX]
|
168
|
+
last_h_split_line_y1 = bbox[Y1_IDX] # 更新这条线
|
131
169
|
else:
|
132
170
|
continue
|
133
171
|
"""
|
@@ -142,13 +180,12 @@ def _horizontal_split(bboxes:list, boundry:tuple, avg_font_size=20)-> list:
|
|
142
180
|
if bbox[X0_EXT_IDX] == bound_x0 and bbox[X1_EXT_IDX] == bound_x1:
|
143
181
|
h_bbox_group.append(bbox)
|
144
182
|
else:
|
145
|
-
if len(h_bbox_group)>0:
|
146
|
-
h_bboxes.append(h_bbox_group)
|
183
|
+
if len(h_bbox_group) > 0:
|
184
|
+
h_bboxes.append(h_bbox_group)
|
147
185
|
h_bbox_group = []
|
148
186
|
# 最后一个group
|
149
|
-
if len(h_bbox_group)>0:
|
187
|
+
if len(h_bbox_group) > 0:
|
150
188
|
h_bboxes.append(h_bbox_group)
|
151
|
-
|
152
189
|
"""
|
153
190
|
现在h_bboxes里面是所有的group了,每个group都是一个list
|
154
191
|
对h_bboxes里的每个group进行计算放回到sorted_layouts里
|
@@ -157,52 +194,67 @@ def _horizontal_split(bboxes:list, boundry:tuple, avg_font_size=20)-> list:
|
|
157
194
|
for gp in h_bboxes:
|
158
195
|
gp.sort(key=lambda x: x[Y0_IDX])
|
159
196
|
# 然后计算这个group的layout_bbox,也就是最小的x0,y0, 最大的x1,y1
|
160
|
-
x0, y0, x1, y1 =
|
161
|
-
|
162
|
-
|
197
|
+
x0, y0, x1, y1 = (
|
198
|
+
gp[0][X0_EXT_IDX],
|
199
|
+
gp[0][Y0_EXT_IDX],
|
200
|
+
gp[-1][X1_EXT_IDX],
|
201
|
+
gp[-1][Y1_EXT_IDX],
|
202
|
+
)
|
203
|
+
h_layouts.append([x0, y0, x1, y1, LAYOUT_H]) # 水平的布局
|
163
204
|
"""
|
164
205
|
接下来利用这些连续的水平bbox的layout_bbox的y0, y1,从水平上切分开其余的为几个部分
|
165
206
|
"""
|
166
207
|
h_split_lines = [bound_y0]
|
167
|
-
for gp in h_bboxes:
|
208
|
+
for gp in h_bboxes: # gp是一个list[bbox_list]
|
168
209
|
y0, y1 = gp[0][1], gp[-1][3]
|
169
210
|
h_split_lines.append(y0)
|
170
211
|
h_split_lines.append(y1)
|
171
212
|
h_split_lines.append(bound_y1)
|
172
|
-
|
213
|
+
|
173
214
|
unsplited_bboxes = []
|
174
215
|
for i in range(0, len(h_split_lines), 2):
|
175
|
-
start_y0, start_y1 = h_split_lines[i:i+2]
|
216
|
+
start_y0, start_y1 = h_split_lines[i : i + 2]
|
176
217
|
# 然后找出[start_y0, start_y1]之间的其他bbox,这些组成一个未分割板块
|
177
|
-
bboxes_in_block = [
|
218
|
+
bboxes_in_block = [
|
219
|
+
bbox
|
220
|
+
for bbox in all_bboxes
|
221
|
+
if bbox[Y0_IDX] >= start_y0 and bbox[Y1_IDX] <= start_y1
|
222
|
+
]
|
178
223
|
unsplited_bboxes.append(bboxes_in_block)
|
179
224
|
# 接着把未处理的加入到h_layouts里
|
180
225
|
for bboxes_in_block in unsplited_bboxes:
|
181
226
|
if len(bboxes_in_block) == 0:
|
182
227
|
continue
|
183
|
-
x0, y0, x1, y1 =
|
228
|
+
x0, y0, x1, y1 = (
|
229
|
+
bound_x0,
|
230
|
+
min([bbox[Y0_IDX] for bbox in bboxes_in_block]),
|
231
|
+
bound_x1,
|
232
|
+
max([bbox[Y1_IDX] for bbox in bboxes_in_block]),
|
233
|
+
)
|
184
234
|
h_layouts.append([x0, y0, x1, y1, LAYOUT_UNPROC])
|
185
|
-
|
186
|
-
h_layouts.sort(key=lambda x: x[1])
|
187
|
-
|
235
|
+
|
236
|
+
h_layouts.sort(key=lambda x: x[1]) # 按照y0排序, 也就是从上到下的顺序
|
188
237
|
"""
|
189
238
|
转换成如下格式返回
|
190
239
|
"""
|
191
240
|
for layout in h_layouts:
|
192
|
-
sorted_layout_blocks.append(
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
241
|
+
sorted_layout_blocks.append(
|
242
|
+
{
|
243
|
+
'layout_bbox': layout[:4],
|
244
|
+
'layout_label': layout[4],
|
245
|
+
'sub_layout': [],
|
246
|
+
}
|
247
|
+
)
|
197
248
|
return sorted_layout_blocks
|
198
|
-
|
249
|
+
|
250
|
+
|
199
251
|
###############################################################################################
|
200
252
|
#
|
201
253
|
# 垂直方向的处理
|
202
254
|
#
|
203
255
|
#
|
204
|
-
###############################################################################################
|
205
|
-
def _vertical_align_split_v1(bboxes:list,
|
256
|
+
###############################################################################################
|
257
|
+
def _vertical_align_split_v1(bboxes: list, boundary: tuple) -> list:
|
206
258
|
"""
|
207
259
|
计算垂直方向上的对齐, 并分割bboxes成layout。负责对一列多行的进行列维度分割。
|
208
260
|
如果不能完全分割,剩余部分作为layout_lable为u的layout返回
|
@@ -214,85 +266,124 @@ def _vertical_align_split_v1(bboxes:list, boundry:tuple)-> list:
|
|
214
266
|
-------------------------
|
215
267
|
此函数会将:以上布局将会切分出来2列
|
216
268
|
"""
|
217
|
-
sorted_layout_blocks = []
|
218
|
-
|
219
|
-
|
269
|
+
sorted_layout_blocks = [] # 这是要最终返回的值
|
270
|
+
new_boundary = [boundary[0], boundary[1], boundary[2], boundary[3]]
|
271
|
+
|
220
272
|
v_blocks = []
|
221
273
|
"""
|
222
274
|
先从左到右切分
|
223
275
|
"""
|
224
|
-
while True:
|
225
|
-
all_bboxes =
|
276
|
+
while True:
|
277
|
+
all_bboxes = get_bbox_in_boundary(bboxes, new_boundary)
|
226
278
|
left_edge_bboxes = get_left_edge_bboxes(all_bboxes)
|
227
279
|
if len(left_edge_bboxes) == 0:
|
228
280
|
break
|
229
|
-
right_split_line_x1 = max([bbox[X1_IDX] for bbox in left_edge_bboxes])+1
|
281
|
+
right_split_line_x1 = max([bbox[X1_IDX] for bbox in left_edge_bboxes]) + 1
|
230
282
|
# 然后检查这条线能不与其他bbox的左边界相交或者重合
|
231
|
-
if any(
|
283
|
+
if any(
|
284
|
+
[bbox[X0_IDX] <= right_split_line_x1 <= bbox[X1_IDX] for bbox in all_bboxes]
|
285
|
+
):
|
232
286
|
# 垂直切分线与某些box发生相交,说明无法完全垂直方向切分。
|
233
287
|
break
|
234
|
-
else:
|
288
|
+
else: # 说明成功分割出一列
|
235
289
|
# 找到左侧边界最靠左的bbox作为layout的x0
|
236
|
-
layout_x0 = min(
|
237
|
-
|
238
|
-
|
239
|
-
|
290
|
+
layout_x0 = min(
|
291
|
+
[bbox[X0_IDX] for bbox in left_edge_bboxes]
|
292
|
+
) # 这里主要是为了画出来有一定间距
|
293
|
+
v_blocks.append(
|
294
|
+
[
|
295
|
+
layout_x0,
|
296
|
+
new_boundary[1],
|
297
|
+
right_split_line_x1,
|
298
|
+
new_boundary[3],
|
299
|
+
LAYOUT_V,
|
300
|
+
]
|
301
|
+
)
|
302
|
+
new_boundary[0] = right_split_line_x1 # 更新边界
|
240
303
|
"""
|
241
304
|
再从右到左切, 此时如果还是无法完全切分,那么剩余部分作为layout_lable为u的layout返回
|
242
305
|
"""
|
243
306
|
unsplited_block = []
|
244
307
|
while True:
|
245
|
-
all_bboxes =
|
308
|
+
all_bboxes = get_bbox_in_boundary(bboxes, new_boundary)
|
246
309
|
right_edge_bboxes = get_right_edge_bboxes(all_bboxes)
|
247
310
|
if len(right_edge_bboxes) == 0:
|
248
311
|
break
|
249
|
-
left_split_line_x0 = min([bbox[X0_IDX] for bbox in right_edge_bboxes])-1
|
312
|
+
left_split_line_x0 = min([bbox[X0_IDX] for bbox in right_edge_bboxes]) - 1
|
250
313
|
# 然后检查这条线能不与其他bbox的左边界相交或者重合
|
251
|
-
if any(
|
314
|
+
if any(
|
315
|
+
[bbox[X0_IDX] <= left_split_line_x0 <= bbox[X1_IDX] for bbox in all_bboxes]
|
316
|
+
):
|
252
317
|
# 这里是余下的
|
253
|
-
unsplited_block.append(
|
318
|
+
unsplited_block.append(
|
319
|
+
[
|
320
|
+
new_boundary[0],
|
321
|
+
new_boundary[1],
|
322
|
+
new_boundary[2],
|
323
|
+
new_boundary[3],
|
324
|
+
LAYOUT_UNPROC,
|
325
|
+
]
|
326
|
+
)
|
254
327
|
break
|
255
328
|
else:
|
256
329
|
# 找到右侧边界最靠右的bbox作为layout的x1
|
257
330
|
layout_x1 = max([bbox[X1_IDX] for bbox in right_edge_bboxes])
|
258
|
-
v_blocks.append(
|
259
|
-
|
260
|
-
|
331
|
+
v_blocks.append(
|
332
|
+
[
|
333
|
+
left_split_line_x0,
|
334
|
+
new_boundary[1],
|
335
|
+
layout_x1,
|
336
|
+
new_boundary[3],
|
337
|
+
LAYOUT_V,
|
338
|
+
]
|
339
|
+
)
|
340
|
+
new_boundary[2] = left_split_line_x0 # 更新右边界
|
261
341
|
"""
|
262
342
|
最后拼装成layout格式返回
|
263
343
|
"""
|
264
344
|
for block in v_blocks:
|
265
|
-
sorted_layout_blocks.append(
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
345
|
+
sorted_layout_blocks.append(
|
346
|
+
{
|
347
|
+
'layout_bbox': block[:4],
|
348
|
+
'layout_label': block[4],
|
349
|
+
'sub_layout': [],
|
350
|
+
}
|
351
|
+
)
|
270
352
|
for block in unsplited_block:
|
271
|
-
sorted_layout_blocks.append(
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
353
|
+
sorted_layout_blocks.append(
|
354
|
+
{
|
355
|
+
'layout_bbox': block[:4],
|
356
|
+
'layout_label': block[4],
|
357
|
+
'sub_layout': [],
|
358
|
+
}
|
359
|
+
)
|
360
|
+
|
277
361
|
# 按照x0排序
|
278
362
|
sorted_layout_blocks.sort(key=lambda x: x['layout_bbox'][0])
|
279
363
|
return sorted_layout_blocks
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
改进的
|
284
|
-
|
285
|
-
"""
|
286
|
-
sorted_layout_blocks = []
|
287
|
-
|
288
|
-
bad_boxes = []
|
364
|
+
|
365
|
+
|
366
|
+
def _vertical_align_split_v2(bboxes: list, boundary: tuple) -> list:
|
367
|
+
"""改进的
|
368
|
+
_vertical_align_split算法,原算法会因为第二列的box由于左侧没有遮挡被认为是左侧的一部分,导致整个layout多列被识别为一列。
|
369
|
+
利用从左上角的box开始向下看的方法,不断扩展w_x0, w_x1,直到不能继续向下扩展,或者到达边界下边界。"""
|
370
|
+
sorted_layout_blocks = [] # 这是要最终返回的值
|
371
|
+
new_boundary = [boundary[0], boundary[1], boundary[2], boundary[3]]
|
372
|
+
bad_boxes = [] # 被割中的box
|
289
373
|
v_blocks = []
|
290
374
|
while True:
|
291
|
-
all_bboxes =
|
375
|
+
all_bboxes = get_bbox_in_boundary(bboxes, new_boundary)
|
292
376
|
if len(all_bboxes) == 0:
|
293
377
|
break
|
294
|
-
left_top_box = min(
|
295
|
-
|
378
|
+
left_top_box = min(
|
379
|
+
all_bboxes, key=lambda x: (x[X0_IDX], x[Y0_IDX])
|
380
|
+
) # 这里应该加强,检查一下必须是在第一列的 TODO
|
381
|
+
start_box = [
|
382
|
+
left_top_box[X0_IDX],
|
383
|
+
left_top_box[Y0_IDX],
|
384
|
+
left_top_box[X1_IDX],
|
385
|
+
left_top_box[Y1_IDX],
|
386
|
+
]
|
296
387
|
w_x0, w_x1 = left_top_box[X0_IDX], left_top_box[X1_IDX]
|
297
388
|
"""
|
298
389
|
然后沿着这个box线向下找最近的那个box, 然后扩展w_x0, w_x1
|
@@ -301,98 +392,140 @@ def _vertical_align_split_v2(bboxes:list, boundry:tuple)-> list:
|
|
301
392
|
1. 达到,那么更新左边界继续分下一个列
|
302
393
|
2. 没有达到,那么此时开始从右侧切分进入下面的循环里
|
303
394
|
"""
|
304
|
-
while left_top_box is not None:
|
395
|
+
while left_top_box is not None: # 向下去找
|
305
396
|
virtual_box = [w_x0, left_top_box[Y0_IDX], w_x1, left_top_box[Y1_IDX]]
|
306
|
-
left_top_box = find_bottom_bbox_direct_from_left_edge(
|
397
|
+
left_top_box = find_bottom_bbox_direct_from_left_edge(
|
398
|
+
virtual_box, all_bboxes
|
399
|
+
)
|
307
400
|
if left_top_box:
|
308
|
-
w_x0, w_x1 = min(virtual_box[X0_IDX], left_top_box[X0_IDX]), max(
|
401
|
+
w_x0, w_x1 = min(virtual_box[X0_IDX], left_top_box[X0_IDX]), max(
|
402
|
+
[virtual_box[X1_IDX], left_top_box[X1_IDX]]
|
403
|
+
)
|
309
404
|
# 万一这个初始的box在column中间,那么还要向上看
|
310
|
-
start_box = [
|
405
|
+
start_box = [
|
406
|
+
w_x0,
|
407
|
+
start_box[Y0_IDX],
|
408
|
+
w_x1,
|
409
|
+
start_box[Y1_IDX],
|
410
|
+
] # 扩展一下宽度更鲁棒
|
311
411
|
left_top_box = find_top_bbox_direct_from_left_edge(start_box, all_bboxes)
|
312
|
-
while left_top_box is not None:
|
412
|
+
while left_top_box is not None: # 向上去找
|
313
413
|
virtual_box = [w_x0, left_top_box[Y0_IDX], w_x1, left_top_box[Y1_IDX]]
|
314
414
|
left_top_box = find_top_bbox_direct_from_left_edge(virtual_box, all_bboxes)
|
315
415
|
if left_top_box:
|
316
|
-
w_x0, w_x1 = min(virtual_box[X0_IDX], left_top_box[X0_IDX]), max(
|
317
|
-
|
318
|
-
|
319
|
-
|
416
|
+
w_x0, w_x1 = min(virtual_box[X0_IDX], left_top_box[X0_IDX]), max(
|
417
|
+
[virtual_box[X1_IDX], left_top_box[X1_IDX]]
|
418
|
+
)
|
419
|
+
|
420
|
+
# 检查相交
|
421
|
+
if any([bbox[X0_IDX] <= w_x1 + 1 <= bbox[X1_IDX] for bbox in all_bboxes]):
|
320
422
|
for b in all_bboxes:
|
321
|
-
if b[X0_IDX] <= w_x1+1 <= b[X1_IDX]:
|
423
|
+
if b[X0_IDX] <= w_x1 + 1 <= b[X1_IDX]:
|
322
424
|
bad_boxes.append([b[X0_IDX], b[Y0_IDX], b[X1_IDX], b[Y1_IDX]])
|
323
425
|
break
|
324
|
-
else:
|
325
|
-
v_blocks.append([w_x0,
|
326
|
-
|
327
|
-
|
426
|
+
else: # 说明成功分割出一列
|
427
|
+
v_blocks.append([w_x0, new_boundary[1], w_x1, new_boundary[3], LAYOUT_V])
|
428
|
+
new_boundary[0] = w_x1 # 更新边界
|
328
429
|
"""
|
329
430
|
接着开始从右上角的box扫描
|
330
431
|
"""
|
331
|
-
w_x0
|
432
|
+
w_x0, w_x1 = 0, 0
|
332
433
|
unsplited_block = []
|
333
434
|
while True:
|
334
|
-
all_bboxes =
|
435
|
+
all_bboxes = get_bbox_in_boundary(bboxes, new_boundary)
|
335
436
|
if len(all_bboxes) == 0:
|
336
437
|
break
|
337
438
|
# 先找到X1最大的
|
338
|
-
bbox_list_sorted = sorted(
|
439
|
+
bbox_list_sorted = sorted(
|
440
|
+
all_bboxes, key=lambda bbox: bbox[X1_IDX], reverse=True
|
441
|
+
)
|
339
442
|
# Then, find the boxes with the smallest Y0 value
|
340
443
|
bigest_x1 = bbox_list_sorted[0][X1_IDX]
|
341
|
-
boxes_with_bigest_x1 = [
|
342
|
-
|
343
|
-
|
444
|
+
boxes_with_bigest_x1 = [
|
445
|
+
bbox for bbox in bbox_list_sorted if bbox[X1_IDX] == bigest_x1
|
446
|
+
] # 也就是最靠右的那些
|
447
|
+
right_top_box = min(
|
448
|
+
boxes_with_bigest_x1, key=lambda bbox: bbox[Y0_IDX]
|
449
|
+
) # y0最小的那个
|
450
|
+
start_box = [
|
451
|
+
right_top_box[X0_IDX],
|
452
|
+
right_top_box[Y0_IDX],
|
453
|
+
right_top_box[X1_IDX],
|
454
|
+
right_top_box[Y1_IDX],
|
455
|
+
]
|
344
456
|
w_x0, w_x1 = right_top_box[X0_IDX], right_top_box[X1_IDX]
|
345
|
-
|
457
|
+
|
346
458
|
while right_top_box is not None:
|
347
459
|
virtual_box = [w_x0, right_top_box[Y0_IDX], w_x1, right_top_box[Y1_IDX]]
|
348
|
-
right_top_box = find_bottom_bbox_direct_from_right_edge(
|
460
|
+
right_top_box = find_bottom_bbox_direct_from_right_edge(
|
461
|
+
virtual_box, all_bboxes
|
462
|
+
)
|
349
463
|
if right_top_box:
|
350
|
-
w_x0, w_x1 = min([w_x0, right_top_box[X0_IDX]]), max(
|
464
|
+
w_x0, w_x1 = min([w_x0, right_top_box[X0_IDX]]), max(
|
465
|
+
[w_x1, right_top_box[X1_IDX]]
|
466
|
+
)
|
351
467
|
# 在向上扫描
|
352
|
-
start_box = [
|
468
|
+
start_box = [
|
469
|
+
w_x0,
|
470
|
+
start_box[Y0_IDX],
|
471
|
+
w_x1,
|
472
|
+
start_box[Y1_IDX],
|
473
|
+
] # 扩展一下宽度更鲁棒
|
353
474
|
right_top_box = find_top_bbox_direct_from_right_edge(start_box, all_bboxes)
|
354
475
|
while right_top_box is not None:
|
355
476
|
virtual_box = [w_x0, right_top_box[Y0_IDX], w_x1, right_top_box[Y1_IDX]]
|
356
|
-
right_top_box = find_top_bbox_direct_from_right_edge(
|
477
|
+
right_top_box = find_top_bbox_direct_from_right_edge(
|
478
|
+
virtual_box, all_bboxes
|
479
|
+
)
|
357
480
|
if right_top_box:
|
358
|
-
w_x0, w_x1 = min([w_x0, right_top_box[X0_IDX]]), max(
|
359
|
-
|
481
|
+
w_x0, w_x1 = min([w_x0, right_top_box[X0_IDX]]), max(
|
482
|
+
[w_x1, right_top_box[X1_IDX]]
|
483
|
+
)
|
484
|
+
|
360
485
|
# 检查是否与其他box相交, 垂直切分线与某些box发生相交,说明无法完全垂直方向切分。
|
361
|
-
if any([bbox[X0_IDX] <= w_x0-1 <= bbox[X1_IDX] for bbox in all_bboxes]):
|
362
|
-
unsplited_block.append(
|
486
|
+
if any([bbox[X0_IDX] <= w_x0 - 1 <= bbox[X1_IDX] for bbox in all_bboxes]):
|
487
|
+
unsplited_block.append(
|
488
|
+
[
|
489
|
+
new_boundary[0],
|
490
|
+
new_boundary[1],
|
491
|
+
new_boundary[2],
|
492
|
+
new_boundary[3],
|
493
|
+
LAYOUT_UNPROC,
|
494
|
+
]
|
495
|
+
)
|
363
496
|
for b in all_bboxes:
|
364
|
-
if b[X0_IDX] <= w_x0-1 <= b[X1_IDX]:
|
497
|
+
if b[X0_IDX] <= w_x0 - 1 <= b[X1_IDX]:
|
365
498
|
bad_boxes.append([b[X0_IDX], b[Y0_IDX], b[X1_IDX], b[Y1_IDX]])
|
366
499
|
break
|
367
|
-
else:
|
368
|
-
v_blocks.append([w_x0,
|
369
|
-
|
370
|
-
|
500
|
+
else: # 说明成功分割出一列
|
501
|
+
v_blocks.append([w_x0, new_boundary[1], w_x1, new_boundary[3], LAYOUT_V])
|
502
|
+
new_boundary[2] = w_x0
|
371
503
|
"""转换数据结构"""
|
372
504
|
for block in v_blocks:
|
373
|
-
sorted_layout_blocks.append(
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
505
|
+
sorted_layout_blocks.append(
|
506
|
+
{
|
507
|
+
'layout_bbox': block[:4],
|
508
|
+
'layout_label': block[4],
|
509
|
+
'sub_layout': [],
|
510
|
+
}
|
511
|
+
)
|
512
|
+
|
379
513
|
for block in unsplited_block:
|
380
|
-
sorted_layout_blocks.append(
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
514
|
+
sorted_layout_blocks.append(
|
515
|
+
{
|
516
|
+
'layout_bbox': block[:4],
|
517
|
+
'layout_label': block[4],
|
518
|
+
'sub_layout': [],
|
519
|
+
'bad_boxes': bad_boxes, # 记录下来,这个box是被割中的
|
520
|
+
}
|
521
|
+
)
|
522
|
+
|
388
523
|
# 按照x0排序
|
389
524
|
sorted_layout_blocks.sort(key=lambda x: x['layout_bbox'][0])
|
390
525
|
return sorted_layout_blocks
|
391
|
-
|
392
|
-
|
393
526
|
|
394
527
|
|
395
|
-
def _try_horizontal_mult_column_split(bboxes:list,
|
528
|
+
def _try_horizontal_mult_column_split(bboxes: list, boundary: tuple) -> list:
|
396
529
|
"""
|
397
530
|
尝试水平切分,如果切分不动,那就当一个BAD_LAYOUT返回
|
398
531
|
------------------
|
@@ -406,51 +539,58 @@ def _try_horizontal_mult_column_split(bboxes:list, boundry:tuple)-> list:
|
|
406
539
|
pass
|
407
540
|
|
408
541
|
|
409
|
-
|
410
|
-
|
411
|
-
def _vertical_split(bboxes:list, boundry:tuple)-> list:
|
542
|
+
def _vertical_split(bboxes: list, boundary: tuple) -> list:
|
412
543
|
"""
|
413
544
|
从垂直方向进行切割,分block
|
414
545
|
这个版本里,如果垂直切分不动,那就当一个BAD_LAYOUT返回
|
415
|
-
|
546
|
+
|
416
547
|
--------------------------
|
417
548
|
| | |
|
418
549
|
| | |
|
419
550
|
| |
|
420
|
-
这种列是此函数要切分的 -> | |
|
551
|
+
这种列是此函数要切分的 -> | |
|
421
552
|
| |
|
422
553
|
| | |
|
423
554
|
| | |
|
424
555
|
-------------------------
|
425
556
|
"""
|
426
|
-
sorted_layout_blocks = []
|
427
|
-
|
428
|
-
bound_x0, bound_y0, bound_x1, bound_y1 =
|
429
|
-
all_bboxes =
|
557
|
+
sorted_layout_blocks = [] # 这是要最终返回的值
|
558
|
+
|
559
|
+
bound_x0, bound_y0, bound_x1, bound_y1 = boundary
|
560
|
+
all_bboxes = get_bbox_in_boundary(bboxes, boundary)
|
430
561
|
"""
|
431
562
|
all_bboxes = fix_vertical_bbox_pos(all_bboxes) # 垂直方向解覆盖
|
432
563
|
all_bboxes = fix_hor_bbox_pos(all_bboxes) # 水平解覆盖
|
433
|
-
|
564
|
+
|
434
565
|
这两行代码目前先不执行,因为公式检测,表格检测还不是很成熟,导致非常多的textblock参与了运算,时间消耗太大。
|
435
566
|
这两行代码的作用是:
|
436
567
|
如果遇到互相重叠的bbox, 那么会把面积较小的box进行压缩,从而避免重叠。对布局切分来说带来正反馈。
|
437
568
|
"""
|
438
|
-
|
439
|
-
#all_bboxes = paper_bbox_sort(all_bboxes, abs(bound_x1-bound_x0), abs(bound_y1-bound_x0)) # 大致拍下序, 这个是基于直接遮挡的。
|
569
|
+
|
570
|
+
# all_bboxes = paper_bbox_sort(all_bboxes, abs(bound_x1-bound_x0), abs(bound_y1-bound_x0)) # 大致拍下序, 这个是基于直接遮挡的。
|
440
571
|
"""
|
441
572
|
首先在垂直方向上扩展独占一行的bbox
|
442
|
-
|
573
|
+
|
443
574
|
"""
|
444
575
|
for bbox in all_bboxes:
|
445
|
-
top_nearest_bbox = find_all_top_bbox_direct(bbox, all_bboxes)
|
576
|
+
top_nearest_bbox = find_all_top_bbox_direct(bbox, all_bboxes) # 非扩展线
|
446
577
|
bottom_nearest_bbox = find_all_bottom_bbox_direct(bbox, all_bboxes)
|
447
|
-
if
|
578
|
+
if (
|
579
|
+
top_nearest_bbox is None
|
580
|
+
and bottom_nearest_bbox is None
|
581
|
+
and not any(
|
582
|
+
[
|
583
|
+
b[X0_IDX] < bbox[X1_IDX] < b[X1_IDX]
|
584
|
+
or b[X0_IDX] < bbox[X0_IDX] < b[X1_IDX]
|
585
|
+
for b in all_bboxes
|
586
|
+
]
|
587
|
+
)
|
588
|
+
): # 独占一列, 且不和其他重叠
|
448
589
|
bbox[X0_EXT_IDX] = bbox[X0_IDX]
|
449
590
|
bbox[Y0_EXT_IDX] = bound_y0
|
450
591
|
bbox[X1_EXT_IDX] = bbox[X1_IDX]
|
451
592
|
bbox[Y1_EXT_IDX] = bound_y1
|
452
|
-
|
453
|
-
"""
|
593
|
+
"""
|
454
594
|
此时独占一列的被成功扩展到指定的边界上,这个时候利用边界条件合并连续的bbox,成为一个group
|
455
595
|
然后合并所有连续垂直方向的bbox.
|
456
596
|
"""
|
@@ -458,20 +598,23 @@ def _vertical_split(bboxes:list, boundry:tuple)-> list:
|
|
458
598
|
# fix: 这里水平方向的列不要合并成一个行,因为需要保证返回给下游的最小block,总是可以无脑从上到下阅读文字。
|
459
599
|
v_bboxes = []
|
460
600
|
for box in all_bboxes:
|
461
|
-
if box[Y0_EXT_IDX] == bound_y0 and box[Y1_EXT_IDX] == bound_y1:
|
601
|
+
if box[Y0_EXT_IDX] == bound_y0 and box[Y1_EXT_IDX] == bound_y1:
|
462
602
|
v_bboxes.append(box)
|
463
|
-
|
464
603
|
"""
|
465
604
|
现在v_bboxes里面是所有的group了,每个group都是一个list
|
466
605
|
对v_bboxes里的每个group进行计算放回到sorted_layouts里
|
467
606
|
"""
|
468
607
|
v_layouts = []
|
469
608
|
for vbox in v_bboxes:
|
470
|
-
#gp.sort(key=lambda x: x[X0_IDX])
|
609
|
+
# gp.sort(key=lambda x: x[X0_IDX])
|
471
610
|
# 然后计算这个group的layout_bbox,也就是最小的x0,y0, 最大的x1,y1
|
472
|
-
x0, y0, x1, y1 =
|
473
|
-
|
474
|
-
|
611
|
+
x0, y0, x1, y1 = (
|
612
|
+
vbox[X0_EXT_IDX],
|
613
|
+
vbox[Y0_EXT_IDX],
|
614
|
+
vbox[X1_EXT_IDX],
|
615
|
+
vbox[Y1_EXT_IDX],
|
616
|
+
)
|
617
|
+
v_layouts.append([x0, y0, x1, y1, LAYOUT_V]) # 垂直的布局
|
475
618
|
"""
|
476
619
|
接下来利用这些连续的垂直bbox的layout_bbox的x0, x1,从垂直上切分开其余的为几个部分
|
477
620
|
"""
|
@@ -481,29 +624,41 @@ def _vertical_split(bboxes:list, boundry:tuple)-> list:
|
|
481
624
|
v_split_lines.append(x0)
|
482
625
|
v_split_lines.append(x1)
|
483
626
|
v_split_lines.append(bound_x1)
|
484
|
-
|
627
|
+
|
485
628
|
unsplited_bboxes = []
|
486
629
|
for i in range(0, len(v_split_lines), 2):
|
487
|
-
start_x0, start_x1 = v_split_lines[i:i+2]
|
630
|
+
start_x0, start_x1 = v_split_lines[i : i + 2]
|
488
631
|
# 然后找出[start_x0, start_x1]之间的其他bbox,这些组成一个未分割板块
|
489
|
-
bboxes_in_block = [
|
632
|
+
bboxes_in_block = [
|
633
|
+
bbox
|
634
|
+
for bbox in all_bboxes
|
635
|
+
if bbox[X0_IDX] >= start_x0 and bbox[X1_IDX] <= start_x1
|
636
|
+
]
|
490
637
|
unsplited_bboxes.append(bboxes_in_block)
|
491
638
|
# 接着把未处理的加入到v_layouts里
|
492
639
|
for bboxes_in_block in unsplited_bboxes:
|
493
640
|
if len(bboxes_in_block) == 0:
|
494
641
|
continue
|
495
|
-
x0, y0, x1, y1 =
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
642
|
+
x0, y0, x1, y1 = (
|
643
|
+
min([bbox[X0_IDX] for bbox in bboxes_in_block]),
|
644
|
+
bound_y0,
|
645
|
+
max([bbox[X1_IDX] for bbox in bboxes_in_block]),
|
646
|
+
bound_y1,
|
647
|
+
)
|
648
|
+
v_layouts.append(
|
649
|
+
[x0, y0, x1, y1, LAYOUT_UNPROC]
|
650
|
+
) # 说明这篇区域未能够分析出可靠的版面
|
651
|
+
|
652
|
+
v_layouts.sort(key=lambda x: x[0]) # 按照x0排序, 也就是从左到右的顺序
|
653
|
+
|
500
654
|
for layout in v_layouts:
|
501
|
-
sorted_layout_blocks.append(
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
655
|
+
sorted_layout_blocks.append(
|
656
|
+
{
|
657
|
+
'layout_bbox': layout[:4],
|
658
|
+
'layout_label': layout[4],
|
659
|
+
'sub_layout': [],
|
660
|
+
}
|
661
|
+
)
|
507
662
|
"""
|
508
663
|
至此,垂直方向切成了2种类型,其一是独占一列的,其二是未处理的。
|
509
664
|
下面对这些未处理的进行垂直方向切分,这个切分要切出来类似“吕”这种类型的垂直方向的布局
|
@@ -513,24 +668,32 @@ def _vertical_split(bboxes:list, boundry:tuple)-> list:
|
|
513
668
|
x0, y0, x1, y1 = layout['layout_bbox']
|
514
669
|
v_split_layouts = _vertical_align_split_v2(bboxes, [x0, y0, x1, y1])
|
515
670
|
sorted_layout_blocks[i] = {
|
516
|
-
|
517
|
-
|
518
|
-
|
671
|
+
'layout_bbox': [x0, y0, x1, y1],
|
672
|
+
'layout_label': LAYOUT_H,
|
673
|
+
'sub_layout': v_split_layouts,
|
519
674
|
}
|
520
|
-
layout['layout_label'] = LAYOUT_H
|
521
|
-
|
675
|
+
layout['layout_label'] = LAYOUT_H # 被垂线切分成了水平布局
|
676
|
+
|
522
677
|
return sorted_layout_blocks
|
523
|
-
|
524
678
|
|
525
|
-
|
679
|
+
|
680
|
+
def split_layout(bboxes: list, boundary: tuple, page_num: int) -> list:
|
526
681
|
"""
|
527
682
|
把bboxes切割成layout
|
528
683
|
return:
|
529
684
|
[
|
530
685
|
{
|
531
|
-
"layout_bbox": [x0,
|
686
|
+
"layout_bbox": [x0,y0,x1,y1],
|
532
687
|
"layout_label":"u|v|h|b", 未处理|垂直|水平|BAD_LAYOUT
|
533
|
-
"sub_layout":
|
688
|
+
"sub_layout":[] #每个元素都是[
|
689
|
+
x0,y0,
|
690
|
+
x1,y1,
|
691
|
+
block_content,
|
692
|
+
idx_x,idx_y,
|
693
|
+
content_type,
|
694
|
+
ext_x0,ext_y0,
|
695
|
+
ext_x1,ext_y1
|
696
|
+
], 并且顺序就是阅读顺序
|
534
697
|
}
|
535
698
|
]
|
536
699
|
example:
|
@@ -539,7 +702,7 @@ def split_layout(bboxes:list, boundry:tuple, page_num:int)-> list:
|
|
539
702
|
"layout_bbox": [0, 0, 100, 100],
|
540
703
|
"layout_label":"u|v|h|b",
|
541
704
|
"sub_layout":[
|
542
|
-
|
705
|
+
|
543
706
|
]
|
544
707
|
},
|
545
708
|
{
|
@@ -559,35 +722,35 @@ def split_layout(bboxes:list, boundry:tuple, page_num:int)-> list:
|
|
559
722
|
"layout_bbox": [0, 0, 100, 100],
|
560
723
|
"layout_label":"u|v|h|b",
|
561
724
|
"sub_layout":[
|
562
|
-
|
725
|
+
|
563
726
|
]
|
564
727
|
}
|
565
728
|
}
|
566
|
-
]
|
729
|
+
]
|
567
730
|
"""
|
568
|
-
sorted_layouts = []
|
569
|
-
|
570
|
-
|
571
|
-
if len(bboxes) <=1:
|
731
|
+
sorted_layouts = [] # 最终返回的结果
|
732
|
+
|
733
|
+
boundary_x0, boundary_y0, boundary_x1, boundary_y1 = boundary
|
734
|
+
if len(bboxes) <= 1:
|
572
735
|
return [
|
573
736
|
{
|
574
|
-
|
575
|
-
|
576
|
-
|
737
|
+
'layout_bbox': [boundary_x0, boundary_y0, boundary_x1, boundary_y1],
|
738
|
+
'layout_label': LAYOUT_V,
|
739
|
+
'sub_layout': [],
|
577
740
|
}
|
578
741
|
]
|
579
|
-
|
580
742
|
"""
|
581
743
|
接下来按照先水平后垂直的顺序进行切分
|
582
744
|
"""
|
583
|
-
bboxes = paper_bbox_sort(
|
584
|
-
|
745
|
+
bboxes = paper_bbox_sort(
|
746
|
+
bboxes, boundary_x1 - boundary_x0, boundary_y1 - boundary_y0
|
747
|
+
)
|
748
|
+
sorted_layouts = _horizontal_split(bboxes, boundary) # 通过水平分割出来的layout
|
585
749
|
for i, layout in enumerate(sorted_layouts):
|
586
750
|
x0, y0, x1, y1 = layout['layout_bbox']
|
587
751
|
layout_type = layout['layout_label']
|
588
|
-
if layout_type == LAYOUT_UNPROC:
|
752
|
+
if layout_type == LAYOUT_UNPROC: # 说明是非独占单行的,这些需要垂直切分
|
589
753
|
v_split_layouts = _vertical_split(bboxes, [x0, y0, x1, y1])
|
590
|
-
|
591
754
|
"""
|
592
755
|
最后这里有个逻辑问题:如果这个函数只分离出来了一个column layout,那么这个layout分割肯定超出了算法能力范围。因为我们假定的是传进来的
|
593
756
|
box已经把行全部剥离了,所以这里必须十多个列才可以。如果只剥离出来一个layout,并且是多个box,那么就说明这个layout是无法分割的,标记为LAYOUT_UNPROC
|
@@ -596,28 +759,26 @@ def split_layout(bboxes:list, boundry:tuple, page_num:int)-> list:
|
|
596
759
|
if len(v_split_layouts) == 1:
|
597
760
|
if len(v_split_layouts[0]['sub_layout']) == 0:
|
598
761
|
layout_label = LAYOUT_UNPROC
|
599
|
-
#logger.warning(f"WARNING: pageno={page_num}, 无法分割的layout: ", v_split_layouts)
|
600
|
-
|
762
|
+
# logger.warning(f"WARNING: pageno={page_num}, 无法分割的layout: ", v_split_layouts)
|
601
763
|
"""
|
602
764
|
组合起来最终的layout
|
603
765
|
"""
|
604
766
|
sorted_layouts[i] = {
|
605
|
-
|
606
|
-
|
607
|
-
|
767
|
+
'layout_bbox': [x0, y0, x1, y1],
|
768
|
+
'layout_label': layout_label,
|
769
|
+
'sub_layout': v_split_layouts,
|
608
770
|
}
|
609
771
|
layout['layout_label'] = LAYOUT_H
|
610
|
-
|
611
772
|
"""
|
612
773
|
水平和垂直方向都切分完毕了。此时还有一些未处理的,这些未处理的可能是因为水平和垂直方向都无法切分。
|
613
774
|
这些最后调用_try_horizontal_mult_block_split做一次水平多个block的联合切分,如果也不能切分最终就当做BAD_LAYOUT返回
|
614
775
|
"""
|
615
776
|
# TODO
|
616
|
-
|
777
|
+
|
617
778
|
return sorted_layouts
|
618
779
|
|
619
780
|
|
620
|
-
def get_bboxes_layout(all_boxes:list,
|
781
|
+
def get_bboxes_layout(all_boxes: list, boundary: tuple, page_id: int):
|
621
782
|
"""
|
622
783
|
对利用layout排序之后的box,进行排序
|
623
784
|
return:
|
@@ -628,10 +789,10 @@ def get_bboxes_layout(all_boxes:list, boundry:tuple, page_id:int):
|
|
628
789
|
},
|
629
790
|
]
|
630
791
|
"""
|
792
|
+
|
631
793
|
def _preorder_traversal(layout):
|
632
|
-
"""
|
633
|
-
|
634
|
-
"""
|
794
|
+
"""对sorted_layouts的叶子节点,也就是len(sub_layout)==0的节点进行排序。排序按照前序遍历的顺序,也就是从上到
|
795
|
+
下,从左到右的顺序."""
|
635
796
|
sorted_layout_blocks = []
|
636
797
|
for layout in layout:
|
637
798
|
sub_layout = layout['sub_layout']
|
@@ -641,71 +802,89 @@ def get_bboxes_layout(all_boxes:list, boundry:tuple, page_id:int):
|
|
641
802
|
s = _preorder_traversal(sub_layout)
|
642
803
|
sorted_layout_blocks.extend(s)
|
643
804
|
return sorted_layout_blocks
|
805
|
+
|
644
806
|
# -------------------------------------------------------------------------------------------------------------------------
|
645
|
-
sorted_layouts = split_layout(
|
646
|
-
|
807
|
+
sorted_layouts = split_layout(
|
808
|
+
all_boxes, boundary, page_id
|
809
|
+
) # 先切分成layout,得到一个Tree
|
810
|
+
total_sorted_layout_blocks = _preorder_traversal(sorted_layouts)
|
647
811
|
return total_sorted_layout_blocks, sorted_layouts
|
648
812
|
|
649
813
|
|
650
814
|
def get_columns_cnt_of_layout(layout_tree):
|
651
|
-
"""
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
for items in layout_tree: # 针对每一层(横切)计算列数,横着的算一列
|
815
|
+
"""获取一个layout的宽度."""
|
816
|
+
max_width_list = [0] # 初始化一个元素,防止max,min函数报错
|
817
|
+
|
818
|
+
for items in layout_tree: # 针对每一层(横切)计算列数,横着的算一列
|
657
819
|
layout_type = items['layout_label']
|
658
820
|
sub_layouts = items['sub_layout']
|
659
|
-
if len(sub_layouts)==0:
|
821
|
+
if len(sub_layouts) == 0:
|
660
822
|
max_width_list.append(1)
|
661
823
|
else:
|
662
824
|
if layout_type == LAYOUT_H:
|
663
825
|
max_width_list.append(1)
|
664
826
|
else:
|
665
827
|
width = 0
|
666
|
-
for
|
667
|
-
if len(
|
828
|
+
for sub_layout in sub_layouts:
|
829
|
+
if len(sub_layout['sub_layout']) == 0:
|
668
830
|
width += 1
|
669
831
|
else:
|
670
|
-
for lay in
|
832
|
+
for lay in sub_layout['sub_layout']:
|
671
833
|
width += get_columns_cnt_of_layout([lay])
|
672
834
|
max_width_list.append(width)
|
673
|
-
|
835
|
+
|
674
836
|
return max(max_width_list)
|
675
837
|
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
输入是一个bbox的list.
|
838
|
+
|
839
|
+
def sort_with_layout(bboxes: list, page_width, page_height) -> (list, list):
|
840
|
+
"""输入是一个bbox的list.
|
841
|
+
|
681
842
|
获取到输入之后,先进行layout切分,然后对这些bbox进行排序。返回排序后的bboxes
|
682
843
|
"""
|
683
844
|
|
684
845
|
new_bboxes = []
|
685
846
|
for box in bboxes:
|
686
847
|
# new_bboxes.append([box[0], box[1], box[2], box[3], None, None, None, 'text', None, None, None, None])
|
687
|
-
new_bboxes.append(
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
848
|
+
new_bboxes.append(
|
849
|
+
[
|
850
|
+
box[0],
|
851
|
+
box[1],
|
852
|
+
box[2],
|
853
|
+
box[3],
|
854
|
+
None,
|
855
|
+
None,
|
856
|
+
None,
|
857
|
+
'text',
|
858
|
+
None,
|
859
|
+
None,
|
860
|
+
None,
|
861
|
+
None,
|
862
|
+
box[4],
|
863
|
+
]
|
864
|
+
)
|
865
|
+
|
866
|
+
layout_bboxes, _ = get_bboxes_layout(
|
867
|
+
new_bboxes, tuple([0, 0, page_width, page_height]), 0
|
868
|
+
)
|
869
|
+
if any([lay['layout_label'] == LAYOUT_UNPROC for lay in layout_bboxes]):
|
870
|
+
logger.warning('drop this pdf, reason: 复杂版面')
|
871
|
+
return None, None
|
872
|
+
|
873
|
+
sorted_bboxes = []
|
695
874
|
# 利用layout bbox每次框定一些box,然后排序
|
696
875
|
for layout in layout_bboxes:
|
697
876
|
lbox = layout['layout_bbox']
|
698
|
-
bbox_in_layout =
|
699
|
-
sorted_bbox = paper_bbox_sort(
|
877
|
+
bbox_in_layout = get_bbox_in_boundary(new_bboxes, lbox)
|
878
|
+
sorted_bbox = paper_bbox_sort(
|
879
|
+
bbox_in_layout, lbox[2] - lbox[0], lbox[3] - lbox[1]
|
880
|
+
)
|
700
881
|
sorted_bboxes.extend(sorted_bbox)
|
701
|
-
|
882
|
+
|
702
883
|
return sorted_bboxes, layout_bboxes
|
703
884
|
|
704
885
|
|
705
886
|
def sort_text_block(text_block, layout_bboxes):
|
706
|
-
"""
|
707
|
-
对一页的text_block进行排序
|
708
|
-
"""
|
887
|
+
"""对一页的text_block进行排序."""
|
709
888
|
sorted_text_bbox = []
|
710
889
|
all_text_bbox = []
|
711
890
|
# 做一个box=>text的映射
|
@@ -714,19 +893,29 @@ def sort_text_block(text_block, layout_bboxes):
|
|
714
893
|
box = blk['bbox']
|
715
894
|
box_to_text[(box[0], box[1], box[2], box[3])] = blk
|
716
895
|
all_text_bbox.append(box)
|
717
|
-
|
896
|
+
|
718
897
|
# text_blocks_to_sort = []
|
719
898
|
# for box in box_to_text.keys():
|
720
899
|
# text_blocks_to_sort.append([box[0], box[1], box[2], box[3], None, None, None, 'text', None, None, None, None])
|
721
|
-
|
900
|
+
|
722
901
|
# 按照layout_bboxes的顺序,对text_block进行排序
|
723
902
|
for layout in layout_bboxes:
|
724
903
|
layout_box = layout['layout_bbox']
|
725
|
-
text_bbox_in_layout =
|
726
|
-
|
727
|
-
|
728
|
-
|
904
|
+
text_bbox_in_layout = get_bbox_in_boundary(
|
905
|
+
all_text_bbox,
|
906
|
+
[
|
907
|
+
layout_box[0] - 1,
|
908
|
+
layout_box[1] - 1,
|
909
|
+
layout_box[2] + 1,
|
910
|
+
layout_box[3] + 1,
|
911
|
+
],
|
912
|
+
)
|
913
|
+
# sorted_bbox = paper_bbox_sort(text_bbox_in_layout, layout_box[2]-layout_box[0], layout_box[3]-layout_box[1])
|
914
|
+
text_bbox_in_layout.sort(
|
915
|
+
key=lambda x: x[1]
|
916
|
+
) # 一个layout内部的box,按照y0自上而下排序
|
917
|
+
# sorted_bbox = [[b] for b in text_blocks_to_sort]
|
729
918
|
for sb in text_bbox_in_layout:
|
730
919
|
sorted_text_bbox.append(box_to_text[(sb[0], sb[1], sb[2], sb[3])])
|
731
|
-
|
920
|
+
|
732
921
|
return sorted_text_bbox
|