magic-pdf 0.10.1__py3-none-any.whl → 0.10.2__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 (75) hide show
  1. magic_pdf/filter/pdf_meta_scan.py +3 -17
  2. magic_pdf/libs/commons.py +0 -161
  3. magic_pdf/libs/draw_bbox.py +2 -3
  4. magic_pdf/libs/markdown_utils.py +0 -21
  5. magic_pdf/libs/pdf_image_tools.py +2 -1
  6. magic_pdf/libs/version.py +1 -1
  7. magic_pdf/model/doc_analyze_by_custom_model.py +2 -2
  8. magic_pdf/model/magic_model.py +0 -30
  9. magic_pdf/model/sub_modules/ocr/paddleocr/ocr_utils.py +3 -28
  10. magic_pdf/model/sub_modules/ocr/paddleocr/ppocr_273_mod.py +3 -3
  11. magic_pdf/para/para_split_v3.py +7 -2
  12. magic_pdf/pdf_parse_union_core_v2.py +97 -124
  13. magic_pdf/pre_proc/construct_page_dict.py +0 -55
  14. magic_pdf/pre_proc/cut_image.py +0 -37
  15. magic_pdf/pre_proc/ocr_detect_all_bboxes.py +5 -178
  16. magic_pdf/pre_proc/ocr_dict_merge.py +1 -224
  17. magic_pdf/pre_proc/ocr_span_list_modify.py +2 -252
  18. magic_pdf/rw/S3ReaderWriter.py +1 -1
  19. {magic_pdf-0.10.1.dist-info → magic_pdf-0.10.2.dist-info}/METADATA +3 -77
  20. {magic_pdf-0.10.1.dist-info → magic_pdf-0.10.2.dist-info}/RECORD +24 -75
  21. magic_pdf/dict2md/mkcontent.py +0 -438
  22. magic_pdf/layout/__init__.py +0 -0
  23. magic_pdf/layout/bbox_sort.py +0 -681
  24. magic_pdf/layout/layout_det_utils.py +0 -182
  25. magic_pdf/layout/layout_sort.py +0 -921
  26. magic_pdf/layout/layout_spiler_recog.py +0 -101
  27. magic_pdf/layout/mcol_sort.py +0 -336
  28. magic_pdf/libs/calc_span_stats.py +0 -239
  29. magic_pdf/libs/detect_language_from_model.py +0 -21
  30. magic_pdf/libs/nlp_utils.py +0 -203
  31. magic_pdf/libs/textbase.py +0 -33
  32. magic_pdf/libs/vis_utils.py +0 -308
  33. magic_pdf/para/block_continuation_processor.py +0 -562
  34. magic_pdf/para/block_termination_processor.py +0 -480
  35. magic_pdf/para/commons.py +0 -222
  36. magic_pdf/para/denoise.py +0 -246
  37. magic_pdf/para/draw.py +0 -121
  38. magic_pdf/para/exceptions.py +0 -198
  39. magic_pdf/para/layout_match_processor.py +0 -40
  40. magic_pdf/para/para_split.py +0 -807
  41. magic_pdf/para/para_split_v2.py +0 -959
  42. magic_pdf/para/raw_processor.py +0 -207
  43. magic_pdf/para/stats.py +0 -268
  44. magic_pdf/para/title_processor.py +0 -1014
  45. magic_pdf/pdf_parse_union_core.py +0 -345
  46. magic_pdf/post_proc/__init__.py +0 -0
  47. magic_pdf/post_proc/detect_para.py +0 -3472
  48. magic_pdf/post_proc/pdf_post_filter.py +0 -60
  49. magic_pdf/post_proc/remove_footnote.py +0 -153
  50. magic_pdf/pre_proc/citationmarker_remove.py +0 -161
  51. magic_pdf/pre_proc/detect_equation.py +0 -134
  52. magic_pdf/pre_proc/detect_footer_by_model.py +0 -64
  53. magic_pdf/pre_proc/detect_footer_header_by_statistics.py +0 -284
  54. magic_pdf/pre_proc/detect_footnote.py +0 -170
  55. magic_pdf/pre_proc/detect_header.py +0 -64
  56. magic_pdf/pre_proc/detect_images.py +0 -647
  57. magic_pdf/pre_proc/detect_page_number.py +0 -64
  58. magic_pdf/pre_proc/detect_tables.py +0 -62
  59. magic_pdf/pre_proc/equations_replace.py +0 -550
  60. magic_pdf/pre_proc/fix_image.py +0 -244
  61. magic_pdf/pre_proc/fix_table.py +0 -270
  62. magic_pdf/pre_proc/main_text_font.py +0 -23
  63. magic_pdf/pre_proc/ocr_detect_layout.py +0 -133
  64. magic_pdf/pre_proc/pdf_pre_filter.py +0 -78
  65. magic_pdf/pre_proc/post_layout_split.py +0 -0
  66. magic_pdf/pre_proc/remove_colored_strip_bbox.py +0 -101
  67. magic_pdf/pre_proc/remove_footer_header.py +0 -114
  68. magic_pdf/pre_proc/remove_rotate_bbox.py +0 -236
  69. magic_pdf/pre_proc/resolve_bbox_conflict.py +0 -184
  70. magic_pdf/pre_proc/solve_line_alien.py +0 -29
  71. magic_pdf/pre_proc/statistics.py +0 -12
  72. {magic_pdf-0.10.1.dist-info → magic_pdf-0.10.2.dist-info}/LICENSE.md +0 -0
  73. {magic_pdf-0.10.1.dist-info → magic_pdf-0.10.2.dist-info}/WHEEL +0 -0
  74. {magic_pdf-0.10.1.dist-info → magic_pdf-0.10.2.dist-info}/entry_points.txt +0 -0
  75. {magic_pdf-0.10.1.dist-info → magic_pdf-0.10.2.dist-info}/top_level.txt +0 -0
