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.
Files changed (34) hide show
  1. magic_pdf/dict2md/ocr_mkcontent.py +130 -76
  2. magic_pdf/integrations/__init__.py +0 -0
  3. magic_pdf/integrations/rag/__init__.py +0 -0
  4. magic_pdf/integrations/rag/api.py +82 -0
  5. magic_pdf/integrations/rag/type.py +82 -0
  6. magic_pdf/integrations/rag/utils.py +285 -0
  7. magic_pdf/layout/layout_sort.py +472 -283
  8. magic_pdf/libs/boxbase.py +188 -149
  9. magic_pdf/libs/draw_bbox.py +113 -87
  10. magic_pdf/libs/ocr_content_type.py +21 -18
  11. magic_pdf/libs/version.py +1 -1
  12. magic_pdf/model/doc_analyze_by_custom_model.py +14 -2
  13. magic_pdf/model/magic_model.py +283 -166
  14. magic_pdf/model/model_list.py +8 -0
  15. magic_pdf/model/pdf_extract_kit.py +105 -15
  16. magic_pdf/model/pek_sub_modules/self_modify.py +84 -0
  17. magic_pdf/para/para_split_v2.py +26 -27
  18. magic_pdf/pdf_parse_union_core.py +34 -6
  19. magic_pdf/pipe/AbsPipe.py +4 -1
  20. magic_pdf/pipe/OCRPipe.py +7 -4
  21. magic_pdf/pipe/TXTPipe.py +7 -4
  22. magic_pdf/pipe/UNIPipe.py +11 -6
  23. magic_pdf/pre_proc/ocr_detect_all_bboxes.py +12 -3
  24. magic_pdf/pre_proc/ocr_dict_merge.py +60 -59
  25. magic_pdf/tools/cli.py +56 -29
  26. magic_pdf/tools/cli_dev.py +61 -64
  27. magic_pdf/tools/common.py +57 -37
  28. magic_pdf/user_api.py +17 -9
  29. {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/METADATA +72 -27
  30. {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/RECORD +34 -29
  31. {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/LICENSE.md +0 -0
  32. {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/WHEEL +0 -0
  33. {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/entry_points.txt +0 -0
  34. {magic_pdf-0.7.1.dist-info → magic_pdf-0.8.1.dist-info}/top_level.txt +0 -0
@@ -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
- 检查bbox里面的文字是否只有一行
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]["paras"]
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["text"]
32
- text_content += "\n\n"
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
- def _horizontal_split(bboxes:list, boundry:tuple, avg_font_size=20)-> list:
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 = boundry
47
- all_bboxes = get_bbox_in_boundry(bboxes, boundry)
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 = _is_single_line_text(bbox)
71
+ # is_single_line = _is_single_line_text(bbox)
66
72
  """
67
- 这里有个点需要注意,当页面内容不是居中的时候,第一次调用传递的是page的boundry,这个时候mid_x就不是中心线了.
68
- 所以这里计算出最紧致的boundry,然后再计算mid_x
73
+ 这里有个点需要注意,当页面内容不是居中的时候,第一次调用传递的是page的boundary,这个时候mid_x就不是中心线了.
74
+ 所以这里计算出最紧致的boundary,然后再计算mid_x
69
75
  """
70
- boundry_real_x0, boundry_real_x1 = min([bbox[X0_IDX] for bbox in all_bboxes]), max([bbox[X1_IDX] for bbox in all_bboxes])
71
- mid_x = (boundry_real_x0+boundry_real_x1)/2
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
- is_cross_boundry_mid_line = min(mid_x-bbox[X0_IDX], bbox[X1_IDX]-mid_x) > avg_font_size*2
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
- #先确定上方的y0, y0
97
+ # 先确定上方的y0, y0
88
98
  b_y0, b_y1 = last_h_split_line_y1, bbox[Y0_IDX]
89
- #然后从box开始逐个向上找到所有与box在x上有交集的box
99
+ # 然后从box开始逐个向上找到所有与box在x上有交集的box
90
100
  box_to_check = [bound_x0, b_y0, bound_x1, b_y1]
91
- bbox_in_bound_check = get_bbox_in_boundry(all_bboxes, box_to_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 = [min([virtual_box[X0_IDX], b_on_top[X0_IDX]]), min(virtual_box[Y0_IDX], b_on_top[Y0_IDX]), max([virtual_box[X1_IDX], b_on_top[X1_IDX]]), b_y1]
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(bbox_in_bound_check):# virtual_box可能会膨胀到占满整个区域,这实际上就不能属于一个col了。
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([b[X0_IDX] <= min_x0-1 <= b[X1_IDX] or b[X0_IDX] <= max_x1+1 <= b[X1_IDX] for b in bbox_in_bound_check]):
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
- top_nearest_bbox is not None and (find_all_left_bbox_direct(top_nearest_bbox, bboxes) is None and find_all_right_bbox_direct(top_nearest_bbox, bboxes) is None),
114
- bottom_nearest_bbox is not None and (find_all_left_bbox_direct(bottom_nearest_bbox, bboxes) is None and find_all_right_bbox_direct(bottom_nearest_bbox, bboxes) is None),
115
- top_nearest_bbox is None or bottom_nearest_bbox is None
116
- ]):
117
- is_belong_to_col = True
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
- 这里为什么没有is_cross_boundry_mid_line的条件呢?
160
+ 这里为什么没有is_cross_boundary_mid_line的条件呢?
123
161
  确实有些杂志左右两栏宽度不是对称的。
124
162
  """
125
- if not is_belong_to_col or is_cross_boundry_mid_line:
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 = gp[0][X0_EXT_IDX], gp[0][Y0_EXT_IDX], gp[-1][X1_EXT_IDX], gp[-1][Y1_EXT_IDX]
161
- h_layouts.append([x0, y0, x1, y1, LAYOUT_H]) # 水平的布局
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: # gp是一个list[bbox_list]
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 = [bbox for bbox in all_bboxes if bbox[Y0_IDX]>=start_y0 and bbox[Y1_IDX]<=start_y1]
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 = bound_x0, min([bbox[Y0_IDX] for bbox in bboxes_in_block]), bound_x1, max([bbox[Y1_IDX] for bbox in bboxes_in_block])
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]) # 按照y0排序, 也就是从上到下的顺序
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
- "layout_bbox": layout[:4],
194
- "layout_label":layout[4],
195
- "sub_layout":[],
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, boundry:tuple)-> 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
- new_boundry = [boundry[0], boundry[1], boundry[2], boundry[3]]
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 = get_bbox_in_boundry(bboxes, new_boundry)
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([bbox[X0_IDX] <= right_split_line_x1 <= bbox[X1_IDX] for bbox in all_bboxes]):
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([bbox[X0_IDX] for bbox in left_edge_bboxes]) # 这里主要是为了画出来有一定间距
237
- v_blocks.append([layout_x0, new_boundry[1], right_split_line_x1, new_boundry[3], LAYOUT_V])
238
- new_boundry[0] = right_split_line_x1 # 更新边界
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 = get_bbox_in_boundry(bboxes, new_boundry)
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([bbox[X0_IDX] <= left_split_line_x0 <= bbox[X1_IDX] for bbox in all_bboxes]):
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([new_boundry[0], new_boundry[1], new_boundry[2], new_boundry[3], LAYOUT_UNPROC])
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([left_split_line_x0, new_boundry[1], layout_x1, new_boundry[3], LAYOUT_V])
259
- new_boundry[2] = left_split_line_x0 # 更新右边界
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
- "layout_bbox": block[:4],
267
- "layout_label":block[4],
268
- "sub_layout":[],
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
- "layout_bbox": block[:4],
273
- "layout_label":block[4],
274
- "sub_layout":[],
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
- def _vertical_align_split_v2(bboxes:list, boundry:tuple)-> list:
282
- """
283
- 改进的 _vertical_align_split算法,原算法会因为第二列的box由于左侧没有遮挡被认为是左侧的一部分,导致整个layout多列被识别为一列。
284
- 利用从左上角的box开始向下看的方法,不断扩展w_x0, w_x1,直到不能继续向下扩展,或者到达边界下边界。
285
- """
286
- sorted_layout_blocks = [] # 这是要最终返回的值
287
- new_boundry = [boundry[0], boundry[1], boundry[2], boundry[3]]
288
- bad_boxes = [] # 被割中的box
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 = get_bbox_in_boundry(bboxes, new_boundry)
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(all_bboxes, key=lambda x: (x[X0_IDX],x[Y0_IDX]))# 这里应该加强,检查一下必须是在第一列的 TODO
295
- start_box = [left_top_box[X0_IDX], left_top_box[Y0_IDX], left_top_box[X1_IDX], left_top_box[Y1_IDX]]
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(virtual_box, all_bboxes)
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([virtual_box[X1_IDX], left_top_box[X1_IDX]])
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 = [w_x0, start_box[Y0_IDX], w_x1, start_box[Y1_IDX]] # 扩展一下宽度更鲁棒
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([virtual_box[X1_IDX], left_top_box[X1_IDX]])
317
-
318
- # 检查相交
319
- if any([bbox[X0_IDX] <= w_x1+1 <= bbox[X1_IDX] for bbox in all_bboxes]):
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, new_boundry[1], w_x1, new_boundry[3], LAYOUT_V])
326
- new_boundry[0] = w_x1 # 更新边界
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 , w_x1 = 0, 0
432
+ w_x0, w_x1 = 0, 0
332
433
  unsplited_block = []
333
434
  while True:
334
- all_bboxes = get_bbox_in_boundry(bboxes, new_boundry)
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(all_bboxes, key=lambda bbox: bbox[X1_IDX], reverse=True)
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 = [bbox for bbox in bbox_list_sorted if bbox[X1_IDX] == bigest_x1] # 也就是最靠右的那些
342
- right_top_box = min(boxes_with_bigest_x1, key=lambda bbox: bbox[Y0_IDX]) # y0最小的那个
343
- start_box = [right_top_box[X0_IDX], right_top_box[Y0_IDX], right_top_box[X1_IDX], right_top_box[Y1_IDX]]
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(virtual_box, all_bboxes)
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([w_x1, right_top_box[X1_IDX]])
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 = [w_x0, start_box[Y0_IDX], w_x1, start_box[Y1_IDX]] # 扩展一下宽度更鲁棒
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(virtual_box, all_bboxes)
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([w_x1, right_top_box[X1_IDX]])
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([new_boundry[0], new_boundry[1], new_boundry[2], new_boundry[3], LAYOUT_UNPROC])
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, new_boundry[1], w_x1, new_boundry[3], LAYOUT_V])
369
- new_boundry[2] = w_x0
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
- "layout_bbox": block[:4],
375
- "layout_label":block[4],
376
- "sub_layout":[],
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
- "layout_bbox": block[:4],
382
- "layout_label":block[4],
383
- "sub_layout":[],
384
- "bad_boxes": bad_boxes # 记录下来,这个box是被割中的
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, boundry:tuple)-> 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 = boundry
429
- all_bboxes = get_bbox_in_boundry(bboxes, boundry)
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 top_nearest_bbox is None and bottom_nearest_bbox is None and not any([b[X0_IDX]<bbox[X1_IDX]<b[X1_IDX] or b[X0_IDX]<bbox[X0_IDX]<b[X1_IDX] for b in all_bboxes]): # 独占一列, 且不和其他重叠
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 = vbox[X0_EXT_IDX], vbox[Y0_EXT_IDX], vbox[X1_EXT_IDX], vbox[Y1_EXT_IDX]
473
- v_layouts.append([x0, y0, x1, y1, LAYOUT_V]) # 垂直的布局
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 = [bbox for bbox in all_bboxes if bbox[X0_IDX]>=start_x0 and bbox[X1_IDX]<=start_x1]
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 = min([bbox[X0_IDX] for bbox in bboxes_in_block]), bound_y0, max([bbox[X1_IDX] for bbox in bboxes_in_block]), bound_y1
496
- v_layouts.append([x0, y0, x1, y1, LAYOUT_UNPROC]) # 说明这篇区域未能够分析出可靠的版面
497
-
498
- v_layouts.sort(key=lambda x: x[0]) # 按照x0排序, 也就是从左到右的顺序
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
- "layout_bbox": layout[:4],
503
- "layout_label":layout[4],
504
- "sub_layout":[],
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
- "layout_bbox": [x0, y0, x1, y1],
517
- "layout_label": LAYOUT_H,
518
- "sub_layout": v_split_layouts
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
- def split_layout(bboxes:list, boundry:tuple, page_num:int)-> list:
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, y0, x1, y1],
686
+ "layout_bbox": [x0,y0,x1,y1],
532
687
  "layout_label":"u|v|h|b", 未处理|垂直|水平|BAD_LAYOUT
533
- "sub_layout": [] #每个元素都是[x0, y0, x1, y1, block_content, idx_x, idx_y, content_type, ext_x0, ext_y0, ext_x1, ext_y1], 并且顺序就是阅读顺序
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
- boundry_x0, boundry_y0, boundry_x1, boundry_y1 = boundry
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
- "layout_bbox": [boundry_x0, boundry_y0, boundry_x1, boundry_y1],
575
- "layout_label": LAYOUT_V,
576
- "sub_layout":[]
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(bboxes, boundry_x1-boundry_x0, boundry_y1-boundry_y0)
584
- sorted_layouts = _horizontal_split(bboxes, boundry) # 通过水平分割出来的layout
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
- "layout_bbox": [x0, y0, x1, y1],
606
- "layout_label": layout_label,
607
- "sub_layout": v_split_layouts
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, boundry:tuple, page_id:int):
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
- 对sorted_layouts的叶子节点,也就是len(sub_layout)==0的节点进行排序。排序按照前序遍历的顺序,也就是从上到下,从左到右的顺序
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(all_boxes, boundry, page_id)# 先切分成layout,得到一个Tree
646
- total_sorted_layout_blocks = _preorder_traversal(sorted_layouts)
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
- 获取一个layout的宽度
653
- """
654
- max_width_list = [0] # 初始化一个元素,防止max,min函数报错
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 l in sub_layouts:
667
- if len(l['sub_layout']) == 0:
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 l['sub_layout']:
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
- def sort_with_layout(bboxes:list, page_width, page_height) -> (list,list):
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([box[0], box[1], box[2], box[3], None, None, None, 'text', None, None, None, None, box[4]])
688
-
689
- layout_bboxes, _ = get_bboxes_layout(new_bboxes, [0, 0, page_width, page_height], 0)
690
- if any([lay['layout_label']==LAYOUT_UNPROC for lay in layout_bboxes]):
691
- logger.warning(f"drop this pdf, reason: 复杂版面")
692
- return None,None
693
-
694
- sorted_bboxes = []
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 = get_bbox_in_boundry(new_bboxes, lbox)
699
- sorted_bbox = paper_bbox_sort(bbox_in_layout, lbox[2]-lbox[0], lbox[3]-lbox[1])
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 = get_bbox_in_boundry(all_text_bbox, [layout_box[0]-1, layout_box[1]-1, layout_box[2]+1, layout_box[3]+1])
726
- #sorted_bbox = paper_bbox_sort(text_bbox_in_layout, layout_box[2]-layout_box[0], layout_box[3]-layout_box[1])
727
- text_bbox_in_layout.sort(key = lambda x: x[1]) # 一个layout内部的box,按照y0自上而下排序
728
- #sorted_bbox = [[b] for b in text_blocks_to_sort]
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