magic-pdf 0.9.2__py3-none-any.whl → 0.9.3__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 (67) hide show
  1. magic_pdf/dict2md/ocr_mkcontent.py +1 -1
  2. magic_pdf/libs/Constants.py +3 -1
  3. magic_pdf/libs/config_reader.py +1 -1
  4. magic_pdf/libs/draw_bbox.py +10 -4
  5. magic_pdf/libs/version.py +1 -1
  6. magic_pdf/model/pdf_extract_kit.py +42 -297
  7. magic_pdf/model/sub_modules/layout/doclayout_yolo/DocLayoutYOLO.py +21 -0
  8. magic_pdf/model/sub_modules/mfd/__init__.py +0 -0
  9. magic_pdf/model/sub_modules/mfd/yolov8/YOLOv8.py +12 -0
  10. magic_pdf/model/sub_modules/mfd/yolov8/__init__.py +0 -0
  11. magic_pdf/model/sub_modules/mfr/__init__.py +0 -0
  12. magic_pdf/model/sub_modules/mfr/unimernet/Unimernet.py +98 -0
  13. magic_pdf/model/sub_modules/mfr/unimernet/__init__.py +0 -0
  14. magic_pdf/model/sub_modules/model_init.py +144 -0
  15. magic_pdf/model/sub_modules/model_utils.py +51 -0
  16. magic_pdf/model/sub_modules/ocr/__init__.py +0 -0
  17. magic_pdf/model/sub_modules/ocr/paddleocr/__init__.py +0 -0
  18. magic_pdf/model/sub_modules/ocr/paddleocr/ocr_utils.py +259 -0
  19. magic_pdf/model/sub_modules/ocr/paddleocr/ppocr_273_mod.py +168 -0
  20. magic_pdf/model/sub_modules/ocr/paddleocr/ppocr_291_mod.py +213 -0
  21. magic_pdf/model/sub_modules/reading_oreder/__init__.py +0 -0
  22. magic_pdf/model/sub_modules/reading_oreder/layoutreader/__init__.py +0 -0
  23. magic_pdf/model/sub_modules/reading_oreder/layoutreader/xycut.py +242 -0
  24. magic_pdf/model/sub_modules/table/__init__.py +0 -0
  25. magic_pdf/model/sub_modules/table/rapidtable/__init__.py +0 -0
  26. magic_pdf/model/sub_modules/table/rapidtable/rapid_table.py +14 -0
  27. magic_pdf/model/sub_modules/table/structeqtable/__init__.py +0 -0
  28. magic_pdf/model/{pek_sub_modules/structeqtable/StructTableModel.py → sub_modules/table/structeqtable/struct_eqtable.py} +3 -11
  29. magic_pdf/model/sub_modules/table/table_utils.py +11 -0
  30. magic_pdf/model/sub_modules/table/tablemaster/__init__.py +0 -0
  31. magic_pdf/model/{ppTableModel.py → sub_modules/table/tablemaster/tablemaster_paddle.py} +1 -1
  32. magic_pdf/para/para_split_v3.py +13 -15
  33. magic_pdf/pdf_parse_union_core_v2.py +56 -19
  34. magic_pdf/resources/model_config/model_configs.yaml +2 -1
  35. magic_pdf/tools/common.py +47 -3
  36. {magic_pdf-0.9.2.dist-info → magic_pdf-0.9.3.dist-info}/METADATA +9 -3
  37. {magic_pdf-0.9.2.dist-info → magic_pdf-0.9.3.dist-info}/RECORD +65 -44
  38. {magic_pdf-0.9.2.dist-info → magic_pdf-0.9.3.dist-info}/WHEEL +1 -1
  39. magic_pdf/model/pek_sub_modules/post_process.py +0 -36
  40. magic_pdf/model/pek_sub_modules/self_modify.py +0 -388
  41. /magic_pdf/model/{pek_sub_modules → sub_modules}/__init__.py +0 -0
  42. /magic_pdf/model/{pek_sub_modules/layoutlmv3 → sub_modules/layout}/__init__.py +0 -0
  43. /magic_pdf/model/{pek_sub_modules/structeqtable → sub_modules/layout/doclayout_yolo}/__init__.py +0 -0
  44. /magic_pdf/model/{v3 → sub_modules/layout/layoutlmv3}/__init__.py +0 -0
  45. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/backbone.py +0 -0
  46. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/beit.py +0 -0
  47. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/deit.py +0 -0
  48. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/__init__.py +0 -0
  49. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/__init__.py +0 -0
  50. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/cord.py +0 -0
  51. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/data_collator.py +0 -0
  52. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/funsd.py +0 -0
  53. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/image_utils.py +0 -0
  54. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/data/xfund.py +0 -0
  55. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/__init__.py +0 -0
  56. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/__init__.py +0 -0
  57. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/configuration_layoutlmv3.py +0 -0
  58. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/modeling_layoutlmv3.py +0 -0
  59. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/tokenization_layoutlmv3.py +0 -0
  60. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/layoutlmft/models/layoutlmv3/tokenization_layoutlmv3_fast.py +0 -0
  61. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/model_init.py +0 -0
  62. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/rcnn_vl.py +0 -0
  63. /magic_pdf/model/{pek_sub_modules → sub_modules/layout}/layoutlmv3/visualizer.py +0 -0
  64. /magic_pdf/model/{v3 → sub_modules/reading_oreder/layoutreader}/helpers.py +0 -0
  65. {magic_pdf-0.9.2.dist-info → magic_pdf-0.9.3.dist-info}/LICENSE.md +0 -0
  66. {magic_pdf-0.9.2.dist-info → magic_pdf-0.9.3.dist-info}/entry_points.txt +0 -0
  67. {magic_pdf-0.9.2.dist-info → magic_pdf-0.9.3.dist-info}/top_level.txt +0 -0
