docling 1.19.1__py3-none-any.whl → 1.20.0__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.
@@ -0,0 +1,236 @@
1
+ import logging
2
+ import random
3
+ from io import BytesIO
4
+ from pathlib import Path
5
+ from typing import Iterable, List, Optional, Union
6
+
7
+ import pypdfium2 as pdfium
8
+ from docling_parse.docling_parse import pdf_parser_v2
9
+ from PIL import Image, ImageDraw
10
+ from pypdfium2 import PdfPage
11
+
12
+ from docling.backend.abstract_backend import PdfDocumentBackend, PdfPageBackend
13
+ from docling.datamodel.base_models import BoundingBox, Cell, CoordOrigin, PageSize
14
+
15
+ _log = logging.getLogger(__name__)
16
+
17
+
18
+ class DoclingParseV2PageBackend(PdfPageBackend):
19
+ def __init__(
20
+ self, parser: pdf_parser_v2, document_hash: str, page_no: int, page_obj: PdfPage
21
+ ):
22
+ self._ppage = page_obj
23
+ parsed_page = parser.parse_pdf_from_key_on_page(document_hash, page_no)
24
+
25
+ self.valid = "pages" in parsed_page
26
+ if self.valid:
27
+ self._dpage = parsed_page["pages"][page_no]
28
+ else:
29
+ _log.info(
30
+ f"An error occured when loading page {page_no} of document {document_hash}."
31
+ )
32
+
33
+ def is_valid(self) -> bool:
34
+ return self.valid
35
+
36
+ def get_text_in_rect(self, bbox: BoundingBox) -> str:
37
+ if not self.valid:
38
+ return ""
39
+ # Find intersecting cells on the page
40
+ text_piece = ""
41
+ page_size = self.get_size()
42
+
43
+ parser_width = self._dpage["sanitized"]["dimension"]["width"]
44
+ parser_height = self._dpage["sanitized"]["dimension"]["height"]
45
+
46
+ scale = (
47
+ 1 # FIX - Replace with param in get_text_in_rect across backends (optional)
48
+ )
49
+
50
+ cells_data = self._dpage["sanitized"]["cells"]["data"]
51
+ cells_header = self._dpage["sanitized"]["cells"]["header"]
52
+
53
+ for i, cell_data in enumerate(cells_data):
54
+ x0 = cell_data[cells_header.index("x0")]
55
+ y0 = cell_data[cells_header.index("y0")]
56
+ x1 = cell_data[cells_header.index("x1")]
57
+ y1 = cell_data[cells_header.index("y1")]
58
+
59
+ cell_bbox = BoundingBox(
60
+ l=x0 * scale * page_size.width / parser_width,
61
+ b=y0 * scale * page_size.height / parser_height,
62
+ r=x1 * scale * page_size.width / parser_width,
63
+ t=y1 * scale * page_size.height / parser_height,
64
+ coord_origin=CoordOrigin.BOTTOMLEFT,
65
+ ).to_top_left_origin(page_height=page_size.height * scale)
66
+
67
+ overlap_frac = cell_bbox.intersection_area_with(bbox) / cell_bbox.area()
68
+
69
+ if overlap_frac > 0.5:
70
+ if len(text_piece) > 0:
71
+ text_piece += " "
72
+ text_piece += cell_data[cells_header.index("text")]
73
+
74
+ return text_piece
75
+
76
+ def get_text_cells(self) -> Iterable[Cell]:
77
+ cells: List[Cell] = []
78
+ cell_counter = 0
79
+
80
+ if not self.valid:
81
+ return cells
82
+
83
+ page_size = self.get_size()
84
+
85
+ parser_width = self._dpage["sanitized"]["dimension"]["width"]
86
+ parser_height = self._dpage["sanitized"]["dimension"]["height"]
87
+
88
+ cells_data = self._dpage["sanitized"]["cells"]["data"]
89
+ cells_header = self._dpage["sanitized"]["cells"]["header"]
90
+
91
+ for i, cell_data in enumerate(cells_data):
92
+ x0 = cell_data[cells_header.index("x0")]
93
+ y0 = cell_data[cells_header.index("y0")]
94
+ x1 = cell_data[cells_header.index("x1")]
95
+ y1 = cell_data[cells_header.index("y1")]
96
+
97
+ if x1 < x0:
98
+ x0, x1 = x1, x0
99
+ if y1 < y0:
100
+ y0, y1 = y1, y0
101
+
102
+ text_piece = cell_data[cells_header.index("text")]
103
+ cells.append(
104
+ Cell(
105
+ id=cell_counter,
106
+ text=text_piece,
107
+ bbox=BoundingBox(
108
+ # l=x0, b=y0, r=x1, t=y1,
109
+ l=x0 * page_size.width / parser_width,
110
+ b=y0 * page_size.height / parser_height,
111
+ r=x1 * page_size.width / parser_width,
112
+ t=y1 * page_size.height / parser_height,
113
+ coord_origin=CoordOrigin.BOTTOMLEFT,
114
+ ).to_top_left_origin(page_size.height),
115
+ )
116
+ )
117
+ cell_counter += 1
118
+
119
+ def draw_clusters_and_cells():
120
+ image = (
121
+ self.get_page_image()
122
+ ) # make new image to avoid drawing on the saved ones
123
+ draw = ImageDraw.Draw(image)
124
+ for c in cells:
125
+ x0, y0, x1, y1 = c.bbox.as_tuple()
126
+ cell_color = (
127
+ random.randint(30, 140),
128
+ random.randint(30, 140),
129
+ random.randint(30, 140),
130
+ )
131
+ draw.rectangle([(x0, y0), (x1, y1)], outline=cell_color)
132
+ image.show()
133
+
134
+ # draw_clusters_and_cells()
135
+
136
+ return cells
137
+
138
+ def get_bitmap_rects(self, scale: float = 1) -> Iterable[BoundingBox]:
139
+ AREA_THRESHOLD = 32 * 32
140
+
141
+ images = self._dpage["sanitized"]["images"]["data"]
142
+ images_header = self._dpage["sanitized"]["images"]["header"]
143
+
144
+ for row in images:
145
+ x0 = row[images_header.index("x0")]
146
+ y0 = row[images_header.index("y0")]
147
+ x1 = row[images_header.index("x1")]
148
+ y1 = row[images_header.index("y1")]
149
+
150
+ cropbox = BoundingBox.from_tuple(
151
+ (x0, y0, x1, y1), origin=CoordOrigin.BOTTOMLEFT
152
+ ).to_top_left_origin(self.get_size().height)
153
+
154
+ if cropbox.area() > AREA_THRESHOLD:
155
+ cropbox = cropbox.scaled(scale=scale)
156
+
157
+ yield cropbox
158
+
159
+ def get_page_image(
160
+ self, scale: float = 1, cropbox: Optional[BoundingBox] = None
161
+ ) -> Image.Image:
162
+
163
+ page_size = self.get_size()
164
+
165
+ if not cropbox:
166
+ cropbox = BoundingBox(
167
+ l=0,
168
+ r=page_size.width,
169
+ t=0,
170
+ b=page_size.height,
171
+ coord_origin=CoordOrigin.TOPLEFT,
172
+ )
173
+ padbox = BoundingBox(
174
+ l=0, r=0, t=0, b=0, coord_origin=CoordOrigin.BOTTOMLEFT
175
+ )
176
+ else:
177
+ padbox = cropbox.to_bottom_left_origin(page_size.height)
178
+ padbox.r = page_size.width - padbox.r
179
+ padbox.t = page_size.height - padbox.t
180
+
181
+ image = (
182
+ self._ppage.render(
183
+ scale=scale * 1.5,
184
+ rotation=0, # no additional rotation
185
+ crop=padbox.as_tuple(),
186
+ )
187
+ .to_pil()
188
+ .resize(size=(round(cropbox.width * scale), round(cropbox.height * scale)))
189
+ ) # We resize the image from 1.5x the given scale to make it sharper.
190
+
191
+ return image
192
+
193
+ def get_size(self) -> PageSize:
194
+ return PageSize(width=self._ppage.get_width(), height=self._ppage.get_height())
195
+
196
+ def unload(self):
197
+ self._ppage = None
198
+ self._dpage = None
199
+
200
+
201
+ class DoclingParseV2DocumentBackend(PdfDocumentBackend):
202
+ def __init__(self, path_or_stream: Union[BytesIO, Path], document_hash: str):
203
+ super().__init__(path_or_stream, document_hash)
204
+
205
+ self._pdoc = pdfium.PdfDocument(path_or_stream)
206
+ self.parser = pdf_parser_v2("fatal")
207
+
208
+ success = False
209
+ if isinstance(path_or_stream, BytesIO):
210
+ success = self.parser.load_document_from_bytesio(
211
+ document_hash, path_or_stream
212
+ )
213
+ elif isinstance(path_or_stream, Path):
214
+ success = self.parser.load_document(document_hash, str(path_or_stream))
215
+
216
+ if not success:
217
+ raise RuntimeError(
218
+ f"docling-parse could not load document {document_hash}."
219
+ )
220
+
221
+ def page_count(self) -> int:
222
+ return len(self._pdoc) # To be replaced with docling-parse API
223
+
224
+ def load_page(self, page_no: int) -> DoclingParseV2PageBackend:
225
+ return DoclingParseV2PageBackend(
226
+ self.parser, self.document_hash, page_no, self._pdoc[page_no]
227
+ )
228
+
229
+ def is_valid(self) -> bool:
230
+ return self.page_count() > 0
231
+
232
+ def unload(self):
233
+ super().unload()
234
+ self.parser.unload_document(self.document_hash)
235
+ self._pdoc.close()
236
+ self._pdoc = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: docling
3
- Version: 1.19.1
3
+ Version: 1.20.0
4
4
  Summary: Docling PDF conversion package
