9
9
LINE_STOP_FLAG = ['.' , '!' , '?' , '。' , '!' , '?' ,":" , ":" , ")" , ")" , ";" ]
10
10
INLINE_EQUATION = ContentType .InlineEquation
11
11
INTERLINE_EQUATION = ContentType .InterlineEquation
12
- TEXT = "text"
12
+ TEXT = ContentType . Text
13
13
14
14
15
15
def __get_span_text (span ):
@@ -20,7 +20,7 @@ def __get_span_text(span):
20
20
return c
21
21
22
22
23
- def __detect_list_lines (lines , new_layout_bboxes , lang = 'en' ):
23
+ def __detect_list_lines (lines , new_layout_bboxes , lang ):
24
24
"""
25
25
探测是否包含了列表,并且把列表的行分开.
26
26
这样的段落特点是,顶格字母大写/数字,紧跟着几行缩进的。缩进的行首字母含小写的。
@@ -315,11 +315,14 @@ def __split_para_in_layoutbox(lines_group, new_layout_bbox, lang="en", char_avg_
315
315
316
316
return layout_paras , list_info
317
317
318
- def __connect_list_inter_layout (layout_paras , new_layout_bbox , layout_list_info , page_num , lang = "en" ):
318
+ def __connect_list_inter_layout (layout_paras , new_layout_bbox , layout_list_info , page_num , lang ):
319
319
"""
320
- 如果上个layout的最后一个段落是列表,下一个layout的第一个段落也是列表,那么将他们连接起来。
320
+ 如果上个layout的最后一个段落是列表,下一个layout的第一个段落也是列表,那么将他们连接起来。 TODO 因为没有区分列表和段落,所以这个方法暂时不实现。
321
321
根据layout_list_info判断是不是列表。,下个layout的第一个段如果不是列表,那么看他们是否有几行都有相同的缩进。
322
322
"""
323
+ if len (layout_paras )== 0 or len (layout_list_info )== 0 : # 0的时候最后的return 会出错
324
+ return layout_paras , [False , False ]
325
+
323
326
for i in range (1 , len (layout_paras )):
324
327
pre_layout_list_info = layout_list_info [i - 1 ]
325
328
next_layout_list_info = layout_list_info [i ]
@@ -345,7 +348,37 @@ def __connect_list_inter_layout(layout_paras, new_layout_bbox, layout_list_info,
345
348
pre_last_para .extend (may_list_lines )
346
349
layout_paras [i ] = layout_paras [i ][len (may_list_lines ):]
347
350
348
- return layout_paras
351
+ return layout_paras , [layout_list_info [0 ][0 ], layout_list_info [- 1 ][1 ]] # 同时还返回了这个页面级别的开头、结尾是不是列表的信息
352
+
353
+
354
+ def __connect_list_inter_page (pre_page_paras , next_page_paras , pre_page_layout_bbox , next_page_layout_bbox , pre_page_list_info , next_page_list_info , page_num , lang ):
355
+ """
356
+ 如果上个layout的最后一个段落是列表,下一个layout的第一个段落也是列表,那么将他们连接起来。 TODO 因为没有区分列表和段落,所以这个方法暂时不实现。
357
+ 根据layout_list_info判断是不是列表。,下个layout的第一个段如果不是列表,那么看他们是否有几行都有相同的缩进。
358
+ """
359
+ if len (pre_page_paras )== 0 or len (next_page_paras )== 0 : # 0的时候最后的return 会出错
360
+ return False
361
+
362
+ if pre_page_list_info [1 ] and not next_page_list_info [0 ]: # 前一个是列表结尾,后一个是非列表开头,此时检测是否有相同的缩进
363
+ logger .info (f"连接page { page_num } 内的list" )
364
+ # 向layout_paras[i] 寻找开头具有相同缩进的连续的行
365
+ may_list_lines = []
366
+ for j in range (len (next_page_paras [0 ])):
367
+ line = next_page_paras [0 ][j ]
368
+ if len (line )== 1 : # 只可能是一行,多行情况再需要分析了
369
+ if line [0 ]['bbox' ][0 ] > __find_layout_bbox_by_line (line [0 ]['bbox' ], next_page_layout_bbox )[0 ]:
370
+ may_list_lines .append (line [0 ])
371
+ else :
372
+ break
373
+ else :
374
+ break
375
+ # 如果这些行的缩进是相等的,那么连到上一个layout的最后一个段落上。
376
+ if len (may_list_lines )> 0 and len (set ([x ['bbox' ][0 ] for x in may_list_lines ]))== 1 :
377
+ pre_page_paras [- 1 ].append (may_list_lines )
378
+ next_page_paras [0 ] = next_page_paras [0 ][len (may_list_lines ):]
379
+ return True
380
+
381
+ return False
349
382
350
383
351
384
def __find_layout_bbox_by_line (line_bbox , layout_bboxes ):
@@ -358,7 +391,7 @@ def __find_layout_bbox_by_line(line_bbox, layout_bboxes):
358
391
return None
359
392
360
393
361
- def __connect_para_inter_layoutbox (layout_paras , new_layout_bbox , lang = "en" ):
394
+ def __connect_para_inter_layoutbox (layout_paras , new_layout_bbox , lang ):
362
395
"""
363
396
layout之间进行分段。
364
397
主要是计算前一个layOut的最后一行和后一个layout的第一行是否可以连接。
@@ -368,10 +401,19 @@ def __connect_para_inter_layoutbox(layout_paras, new_layout_bbox, lang="en"):
368
401
369
402
"""
370
403
connected_layout_paras = []
404
+ if len (layout_paras )== 0 :
405
+ return connected_layout_paras
406
+
371
407
connected_layout_paras .append (layout_paras [0 ])
372
408
for i in range (1 , len (layout_paras )):
373
- pre_last_line = layout_paras [i - 1 ][- 1 ][- 1 ]
374
- next_first_line = layout_paras [i ][0 ][0 ]
409
+ try :
410
+ if len (layout_paras [i ])== 0 or len (layout_paras [i - 1 ])== 0 : # TODO 考虑连接问题,
411
+ continue
412
+ pre_last_line = layout_paras [i - 1 ][- 1 ][- 1 ]
413
+ next_first_line = layout_paras [i ][0 ][0 ]
414
+ except Exception as e :
415
+ logger .error (f"page layout { i } has no line" )
416
+ continue
375
417
pre_last_line_text = '' .join ([__get_span_text (span ) for span in pre_last_line ['spans' ]])
376
418
pre_last_line_type = pre_last_line ['spans' ][- 1 ]['type' ]
377
419
next_first_line_text = '' .join ([__get_span_text (span ) for span in next_first_line ['spans' ]])
@@ -400,15 +442,15 @@ def __connect_para_inter_layoutbox(layout_paras, new_layout_bbox, lang="en"):
400
442
return connected_layout_paras
401
443
402
444
403
- def __connect_para_inter_page (pre_page_paras , next_page_paras , pre_page_layout_bbox , next_page_layout_bbox , lang ):
445
+ def __connect_para_inter_page (pre_page_paras , next_page_paras , pre_page_layout_bbox , next_page_layout_bbox , page_num , lang ):
404
446
"""
405
447
连接起来相邻两个页面的段落——前一个页面最后一个段落和后一个页面的第一个段落。
406
448
是否可以连接的条件:
407
449
1. 前一个页面的最后一个段落最后一行沾满整个行。并且没有结尾符号。
408
450
2. 后一个页面的第一个段落第一行没有空白开头。
409
451
"""
410
452
# 有的页面可能压根没有文字
411
- if len (pre_page_paras )== 0 or len (next_page_paras )== 0 :
453
+ if len (pre_page_paras )== 0 or len (next_page_paras )== 0 or len ( pre_page_paras [ 0 ]) == 0 or len ( next_page_paras [ 0 ]) == 0 : # TODO [[]]为什么出现在pre_page_paras里?
412
454
return False
413
455
pre_last_para = pre_page_paras [- 1 ][- 1 ]
414
456
next_first_para = next_page_paras [0 ][0 ]
@@ -436,8 +478,85 @@ def __connect_para_inter_page(pre_page_paras, next_page_paras, pre_page_layout_b
436
478
else :
437
479
return False
438
480
481
+ def find_consecutive_true_regions (input_array ):
482
+ start_index = None # 连续True区域的起始索引
483
+ regions = [] # 用于保存所有连续True区域的起始和结束索引
484
+
485
+ for i in range (len (input_array )):
486
+ # 如果我们找到了一个True值,并且当前并没有在连续True区域中
487
+ if input_array [i ] and start_index is None :
488
+ start_index = i # 记录连续True区域的起始索引
439
489
440
- def __do_split (blocks , layout_bboxes , new_layout_bbox , page_num , lang = "en" ):
490
+ # 如果我们找到了一个False值,并且当前在连续True区域中
491
+ elif not input_array [i ] and start_index is not None :
492
+ # 如果连续True区域长度大于1,那么将其添加到结果列表中
493
+ if i - start_index > 1 :
494
+ regions .append ((start_index , i - 1 ))
495
+ start_index = None # 重置起始索引
496
+
497
+ # 如果最后一个元素是True,那么需要将最后一个连续True区域加入到结果列表中
498
+ if start_index is not None and len (input_array ) - start_index > 1 :
499
+ regions .append ((start_index , len (input_array )- 1 ))
500
+
501
+ return regions
502
+
503
+
504
+ def __connect_middle_align_text (page_paras , new_layout_bbox , page_num , lang , debug_mode ):
505
+ """
506
+ 找出来中间对齐的连续单行文本,如果连续行高度相同,那么合并为一个段落。
507
+ 一个line居中的条件是:
508
+ 1. 水平中心点跨越layout的中心点。
509
+ 2. 左右两侧都有空白
510
+ """
511
+
512
+ for layout_i , layout_para in enumerate (page_paras ):
513
+ layout_box = new_layout_bbox [layout_i ]
514
+ single_line_paras_tag = []
515
+ for i in range (len (layout_para )):
516
+ single_line_paras_tag .append (len (layout_para [i ])== 1 and layout_para [i ][0 ]['spans' ][0 ]['type' ]== TEXT )
517
+
518
+ """找出来连续的单行文本,如果连续行高度相同,那么合并为一个段落。"""
519
+ consecutive_single_line_indices = find_consecutive_true_regions (single_line_paras_tag )
520
+ if len (consecutive_single_line_indices )> 0 :
521
+ index_offset = 0
522
+ """检查这些行是否是高度相同的,居中的"""
523
+ for start , end in consecutive_single_line_indices :
524
+ start += index_offset
525
+ end += index_offset
526
+ line_hi = np .array ([line [0 ]['bbox' ][3 ]- line [0 ]['bbox' ][1 ] for line in layout_para [start :end + 1 ]])
527
+ first_line_text = '' .join ([__get_span_text (span ) for span in layout_para [start ][0 ]['spans' ]])
528
+ if "Table" in first_line_text or "Figure" in first_line_text :
529
+ pass
530
+ if debug_mode :
531
+ logger .info (line_hi .std ())
532
+
533
+ if line_hi .std ()< 2 :
534
+ """行高度相同,那么判断是否居中"""
535
+ all_left_x0 = [line [0 ]['bbox' ][0 ] for line in layout_para [start :end + 1 ]]
536
+ all_right_x1 = [line [0 ]['bbox' ][2 ] for line in layout_para [start :end + 1 ]]
537
+ layout_center = (layout_box [0 ] + layout_box [2 ]) / 2
538
+ if all ([x0 < layout_center < x1 for x0 , x1 in zip (all_left_x0 , all_right_x1 )]) \
539
+ and not all ([x0 == layout_box [0 ] for x0 in all_left_x0 ]) \
540
+ and not all ([x1 == layout_box [2 ] for x1 in all_right_x1 ]):
541
+ merge_para = [l [0 ] for l in layout_para [start :end + 1 ]]
542
+ para_text = '' .join ([__get_span_text (span ) for line in merge_para for span in line ['spans' ]])
543
+ if debug_mode :
544
+ logger .info (para_text )
545
+ layout_para [start :end + 1 ] = [merge_para ]
546
+ index_offset -= end - start
547
+
548
+ return
549
+
550
+
551
+ def __merge_signle_list_text (page_paras , new_layout_bbox , page_num , lang ):
552
+ """
553
+ 找出来连续的单行文本,如果首行顶格,接下来的几个单行段落缩进对齐,那么合并为一个段落。
554
+ """
555
+
556
+ pass
557
+
558
+
559
+ def __do_split_page (blocks , layout_bboxes , new_layout_bbox , page_num , lang ):
441
560
"""
442
561
根据line和layout情况进行分段
443
562
先实现一个根据行末尾特征分段的简单方法。
@@ -451,35 +570,54 @@ def __do_split(blocks, layout_bboxes, new_layout_bbox, page_num, lang="en"):
451
570
"""
452
571
lines_group = __group_line_by_layout (blocks , layout_bboxes , lang ) # block内分段
453
572
layout_paras , layout_list_info = __split_para_in_layoutbox (lines_group , new_layout_bbox , lang ) # layout内分段
454
- layout_paras2 = __connect_list_inter_layout (layout_paras , new_layout_bbox , layout_list_info , page_num , lang ) # layout之间连接列表段落
573
+ layout_paras2 , page_list_info = __connect_list_inter_layout (layout_paras , new_layout_bbox , layout_list_info , page_num , lang ) # layout之间连接列表段落
455
574
connected_layout_paras = __connect_para_inter_layoutbox (layout_paras2 , new_layout_bbox , lang ) # layout间链接段落
456
575
457
- return connected_layout_paras
458
-
459
576
460
- def para_split (pdf_info_dict , lang = "en" ):
577
+ return connected_layout_paras , page_list_info
578
+
579
+
580
+ def para_split (pdf_info_dict , debug_mode , lang = "en" ):
461
581
"""
462
582
根据line和layout情况进行分段
463
583
"""
464
584
new_layout_of_pages = [] # 数组的数组,每个元素是一个页面的layoutS
585
+ all_page_list_info = [] # 保存每个页面开头和结尾是否是列表
465
586
for page_num , page in pdf_info_dict .items ():
466
587
blocks = page ['preproc_blocks' ]
467
588
layout_bboxes = page ['layout_bboxes' ]
468
589
new_layout_bbox = __common_pre_proc (blocks , layout_bboxes )
469
590
new_layout_of_pages .append (new_layout_bbox )
470
- splited_blocks = __do_split (blocks , layout_bboxes , new_layout_bbox , page_num , lang )
591
+ splited_blocks , page_list_info = __do_split_page (blocks , layout_bboxes , new_layout_bbox , page_num , lang )
592
+ all_page_list_info .append (page_list_info )
471
593
page ['para_blocks' ] = splited_blocks
472
594
473
595
"""连接页面与页面之间的可能合并的段落"""
474
596
pdf_infos = list (pdf_info_dict .values ())
475
- for i , page in enumerate (pdf_info_dict .values ()):
476
- if i == 0 :
597
+ for page_num , page in enumerate (pdf_info_dict .values ()):
598
+ if page_num == 0 :
477
599
continue
478
- pre_page_paras = pdf_infos [i - 1 ]['para_blocks' ]
479
- next_page_paras = pdf_infos [i ]['para_blocks' ]
480
- pre_page_layout_bbox = new_layout_of_pages [i - 1 ]
481
- next_page_layout_bbox = new_layout_of_pages [i ]
600
+ pre_page_paras = pdf_infos [page_num - 1 ]['para_blocks' ]
601
+ next_page_paras = pdf_infos [page_num ]['para_blocks' ]
602
+ pre_page_layout_bbox = new_layout_of_pages [page_num - 1 ]
603
+ next_page_layout_bbox = new_layout_of_pages [page_num ]
482
604
483
- is_conn = __connect_para_inter_page (pre_page_paras , next_page_paras , pre_page_layout_bbox , next_page_layout_bbox , lang )
484
- if is_conn :
485
- logger .info (f"连接了第{ i - 1 } 页和第{ i } 页的段落" )
605
+ is_conn = __connect_para_inter_page (pre_page_paras , next_page_paras , pre_page_layout_bbox , next_page_layout_bbox , page_num , lang )
606
+ if debug_mode :
607
+ if is_conn :
608
+ logger .info (f"连接了第{ page_num - 1 } 页和第{ page_num } 页的段落" )
609
+
610
+ is_list_conn = __connect_list_inter_page (pre_page_paras , next_page_paras , pre_page_layout_bbox , next_page_layout_bbox , all_page_list_info [page_num - 1 ], all_page_list_info [page_num ], page_num , lang )
611
+ if debug_mode :
612
+ if is_list_conn :
613
+ logger .info (f"连接了第{ page_num - 1 } 页和第{ page_num } 页的列表段落" )
614
+
615
+ """接下来可能会漏掉一些特别的一些可以合并的内容,对他们进行段落连接
616
+ 1. 正文中有时出现一个行顶格,接下来几行缩进的情况。
617
+ 2. 居中的一些连续单行,如果高度相同,那么可能是一个段落。
618
+ """
619
+ for page_num , page in enumerate (pdf_info_dict .values ()):
620
+ page_paras = page ['para_blocks' ]
621
+ new_layout_bbox = new_layout_of_pages [page_num ]
622
+ __connect_middle_align_text (page_paras , new_layout_bbox , page_num , lang , debug_mode = debug_mode )
623
+ __merge_signle_list_text (page_paras , new_layout_bbox , page_num , lang )
0 commit comments