@@ -1,388 +0,0 @@
1
- import time
2
- import copy
3
- import base64
4
- import cv2
5
- import numpy as np
6
- from io import BytesIO
7
- from PIL import Image
8
-
9
- from paddleocr import PaddleOCR
10
- from paddleocr.ppocr.utils.logging import get_logger
11
- from paddleocr.ppocr.utils.utility import check_and_read, alpha_to_color, binarize_img
12
- from paddleocr.tools.infer.utility import draw_ocr_box_txt, get_rotate_crop_image, get_minarea_rect_crop
13
-
14
- from magic_pdf.libs.boxbase import __is_overlaps_y_exceeds_threshold
15
- from magic_pdf.pre_proc.ocr_dict_merge import merge_spans_to_line
16
-
17
- logger = get_logger()
18
-
19
-
20
- def img_decode(content: bytes):
21
- np_arr = np.frombuffer(content, dtype=np.uint8)
22
- return cv2.imdecode(np_arr, cv2.IMREAD_UNCHANGED)
23
-
24
-
25
- def check_img(img):
26
- if isinstance(img, bytes):
27
- img = img_decode(img)
28
- if isinstance(img, str):
29
- image_file = img
30
- img, flag_gif, flag_pdf = check_and_read(image_file)
31
- if not flag_gif and not flag_pdf:
32
- with open(image_file, 'rb') as f:
33
- img_str = f.read()
34
- img = img_decode(img_str)
35
- if img is None:
36
- try:
37
- buf = BytesIO()
38
- image = BytesIO(img_str)
39
- im = Image.open(image)
40
- rgb = im.convert('RGB')
41
- rgb.save(buf, 'jpeg')
42
- buf.seek(0)
43
- image_bytes = buf.read()
44
- data_base64 = str(base64.b64encode(image_bytes),
45
- encoding="utf-8")
46
- image_decode = base64.b64decode(data_base64)
47
- img_array = np.frombuffer(image_decode, np.uint8)
48
- img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
49
- except:
50
- logger.error("error in loading image:{}".format(image_file))
51
- return None
52
- if img is None:
53
- logger.error("error in loading image:{}".format(image_file))
54
- return None
55
- if isinstance(img, np.ndarray) and len(img.shape) == 2:
56
- img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
57
-
58
- return img
59
-
60
-
61
- def sorted_boxes(dt_boxes):
62
- """
63
- Sort text boxes in order from top to bottom, left to right
64
- args:
65
- dt_boxes(array):detected text boxes with shape [4, 2]
66
- return:
67
- sorted boxes(array) with shape [4, 2]
68
- """
69
- num_boxes = dt_boxes.shape[0]
70
- sorted_boxes = sorted(dt_boxes, key=lambda x: (x[0][1], x[0][0]))
71
- _boxes = list(sorted_boxes)
72
-
73
- for i in range(num_boxes - 1):
74
- for j in range(i, -1, -1):
75
- if abs(_boxes[j + 1][0][1] - _boxes[j][0][1]) < 10 and \
76
- (_boxes[j + 1][0][0] < _boxes[j][0][0]):
77
- tmp = _boxes[j]
78
- _boxes[j] = _boxes[j + 1]
79
- _boxes[j + 1] = tmp
80
- else:
81
- break
82
- return _boxes
83
-
84
-
85
- def bbox_to_points(bbox):
86
- """ 将bbox格式转换为四个顶点的数组 """
87
- x0, y0, x1, y1 = bbox
88
- return np.array([[x0, y0], [x1, y0], [x1, y1], [x0, y1]]).astype('float32')
89
-
90
-
91
- def points_to_bbox(points):
92
- """ 将四个顶点的数组转换为bbox格式 """
93
- x0, y0 = points[0]
94
- x1, _ = points[1]
95
- _, y1 = points[2]
96
- return [x0, y0, x1, y1]
97
-
98
-
99
- def merge_intervals(intervals):
100
- # Sort the intervals based on the start value
101
- intervals.sort(key=lambda x: x[0])
102
-
103
- merged = []
104
- for interval in intervals:
105
- # If the list of merged intervals is empty or if the current
106
- # interval does not overlap with the previous, simply append it.
107
- if not merged or merged[-1][1] < interval[0]:
108
- merged.append(interval)
109
- else:
110
- # Otherwise, there is overlap, so we merge the current and previous intervals.
111
- merged[-1][1] = max(merged[-1][1], interval[1])
112
-
113
- return merged
114
-
115
-
116
- def remove_intervals(original, masks):
117
- # Merge all mask intervals
118
- merged_masks = merge_intervals(masks)
119
-
120
- result = []
121
- original_start, original_end = original
122
-
123
- for mask in merged_masks:
124
- mask_start, mask_end = mask
125
-
126
- # If the mask starts after the original range, ignore it
127
- if mask_start > original_end:
128
- continue
129
-
130
- # If the mask ends before the original range starts, ignore it
131
- if mask_end < original_start:
132
- continue
133
-
134
- # Remove the masked part from the original range
135
- if original_start < mask_start:
136
- result.append([original_start, mask_start - 1])
137
-
138
- original_start = max(mask_end + 1, original_start)
139
-
140
- # Add the remaining part of the original range, if any
141
- if original_start <= original_end:
142
- result.append([original_start, original_end])
143
-
144
- return result
145
-
146
-
147
- def update_det_boxes(dt_boxes, mfd_res):
148
- new_dt_boxes = []
149
- for text_box in dt_boxes:
150
- text_bbox = points_to_bbox(text_box)
151
- masks_list = []
152
- for mf_box in mfd_res:
153
- mf_bbox = mf_box['bbox']
154
- if __is_overlaps_y_exceeds_threshold(text_bbox, mf_bbox):
155
- masks_list.append([mf_bbox[0], mf_bbox[2]])
156
- text_x_range = [text_bbox[0], text_bbox[2]]
157
- text_remove_mask_range = remove_intervals(text_x_range, masks_list)
158
- temp_dt_box = []
159
- for text_remove_mask in text_remove_mask_range:
160
- temp_dt_box.append(bbox_to_points([text_remove_mask[0], text_bbox[1], text_remove_mask[1], text_bbox[3]]))
161
- if len(temp_dt_box) > 0:
162
- new_dt_boxes.extend(temp_dt_box)
163
- return new_dt_boxes
164
-
165
-
166
- def merge_overlapping_spans(spans):
167
- """
168
- Merges overlapping spans on the same line.
169
-
170
- :param spans: A list of span coordinates [(x1, y1, x2, y2), ...]
171
- :return: A list of merged spans
172
- """
173
- # Return an empty list if the input spans list is empty
174
- if not spans:
175
- return []
176
-
177
- # Sort spans by their starting x-coordinate
178
- spans.sort(key=lambda x: x[0])
179
-
180
- # Initialize the list of merged spans
181
- merged = []
182
- for span in spans:
183
- # Unpack span coordinates
184
- x1, y1, x2, y2 = span
185
- # If the merged list is empty or there's no horizontal overlap, add the span directly
186
- if not merged or merged[-1][2] < x1:
187
- merged.append(span)
188
- else:
189
- # If there is horizontal overlap, merge the current span with the previous one
190
- last_span = merged.pop()
191
- # Update the merged span's top-left corner to the smaller (x1, y1) and bottom-right to the larger (x2, y2)
192
- x1 = min(last_span[0], x1)
193
- y1 = min(last_span[1], y1)
194
- x2 = max(last_span[2], x2)
195
- y2 = max(last_span[3], y2)
196
- # Add the merged span back to the list
197
- merged.append((x1, y1, x2, y2))
198
-
199
- # Return the list of merged spans
200
- return merged
201
-
202
-
203
- def merge_det_boxes(dt_boxes):
204
- """
205
- Merge detection boxes.
206
-
207
- This function takes a list of detected bounding boxes, each represented by four corner points.
208
- The goal is to merge these bounding boxes into larger text regions.
209
-
210
- Parameters:
211
- dt_boxes (list): A list containing multiple text detection boxes, where each box is defined by four corner points.
212
-
213
- Returns:
214
- list: A list containing the merged text regions, where each region is represented by four corner points.
215
- """
216
- # Convert the detection boxes into a dictionary format with bounding boxes and type
217
- dt_boxes_dict_list = []
218
- for text_box in dt_boxes:
219
- text_bbox = points_to_bbox(text_box)
220
- text_box_dict = {
221
- 'bbox': text_bbox,
222
- 'type': 'text',
223
- }
224
- dt_boxes_dict_list.append(text_box_dict)
225
-
226
- # Merge adjacent text regions into lines
227
- lines = merge_spans_to_line(dt_boxes_dict_list)
228
-
229
- # Initialize a new list for storing the merged text regions
230
- new_dt_boxes = []
231
- for line in lines:
232
- line_bbox_list = []
233
- for span in line:
234
- line_bbox_list.append(span['bbox'])
235
-
236
- # Merge overlapping text regions within the same line
237
- merged_spans = merge_overlapping_spans(line_bbox_list)
238
-
239
- # Convert the merged text regions back to point format and add them to the new detection box list
240
- for span in merged_spans:
241
- new_dt_boxes.append(bbox_to_points(span))
242
-
243
- return new_dt_boxes
244
-
245
-
246
- class ModifiedPaddleOCR(PaddleOCR):
247
- def ocr(self, img, det=True, rec=True, cls=True, bin=False, inv=False, mfd_res=None, alpha_color=(255, 255, 255)):
248
- """
249
- OCR with PaddleOCR
250
- args:
251
- img: img for OCR, support ndarray, img_path and list or ndarray
252
- det: use text detection or not. If False, only rec will be exec. Default is True
253
- rec: use text recognition or not. If False, only det will be exec. Default is True
254
- cls: use angle classifier or not. Default is True. If True, the text with rotation of 180 degrees can be recognized. If no text is rotated by 180 degrees, use cls=False to get better performance. Text with rotation of 90 or 270 degrees can be recognized even if cls=False.
255
- bin: binarize image to black and white. Default is False.
256
- inv: invert image colors. Default is False.
257
- alpha_color: set RGB color Tuple for transparent parts replacement. Default is pure white.
258
- """
259
- assert isinstance(img, (np.ndarray, list, str, bytes))
260
- if isinstance(img, list) and det == True:
261
- logger.error('When input a list of images, det must be false')
262
- exit(0)
263
- if cls == True and self.use_angle_cls == False:
264
- pass
265
- # logger.warning(
266
- # 'Since the angle classifier is not initialized, it will not be used during the forward process'
267
- # )
268
-
269
- img = check_img(img)
270
- # for infer pdf file
271
- if isinstance(img, list):
272
- if self.page_num > len(img) or self.page_num == 0:
273
- self.page_num = len(img)
274
- imgs = img[:self.page_num]
275
- else:
276
- imgs = [img]
277
-
278
- def preprocess_image(_image):
279
- _image = alpha_to_color(_image, alpha_color)
280
- if inv:
281
- _image = cv2.bitwise_not(_image)
282
- if bin:
283
- _image = binarize_img(_image)
284
- return _image
285
-
286
- if det and rec:
287
- ocr_res = []
288
- for idx, img in enumerate(imgs):
289
- img = preprocess_image(img)
290
- dt_boxes, rec_res, _ = self.__call__(img, cls, mfd_res=mfd_res)
291
- if not dt_boxes and not rec_res:
292
- ocr_res.append(None)
293
- continue
294
- tmp_res = [[box.tolist(), res]
295
- for box, res in zip(dt_boxes, rec_res)]
296
- ocr_res.append(tmp_res)
297
- return ocr_res
298
- elif det and not rec:
299
- ocr_res = []
300
- for idx, img in enumerate(imgs):
301
- img = preprocess_image(img)
302
- dt_boxes, elapse = self.text_detector(img)
303
- if not dt_boxes:
304
- ocr_res.append(None)
305
- continue
306
- tmp_res = [box.tolist() for box in dt_boxes]
307
- ocr_res.append(tmp_res)
308
- return ocr_res
309
- else:
310
- ocr_res = []
311
- cls_res = []
312
- for idx, img in enumerate(imgs):
313
- if not isinstance(img, list):
314
- img = preprocess_image(img)
315
- img = [img]
316
- if self.use_angle_cls and cls:
317
- img, cls_res_tmp, elapse = self.text_classifier(img)
318
- if not rec:
319
- cls_res.append(cls_res_tmp)
320
- rec_res, elapse = self.text_recognizer(img)
321
- ocr_res.append(rec_res)
322
- if not rec:
323
- return cls_res
324
- return ocr_res
325
-
326
- def __call__(self, img, cls=True, mfd_res=None):
327
- time_dict = {'det': 0, 'rec': 0, 'cls': 0, 'all': 0}
328
-
329
- if img is None:
330
- logger.debug("no valid image provided")
331
- return None, None, time_dict
332
-
333
- start = time.time()
334
- ori_im = img.copy()
335
- dt_boxes, elapse = self.text_detector(img)
336
- time_dict['det'] = elapse
337
-
338
- if dt_boxes is None:
339
- logger.debug("no dt_boxes found, elapsed : {}".format(elapse))
340
- end = time.time()
341
- time_dict['all'] = end - start
342
- return None, None, time_dict
343
- else:
344
- logger.debug("dt_boxes num : {}, elapsed : {}".format(
345
- len(dt_boxes), elapse))
346
- img_crop_list = []
347
-
348
- dt_boxes = sorted_boxes(dt_boxes)
349
-
350
- dt_boxes = merge_det_boxes(dt_boxes)
351
-
352
- if mfd_res:
353
- bef = time.time()
354
- dt_boxes = update_det_boxes(dt_boxes, mfd_res)
355
- aft = time.time()
356
- logger.debug("split text box by formula, new dt_boxes num : {}, elapsed : {}".format(
357
- len(dt_boxes), aft - bef))
358
-
359
- for bno in range(len(dt_boxes)):
360
- tmp_box = copy.deepcopy(dt_boxes[bno])
361
- if self.args.det_box_type == "quad":
362
- img_crop = get_rotate_crop_image(ori_im, tmp_box)
363
- else:
364
- img_crop = get_minarea_rect_crop(ori_im, tmp_box)
365
- img_crop_list.append(img_crop)
366
- if self.use_angle_cls and cls:
367
- img_crop_list, angle_list, elapse = self.text_classifier(
368
- img_crop_list)
369
- time_dict['cls'] = elapse
370
- logger.debug("cls num : {}, elapsed : {}".format(
371
- len(img_crop_list), elapse))
372
-
373
- rec_res, elapse = self.text_recognizer(img_crop_list)
374
- time_dict['rec'] = elapse
375
- logger.debug("rec_res num : {}, elapsed : {}".format(
376
- len(rec_res), elapse))
377
- if self.args.save_crop_res:
378
- self.draw_crop_rec_res(self.args.crop_res_save_dir, img_crop_list,
379
- rec_res)
380
- filter_boxes, filter_rec_res = [], []
381
- for box, rec_result in zip(dt_boxes, rec_res):
382
- text, score = rec_result
383
- if score >= self.drop_score:
384
- filter_boxes.append(box)
385
- filter_rec_res.append(rec_result)
386
- end = time.time()
387
- time_dict['all'] = end - start
388
- return filter_boxes, filter_rec_res, time_dict