@@ -1,921 +0,0 @@
1
- """对pdf上的box进行layout识别,并对内部组成的box进行排序."""
2
-
3
- from loguru import logger
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'
22
-
23
-
24
- def _is_single_line_text(bbox):
25
- """检查bbox里面的文字是否只有一行."""
26
- return True # TODO
27
- box_type = bbox[CONTENT_TYPE_IDX]
28
- if box_type != 'text':
29
- return False
30
- paras = bbox[CONTENT_IDX]['paras']
31
- text_content = ''
32
- for para_id, para in paras.items(): # 拼装内部的段落文本
33
- is_title = para['is_title']
34
- if is_title != 0:
35
- text_content += f"## {para['text']}"
36
- else:
37
- text_content += para['text']
38
- text_content += '\n\n'
39
-
40
- return bbox[CONTENT_TYPE_IDX] == 'text' and len(text_content.split('\n\n')) <= 1
41
-
42
-
43
- def _horizontal_split(bboxes: list, boundary: tuple, avg_font_size=20) -> list:
44
- """
45
- 对bboxes进行水平切割
46
- 方法是:找到左侧和右侧都没有被直接遮挡的box,然后进行扩展,之后进行切割
47
- return:
48
- 返回几个大的Layout区域 [[x0, y0, x1, y1, "h|u|v"], ], h代表水平,u代表未探测的,v代表垂直布局
49
- """
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)) # 大致拍下序, 这个是基于直接遮挡的。
55
- """
56
- 首先在水平方向上扩展独占一行的bbox
57
-
58
- """
59
- last_h_split_line_y1 = bound_y0 # 记录下上次的水平分割线
60
- for i, bbox in enumerate(all_bboxes):
61
- left_nearest_bbox = find_all_left_bbox_direct(bbox, all_bboxes) # 非扩展线
62
- right_nearest_bbox = find_all_right_bbox_direct(bbox, all_bboxes)
63
- if left_nearest_bbox is None and right_nearest_bbox is None: # 独占一行
64
- """
65
- 然而,如果只是孤立的一行文字,那么就还要满足以下几个条件才可以:
66
- 1. bbox和中心线相交。或者
67
- 2. 上方或者下方也存在同类水平的独占一行的bbox。 或者
68
- 3. TODO 加强条件:这个bbox上方和下方是同一列column,那么就不能算作独占一行
69
- """
70
- # 先检查这个bbox里是否只包含一行文字
71
- # is_single_line = _is_single_line_text(bbox)
72
- """
73
- 这里有个点需要注意,当页面内容不是居中的时候,第一次调用传递的是page的boundary,这个时候mid_x就不是中心线了.
74
- 所以这里计算出最紧致的boundary,然后再计算mid_x
75
- """
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
80
- # 检查这个box是否内容在中心线有交
81
- # 必须跨过去2个字符的宽度
82
- is_cross_boundary_mid_line = (
83
- min(mid_x - bbox[X0_IDX], bbox[X1_IDX] - mid_x) > avg_font_size * 2
84
- )
85
- """
86
- 检查条件2
87
- """
88
- is_belong_to_col = False
89
- """
90
- 检查是否能被上方col吸收,方法是:
91
- 1. 上方非空且不是独占一行的,并且
92
- 2. 从上个水平分割的最大y=y1开始到当前bbox,最左侧的bbox的[min_x0, max_x1],能够覆盖当前box的[x0, x1]
93
- """
94
- """
95
- 以迭代的方式向上找,查找范围是[bound_x0, last_h_sp, bound_x1, bbox[Y0_IDX]]
96
- """
97
- # 先确定上方的y0, y0
98
- b_y0, b_y1 = last_h_split_line_y1, bbox[Y0_IDX]
99
- # 然后从box开始逐个向上找到所有与box在x上有交集的box
100
- box_to_check = [bound_x0, b_y0, bound_x1, b_y1]
101
- bbox_in_bound_check = get_bbox_in_boundary(all_bboxes, box_to_check)
102
-
103
- bboxes_on_top = []
104
- virtual_box = bbox
105
- while True:
106
- b_on_top = find_all_top_bbox_direct(virtual_box, bbox_in_bound_check)
107
- if b_on_top is not None:
108
- bboxes_on_top.append(b_on_top)
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
- ]
115
- else:
116
- break
117
-
118
- # 随后确定这些box的最小x0, 最大x1
119
- if len(bboxes_on_top) > 0 and len(bboxes_on_top) != len(
120
- bbox_in_bound_check
121
- ): # virtual_box可能会膨胀到占满整个区域,这实际上就不能属于一个col了。
122
- min_x0, max_x1 = virtual_box[X0_IDX], virtual_box[X1_IDX]
123
- # 然后采用一种比较粗糙的方法,看min_x0,max_x1是否与位于[bound_x0, last_h_sp, bound_x1, bbox[Y0_IDX]]之间的box有相交
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
- ):
132
- # 其上,下都不能被扩展成行,暂时只检查一下上方 TODO
133
- top_nearest_bbox = find_all_top_bbox_direct(bbox, bboxes)
134
- bottom_nearest_bbox = find_all_bottom_bbox_direct(bbox, bboxes)
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
-
158
- # 检查是否能被下方col吸收 TODO
159
- """
160
- 这里为什么没有is_cross_boundary_mid_line的条件呢?
161
- 确实有些杂志左右两栏宽度不是对称的。
162
- """
163
- if not is_belong_to_col or is_cross_boundary_mid_line:
164
- bbox[X0_EXT_IDX] = bound_x0
165
- bbox[Y0_EXT_IDX] = bbox[Y0_IDX]
166
- bbox[X1_EXT_IDX] = bound_x1
167
- bbox[Y1_EXT_IDX] = bbox[Y1_IDX]
168
- last_h_split_line_y1 = bbox[Y1_IDX] # 更新这条线
169
- else:
170
- continue
171
- """
172
- 此时独占一行的被成功扩展到指定的边界上,这个时候利用边界条件合并连续的bbox,成为一个group
173
- 然后合并所有连续水平方向的bbox.
174
- """
175
- all_bboxes.sort(key=lambda x: x[Y0_IDX])
176
- h_bboxes = []
177
- h_bbox_group = []
178
-
179
- for bbox in all_bboxes:
180
- if bbox[X0_EXT_IDX] == bound_x0 and bbox[X1_EXT_IDX] == bound_x1:
181
- h_bbox_group.append(bbox)
182
- else:
183
- if len(h_bbox_group) > 0:
184
- h_bboxes.append(h_bbox_group)
185
- h_bbox_group = []
186
- # 最后一个group
187
- if len(h_bbox_group) > 0:
188
- h_bboxes.append(h_bbox_group)
189
- """
190
- 现在h_bboxes里面是所有的group了,每个group都是一个list
191
- 对h_bboxes里的每个group进行计算放回到sorted_layouts里
192
- """
193
- h_layouts = []
194
- for gp in h_bboxes:
195
- gp.sort(key=lambda x: x[Y0_IDX])
196
- # 然后计算这个group的layout_bbox,也就是最小的x0,y0, 最大的x1,y1
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]) # 水平的布局
204
- """
205
- 接下来利用这些连续的水平bbox的layout_bbox的y0, y1,从水平上切分开其余的为几个部分
206
- """
207
- h_split_lines = [bound_y0]
208
- for gp in h_bboxes: # gp是一个list[bbox_list]
209
- y0, y1 = gp[0][1], gp[-1][3]
210
- h_split_lines.append(y0)
211
- h_split_lines.append(y1)
212
- h_split_lines.append(bound_y1)
213
-
214
- unsplited_bboxes = []
215
- for i in range(0, len(h_split_lines), 2):
216
- start_y0, start_y1 = h_split_lines[i : i + 2]
217
- # 然后找出[start_y0, start_y1]之间的其他bbox,这些组成一个未分割板块
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
- ]
223
- unsplited_bboxes.append(bboxes_in_block)
224
- # 接着把未处理的加入到h_layouts里
225
- for bboxes_in_block in unsplited_bboxes:
226
- if len(bboxes_in_block) == 0:
227
- continue
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
- )
234
- h_layouts.append([x0, y0, x1, y1, LAYOUT_UNPROC])
235
-
236
- h_layouts.sort(key=lambda x: x[1]) # 按照y0排序, 也就是从上到下的顺序
237
- """
238
- 转换成如下格式返回
239
- """
240
- for layout in h_layouts:
241
- sorted_layout_blocks.append(
242
- {
243
- 'layout_bbox': layout[:4],
244
- 'layout_label': layout[4],
245
- 'sub_layout': [],
246
- }
247
- )
248
- return sorted_layout_blocks
249
-
250
-
251
- ###############################################################################################
252
- #
253
- # 垂直方向的处理
254
- #
255
- #
256
- ###############################################################################################
257
- def _vertical_align_split_v1(bboxes: list, boundary: tuple) -> list:
258
- """
259
- 计算垂直方向上的对齐, 并分割bboxes成layout。负责对一列多行的进行列维度分割。
260
- 如果不能完全分割,剩余部分作为layout_lable为u的layout返回
261
- -----------------------
262
- | | |
263
- | | |
264
- | | |
265
- | | |
266
- -------------------------
267
- 此函数会将:以上布局将会切分出来2列
268
- """
269
- sorted_layout_blocks = [] # 这是要最终返回的值
270
- new_boundary = [boundary[0], boundary[1], boundary[2], boundary[3]]
271
-
272
- v_blocks = []
273
- """
274
- 先从左到右切分
275
- """
276
- while True:
277
- all_bboxes = get_bbox_in_boundary(bboxes, new_boundary)
278
- left_edge_bboxes = get_left_edge_bboxes(all_bboxes)
279
- if len(left_edge_bboxes) == 0:
280
- break
281
- right_split_line_x1 = max([bbox[X1_IDX] for bbox in left_edge_bboxes]) + 1
282
- # 然后检查这条线能不与其他bbox的左边界相交或者重合
283
- if any(
284
- [bbox[X0_IDX] <= right_split_line_x1 <= bbox[X1_IDX] for bbox in all_bboxes]
285
- ):
286
- # 垂直切分线与某些box发生相交,说明无法完全垂直方向切分。
287
- break
288
- else: # 说明成功分割出一列
289
- # 找到左侧边界最靠左的bbox作为layout的x0
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 # 更新边界
303
- """
304
- 再从右到左切, 此时如果还是无法完全切分,那么剩余部分作为layout_lable为u的layout返回
305
- """
306
- unsplited_block = []
307
- while True:
308
- all_bboxes = get_bbox_in_boundary(bboxes, new_boundary)
309
- right_edge_bboxes = get_right_edge_bboxes(all_bboxes)
310
- if len(right_edge_bboxes) == 0:
311
- break
312
- left_split_line_x0 = min([bbox[X0_IDX] for bbox in right_edge_bboxes]) - 1
313
- # 然后检查这条线能不与其他bbox的左边界相交或者重合
314
- if any(
315
- [bbox[X0_IDX] <= left_split_line_x0 <= bbox[X1_IDX] for bbox in all_bboxes]
316
- ):
317
- # 这里是余下的
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
- )
327
- break
328
- else:
329
- # 找到右侧边界最靠右的bbox作为layout的x1
330
- layout_x1 = max([bbox[X1_IDX] for bbox in right_edge_bboxes])
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 # 更新右边界
341
- """
342
- 最后拼装成layout格式返回
343
- """
344
- for block in v_blocks:
345
- sorted_layout_blocks.append(
346
- {
347
- 'layout_bbox': block[:4],
348
- 'layout_label': block[4],
349
- 'sub_layout': [],
350
- }
351
- )
352
- for block in unsplited_block:
353
- sorted_layout_blocks.append(
354
- {
355
- 'layout_bbox': block[:4],
356
- 'layout_label': block[4],
357
- 'sub_layout': [],
358
- }
359
- )
360
-
361
- # 按照x0排序
362
- sorted_layout_blocks.sort(key=lambda x: x['layout_bbox'][0])
363
- return sorted_layout_blocks
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
373
- v_blocks = []
374
- while True:
375
- all_bboxes = get_bbox_in_boundary(bboxes, new_boundary)
376
- if len(all_bboxes) == 0:
377
- break
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
- ]
387
- w_x0, w_x1 = left_top_box[X0_IDX], left_top_box[X1_IDX]
388
- """
389
- 然后沿着这个box线向下找最近的那个box, 然后扩展w_x0, w_x1
390
- 扩展之后,宽度会增加,随后用x=w_x1来检测在边界内是否有box与相交,如果相交,那么就说明不能再扩展了。
391
- 当不能扩展的时候就要看是否到达下边界:
392
- 1. 达到,那么更新左边界继续分下一个列
393
- 2. 没有达到,那么此时开始从右侧切分进入下面的循环里
394
- """
395
- while left_top_box is not None: # 向下去找
396
- virtual_box = [w_x0, left_top_box[Y0_IDX], w_x1, left_top_box[Y1_IDX]]
397
- left_top_box = find_bottom_bbox_direct_from_left_edge(
398
- virtual_box, all_bboxes
399
- )
400
- if left_top_box:
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
- )
404
- # 万一这个初始的box在column中间,那么还要向上看
405
- start_box = [
406
- w_x0,
407
- start_box[Y0_IDX],
408
- w_x1,
409
- start_box[Y1_IDX],
410
- ] # 扩展一下宽度更鲁棒
411
- left_top_box = find_top_bbox_direct_from_left_edge(start_box, all_bboxes)
412
- while left_top_box is not None: # 向上去找
413
- virtual_box = [w_x0, left_top_box[Y0_IDX], w_x1, left_top_box[Y1_IDX]]
414
- left_top_box = find_top_bbox_direct_from_left_edge(virtual_box, all_bboxes)
415
- if left_top_box:
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]):
422
- for b in all_bboxes:
423
- if b[X0_IDX] <= w_x1 + 1 <= b[X1_IDX]:
424
- bad_boxes.append([b[X0_IDX], b[Y0_IDX], b[X1_IDX], b[Y1_IDX]])
425
- break
426
- else: # 说明成功分割出一列
427
- v_blocks.append([w_x0, new_boundary[1], w_x1, new_boundary[3], LAYOUT_V])
428
- new_boundary[0] = w_x1 # 更新边界
429
- """
430
- 接着开始从右上角的box扫描
431
- """
432
- w_x0, w_x1 = 0, 0
433
- unsplited_block = []
434
- while True:
435
- all_bboxes = get_bbox_in_boundary(bboxes, new_boundary)
436
- if len(all_bboxes) == 0:
437
- break
438
- # 先找到X1最大的
439
- bbox_list_sorted = sorted(
440
- all_bboxes, key=lambda bbox: bbox[X1_IDX], reverse=True
441
- )
442
- # Then, find the boxes with the smallest Y0 value
443
- bigest_x1 = bbox_list_sorted[0][X1_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
- ]
456
- w_x0, w_x1 = right_top_box[X0_IDX], right_top_box[X1_IDX]
457
-
458
- while right_top_box is not None:
459
- virtual_box = [w_x0, right_top_box[Y0_IDX], w_x1, right_top_box[Y1_IDX]]
460
- right_top_box = find_bottom_bbox_direct_from_right_edge(
461
- virtual_box, all_bboxes
462
- )
463
- if right_top_box:
464
- w_x0, w_x1 = min([w_x0, right_top_box[X0_IDX]]), max(
465
- [w_x1, right_top_box[X1_IDX]]
466
- )
467
- # 在向上扫描
468
- start_box = [
469
- w_x0,
470
- start_box[Y0_IDX],
471
- w_x1,
472
- start_box[Y1_IDX],
473
- ] # 扩展一下宽度更鲁棒
474
- right_top_box = find_top_bbox_direct_from_right_edge(start_box, all_bboxes)
475
- while right_top_box is not None:
476
- virtual_box = [w_x0, right_top_box[Y0_IDX], w_x1, right_top_box[Y1_IDX]]
477
- right_top_box = find_top_bbox_direct_from_right_edge(
478
- virtual_box, all_bboxes
479
- )
480
- if right_top_box:
481
- w_x0, w_x1 = min([w_x0, right_top_box[X0_IDX]]), max(
482
- [w_x1, right_top_box[X1_IDX]]
483
- )
484
-
485
- # 检查是否与其他box相交, 垂直切分线与某些box发生相交,说明无法完全垂直方向切分。
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
- )
496
- for b in all_bboxes:
497
- if b[X0_IDX] <= w_x0 - 1 <= b[X1_IDX]:
498
- bad_boxes.append([b[X0_IDX], b[Y0_IDX], b[X1_IDX], b[Y1_IDX]])
499
- break
500
- else: # 说明成功分割出一列
501
- v_blocks.append([w_x0, new_boundary[1], w_x1, new_boundary[3], LAYOUT_V])
502
- new_boundary[2] = w_x0
503
- """转换数据结构"""
504
- for block in v_blocks:
505
- sorted_layout_blocks.append(
506
- {
507
- 'layout_bbox': block[:4],
508
- 'layout_label': block[4],
509
- 'sub_layout': [],
510
- }
511
- )
512
-
513
- for block in unsplited_block:
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
-
523
- # 按照x0排序
524
- sorted_layout_blocks.sort(key=lambda x: x['layout_bbox'][0])
525
- return sorted_layout_blocks
526
-
527
-
528
- def _try_horizontal_mult_column_split(bboxes: list, boundary: tuple) -> list:
529
- """
530
- 尝试水平切分,如果切分不动,那就当一个BAD_LAYOUT返回
531
- ------------------
532
- | | |
533
- ------------------
534
- | | | | <- 这里是此函数要切分的场景
535
- ------------------
536
- | | |
537
- | | |
538
- """
539
- pass
540
-
541
-
542
- def _vertical_split(bboxes: list, boundary: tuple) -> list:
543
- """
544
- 从垂直方向进行切割,分block
545
- 这个版本里,如果垂直切分不动,那就当一个BAD_LAYOUT返回
546
-
547
- --------------------------
548
- | | |
549
- | | |
550
- | |
551
- 这种列是此函数要切分的 -> | |
552
- | |
553
- | | |
554
- | | |
555
- -------------------------
556
- """
557
- sorted_layout_blocks = [] # 这是要最终返回的值
558
-
559
- bound_x0, bound_y0, bound_x1, bound_y1 = boundary
560
- all_bboxes = get_bbox_in_boundary(bboxes, boundary)
561
- """
562
- all_bboxes = fix_vertical_bbox_pos(all_bboxes) # 垂直方向解覆盖
563
- all_bboxes = fix_hor_bbox_pos(all_bboxes) # 水平解覆盖
564
-
565
- 这两行代码目前先不执行,因为公式检测,表格检测还不是很成熟,导致非常多的textblock参与了运算,时间消耗太大。
566
- 这两行代码的作用是:
567
- 如果遇到互相重叠的bbox, 那么会把面积较小的box进行压缩,从而避免重叠。对布局切分来说带来正反馈。
568
- """
569
-
570
- # all_bboxes = paper_bbox_sort(all_bboxes, abs(bound_x1-bound_x0), abs(bound_y1-bound_x0)) # 大致拍下序, 这个是基于直接遮挡的。
571
- """
572
- 首先在垂直方向上扩展独占一行的bbox
573
-
574
- """
575
- for bbox in all_bboxes:
576
- top_nearest_bbox = find_all_top_bbox_direct(bbox, all_bboxes) # 非扩展线
577
- bottom_nearest_bbox = find_all_bottom_bbox_direct(bbox, 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
- ): # 独占一列, 且不和其他重叠
589
- bbox[X0_EXT_IDX] = bbox[X0_IDX]
590
- bbox[Y0_EXT_IDX] = bound_y0
591
- bbox[X1_EXT_IDX] = bbox[X1_IDX]
592
- bbox[Y1_EXT_IDX] = bound_y1
593
- """
594
- 此时独占一列的被成功扩展到指定的边界上,这个时候利用边界条件合并连续的bbox,成为一个group
595
- 然后合并所有连续垂直方向的bbox.
596
- """
597
- all_bboxes.sort(key=lambda x: x[X0_IDX])
598
- # fix: 这里水平方向的列不要合并成一个行,因为需要保证返回给下游的最小block,总是可以无脑从上到下阅读文字。
599
- v_bboxes = []
600
- for box in all_bboxes:
601
- if box[Y0_EXT_IDX] == bound_y0 and box[Y1_EXT_IDX] == bound_y1:
602
- v_bboxes.append(box)
603
- """
604
- 现在v_bboxes里面是所有的group了,每个group都是一个list
605
- 对v_bboxes里的每个group进行计算放回到sorted_layouts里
606
- """
607
- v_layouts = []
608
- for vbox in v_bboxes:
609
- # gp.sort(key=lambda x: x[X0_IDX])
610
- # 然后计算这个group的layout_bbox,也就是最小的x0,y0, 最大的x1,y1
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]) # 垂直的布局
618
- """
619
- 接下来利用这些连续的垂直bbox的layout_bbox的x0, x1,从垂直上切分开其余的为几个部分
620
- """
621
- v_split_lines = [bound_x0]
622
- for gp in v_bboxes:
623
- x0, x1 = gp[X0_IDX], gp[X1_IDX]
624
- v_split_lines.append(x0)
625
- v_split_lines.append(x1)
626
- v_split_lines.append(bound_x1)
627
-
628
- unsplited_bboxes = []
629
- for i in range(0, len(v_split_lines), 2):
630
- start_x0, start_x1 = v_split_lines[i : i + 2]
631
- # 然后找出[start_x0, start_x1]之间的其他bbox,这些组成一个未分割板块
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
- ]
637
- unsplited_bboxes.append(bboxes_in_block)
638
- # 接着把未处理的加入到v_layouts里
639
- for bboxes_in_block in unsplited_bboxes:
640
- if len(bboxes_in_block) == 0:
641
- continue
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
-
654
- for layout in v_layouts:
655
- sorted_layout_blocks.append(
656
- {
657
- 'layout_bbox': layout[:4],
658
- 'layout_label': layout[4],
659
- 'sub_layout': [],
660
- }
661
- )
662
- """
663
- 至此,垂直方向切成了2种类型,其一是独占一列的,其二是未处理的。
664
- 下面对这些未处理的进行垂直方向切分,这个切分要切出来类似“吕”这种类型的垂直方向的布局
665
- """
666
- for i, layout in enumerate(sorted_layout_blocks):
667
- if layout['layout_label'] == LAYOUT_UNPROC:
668
- x0, y0, x1, y1 = layout['layout_bbox']
669
- v_split_layouts = _vertical_align_split_v2(bboxes, [x0, y0, x1, y1])
670
- sorted_layout_blocks[i] = {
671
- 'layout_bbox': [x0, y0, x1, y1],
672
- 'layout_label': LAYOUT_H,
673
- 'sub_layout': v_split_layouts,
674
- }
675
- layout['layout_label'] = LAYOUT_H # 被垂线切分成了水平布局
676
-
677
- return sorted_layout_blocks
678
-
679
-
680
- def split_layout(bboxes: list, boundary: tuple, page_num: int) -> list:
681
- """
682
- 把bboxes切割成layout
683
- return:
684
- [
685
- {
686
- "layout_bbox": [x0,y0,x1,y1],
687
- "layout_label":"u|v|h|b", 未处理|垂直|水平|BAD_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
- ], 并且顺序就是阅读顺序
697
- }
698
- ]
699
- example:
700
- [
701
- {
702
- "layout_bbox": [0, 0, 100, 100],
703
- "layout_label":"u|v|h|b",
704
- "sub_layout":[
705
-
706
- ]
707
- },
708
- {
709
- "layout_bbox": [0, 0, 100, 100],
710
- "layout_label":"u|v|h|b",
711
- "sub_layout":[
712
- {
713
- "layout_bbox": [0, 0, 100, 100],
714
- "layout_label":"u|v|h|b",
715
- "content_bboxes":[
716
- [],
717
- [],
718
- []
719
- ]
720
- },
721
- {
722
- "layout_bbox": [0, 0, 100, 100],
723
- "layout_label":"u|v|h|b",
724
- "sub_layout":[
725
-
726
- ]
727
- }
728
- }
729
- ]
730
- """
731
- sorted_layouts = [] # 最终返回的结果
732
-
733
- boundary_x0, boundary_y0, boundary_x1, boundary_y1 = boundary
734
- if len(bboxes) <= 1:
735
- return [
736
- {
737
- 'layout_bbox': [boundary_x0, boundary_y0, boundary_x1, boundary_y1],
738
- 'layout_label': LAYOUT_V,
739
- 'sub_layout': [],
740
- }
741
- ]
742
- """
743
- 接下来按照先水平后垂直的顺序进行切分
744
- """
745
- bboxes = paper_bbox_sort(
746
- bboxes, boundary_x1 - boundary_x0, boundary_y1 - boundary_y0
747
- )
748
- sorted_layouts = _horizontal_split(bboxes, boundary) # 通过水平分割出来的layout
749
- for i, layout in enumerate(sorted_layouts):
750
- x0, y0, x1, y1 = layout['layout_bbox']
751
- layout_type = layout['layout_label']
752
- if layout_type == LAYOUT_UNPROC: # 说明是非独占单行的,这些需要垂直切分
753
- v_split_layouts = _vertical_split(bboxes, [x0, y0, x1, y1])
754
- """
755
- 最后这里有个逻辑问题:如果这个函数只分离出来了一个column layout,那么这个layout分割肯定超出了算法能力范围。因为我们假定的是传进来的
756
- box已经把行全部剥离了,所以这里必须十多个列才可以。如果只剥离出来一个layout,并且是多个box,那么就说明这个layout是无法分割的,标记为LAYOUT_UNPROC
757
- """
758
- layout_label = LAYOUT_V
759
- if len(v_split_layouts) == 1:
760
- if len(v_split_layouts[0]['sub_layout']) == 0:
761
- layout_label = LAYOUT_UNPROC
762
- # logger.warning(f"WARNING: pageno={page_num}, 无法分割的layout: ", v_split_layouts)
763
- """
764
- 组合起来最终的layout
765
- """
766
- sorted_layouts[i] = {
767
- 'layout_bbox': [x0, y0, x1, y1],
768
- 'layout_label': layout_label,
769
- 'sub_layout': v_split_layouts,
770
- }
771
- layout['layout_label'] = LAYOUT_H
772
- """
773
- 水平和垂直方向都切分完毕了。此时还有一些未处理的,这些未处理的可能是因为水平和垂直方向都无法切分。
774
- 这些最后调用_try_horizontal_mult_block_split做一次水平多个block的联合切分,如果也不能切分最终就当做BAD_LAYOUT返回
775
- """
776
- # TODO
777
-
778
- return sorted_layouts
779
-
780
-
781
- def get_bboxes_layout(all_boxes: list, boundary: tuple, page_id: int):
782
- """
783
- 对利用layout排序之后的box,进行排序
784
- return:
785
- [
786
- {
787
- "layout_bbox": [x0, y0, x1, y1],
788
- "layout_label":"u|v|h|b", 未处理|垂直|水平|BAD_LAYOUT
789
- },
790
- ]
791
- """
792
-
793
- def _preorder_traversal(layout):
794
- """对sorted_layouts的叶子节点,也就是len(sub_layout)==0的节点进行排序。排序按照前序遍历的顺序,也就是从上到
795
- 下,从左到右的顺序."""
796
- sorted_layout_blocks = []
797
- for layout in layout:
798
- sub_layout = layout['sub_layout']
799
- if len(sub_layout) == 0:
800
- sorted_layout_blocks.append(layout)
801
- else:
802
- s = _preorder_traversal(sub_layout)
803
- sorted_layout_blocks.extend(s)
804
- return sorted_layout_blocks
805
-
806
- # -------------------------------------------------------------------------------------------------------------------------
807
- sorted_layouts = split_layout(
808
- all_boxes, boundary, page_id
809
- ) # 先切分成layout,得到一个Tree
810
- total_sorted_layout_blocks = _preorder_traversal(sorted_layouts)
811
- return total_sorted_layout_blocks, sorted_layouts
812
-
813
-
814
- def get_columns_cnt_of_layout(layout_tree):
815
- """获取一个layout的宽度."""
816
- max_width_list = [0] # 初始化一个元素,防止max,min函数报错
817
-
818
- for items in layout_tree: # 针对每一层(横切)计算列数,横着的算一列
819
- layout_type = items['layout_label']
820
- sub_layouts = items['sub_layout']
821
- if len(sub_layouts) == 0:
822
- max_width_list.append(1)
823
- else:
824
- if layout_type == LAYOUT_H:
825
- max_width_list.append(1)
826
- else:
827
- width = 0
828
- for sub_layout in sub_layouts:
829
- if len(sub_layout['sub_layout']) == 0:
830
- width += 1
831
- else:
832
- for lay in sub_layout['sub_layout']:
833
- width += get_columns_cnt_of_layout([lay])
834
- max_width_list.append(width)
835
-
836
- return max(max_width_list)
837
-
838
-
839
- def sort_with_layout(bboxes: list, page_width, page_height) -> (list, list):
840
- """输入是一个bbox的list.
841
-
842
- 获取到输入之后,先进行layout切分,然后对这些bbox进行排序。返回排序后的bboxes
843
- """
844
-
845
- new_bboxes = []
846
- for box in bboxes:
847
- # new_bboxes.append([box[0], box[1], box[2], box[3], None, None, None, 'text', None, None, None, None])
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 = []
874
- # 利用layout bbox每次框定一些box,然后排序
875
- for layout in layout_bboxes:
876
- lbox = layout['layout_bbox']
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
- )
881
- sorted_bboxes.extend(sorted_bbox)
882
-
883
- return sorted_bboxes, layout_bboxes
884
-
885
-
886
- def sort_text_block(text_block, layout_bboxes):
887
- """对一页的text_block进行排序."""
888
- sorted_text_bbox = []
889
- all_text_bbox = []
890
- # 做一个box=>text的映射
891
- box_to_text = {}
892
- for blk in text_block:
893
- box = blk['bbox']
894
- box_to_text[(box[0], box[1], box[2], box[3])] = blk
895
- all_text_bbox.append(box)
896
-
897
- # text_blocks_to_sort = []
898
- # for box in box_to_text.keys():
899
- # text_blocks_to_sort.append([box[0], box[1], box[2], box[3], None, None, None, 'text', None, None, None, None])
900
-
901
- # 按照layout_bboxes的顺序,对text_block进行排序
902
- for layout in layout_bboxes:
903
- layout_box = layout['layout_bbox']
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]
918
- for sb in text_bbox_in_layout:
919
- sorted_text_bbox.append(box_to_text[(sb[0], sb[1], sb[2], sb[3])])
920
-
921
- return sorted_text_bbox