5
5
  Home-page: https://github.com/DS4SD/docling
6
6
  License: MIT
@@ -24,7 +24,7 @@ Requires-Dist: certifi (>=2024.7.4)
24
24
  Requires-Dist: deepsearch-glm (>=0.22.0,<0.23.0)
25
25
  Requires-Dist: docling-core (>=1.7.1,<2.0.0)
26
26
  Requires-Dist: docling-ibm-models (>=2.0.0,<3.0.0)
27
- Requires-Dist: docling-parse (>=1.4.1,<2.0.0)
27
+ Requires-Dist: docling-parse (>=1.6.0,<2.0.0)
28
28
  Requires-Dist: easyocr (>=1.7,<2.0)
29
29
  Requires-Dist: filetype (>=1.2.0,<2.0.0)
30
30
  Requires-Dist: huggingface_hub (>=0.23,<1)
@@ -2,6 +2,7 @@ docling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  docling/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  docling/backend/abstract_backend.py,sha256=clJtGxLedpLriEhpx7oyxjmlwMLPorkv-1tdfZm9GdA,1546
4
4
  docling/backend/docling_parse_backend.py,sha256=RUWWZbx2cUotZeeTkc-Lbg2k8MVFXFxaDjM4sPfaFZE,7475
5
+ docling/backend/docling_parse_v2_backend.py,sha256=Fieflqr-SSiNLMJi8qsPWw_7GX4bZQzyvDwneyW4Kr0,8207
5
6
  docling/backend/pypdfium2_backend.py,sha256=bIIImVM73wmcVcKMqjl4JF8CD-Qj2W5rZbI4G7clU4s,8877
6
7
  docling/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
8
  docling/cli/main.py,sha256=Q_5HTL2O20bMlF-U8Ac8ev4iqgLPyrwlHEyLAq6rezg,7913
@@ -27,8 +28,8 @@ docling/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
28
  docling/utils/export.py,sha256=bKLdbeUcR-rQsGPV1IqJkCHKMCv7X2QOHyxmjNuH3HE,4655
28
29
  docling/utils/layout_utils.py,sha256=FOFbL0hKzUoWXdZaeUvEtFqKv0IkPifIr4sdGW4suKs,31804
29
30
  docling/utils/utils.py,sha256=llhXSbIDNZ1MHOwBEfLHBAoJIAYI7QlPIonlI1jLUJ0,1208
30
- docling-1.19.1.dist-info/LICENSE,sha256=mBb7ErEcM8VS9OhiGHnQ2kk75HwPhr54W1Oiz3965MY,1088
31
- docling-1.19.1.dist-info/METADATA,sha256=hCQeq3JVB16CfTwtjjwnX5u9bWYjD0CsSbn9h1tZZTM,16800
32
- docling-1.19.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
33
- docling-1.19.1.dist-info/entry_points.txt,sha256=VOSzV77znM52dz5ysaDuJ0ijl1cnfrh1ZPg8od5OcTs,48
34
- docling-1.19.1.dist-info/RECORD,,
31
+ docling-1.20.0.dist-info/LICENSE,sha256=mBb7ErEcM8VS9OhiGHnQ2kk75HwPhr54W1Oiz3965MY,1088
32
+ docling-1.20.0.dist-info/METADATA,sha256=OMQd4Jk1nJnmJi7Q7tQ5wA0Y6z5sQg2njNvIXXISM4c,16800
33
+ docling-1.20.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
34
+ docling-1.20.0.dist-info/entry_points.txt,sha256=VOSzV77znM52dz5ysaDuJ0ijl1cnfrh1ZPg8od5OcTs,48
35
+ docling-1.20.0.dist-info/RECORD,,