custom-layoutparser 0.1.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.
Files changed (36) hide show
  1. custom_layoutparser-0.1.0.dist-info/METADATA +5 -0
  2. custom_layoutparser-0.1.0.dist-info/RECORD +36 -0
  3. custom_layoutparser-0.1.0.dist-info/WHEEL +5 -0
  4. custom_layoutparser-0.1.0.dist-info/top_level.txt +1 -0
  5. layoutparser/__init__.py +89 -0
  6. layoutparser/elements/__init__.py +25 -0
  7. layoutparser/elements/base.py +275 -0
  8. layoutparser/elements/errors.py +26 -0
  9. layoutparser/elements/layout.py +348 -0
  10. layoutparser/elements/layout_elements.py +1352 -0
  11. layoutparser/elements/utils.py +82 -0
  12. layoutparser/file_utils.py +235 -0
  13. layoutparser/io/__init__.py +2 -0
  14. layoutparser/io/basic.py +148 -0
  15. layoutparser/io/pdf.py +225 -0
  16. layoutparser/models/__init__.py +18 -0
  17. layoutparser/models/auto_layoutmodel.py +70 -0
  18. layoutparser/models/base_catalog.py +34 -0
  19. layoutparser/models/base_layoutmodel.py +88 -0
  20. layoutparser/models/detectron2/__init__.py +18 -0
  21. layoutparser/models/detectron2/catalog.py +142 -0
  22. layoutparser/models/detectron2/layoutmodel.py +168 -0
  23. layoutparser/models/effdet/__init__.py +16 -0
  24. layoutparser/models/effdet/catalog.py +88 -0
  25. layoutparser/models/effdet/layoutmodel.py +256 -0
  26. layoutparser/models/model_config.py +133 -0
  27. layoutparser/models/paddledetection/__init__.py +17 -0
  28. layoutparser/models/paddledetection/catalog.py +214 -0
  29. layoutparser/models/paddledetection/layoutmodel.py +297 -0
  30. layoutparser/ocr/__init__.py +16 -0
  31. layoutparser/ocr/base.py +41 -0
  32. layoutparser/ocr/gcv_agent.py +288 -0
  33. layoutparser/ocr/tesseract_agent.py +193 -0
  34. layoutparser/tools/__init__.py +5 -0
  35. layoutparser/tools/shape_operations.py +167 -0
  36. layoutparser/visualization.py +571 -0
@@ -0,0 +1,348 @@
1
+ # Copyright 2021 The Layout sParser team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import List, Union, Dict, Dict, Any, Optional
16
+ from collections.abc import MutableSequence, Iterable
17
+ from copy import copy
18
+
19
+ import pandas as pd
20
+
21
+ from .base import BaseCoordElement, BaseLayoutElement
22
+ from .layout_elements import (
23
+ Interval,
24
+ Rectangle,
25
+ Quadrilateral,
26
+ TextBlock,
27
+ ALL_BASECOORD_ELEMENTS,
28
+ BASECOORD_ELEMENT_NAMEMAP,
29
+ BASECOORD_ELEMENT_INDEXMAP,
30
+ )
31
+
32
+
33
+ class Layout(MutableSequence):
34
+ """
35
+ The :obj:`Layout` class id designed for processing a list of layout elements
36
+ on a page. It stores the layout elements in a list and the related `page_data`,
37
+ and provides handy APIs for processing all the layout elements in batch. `
38
+
39
+ Args:
40
+ blocks (:obj:`list`):
41
+ A list of layout element blocks
42
+ page_data (Dict, optional):
43
+ A dictionary storing the page (canvas) related information
44
+ like `height`, `width`, etc. It should be passed in as a
45
+ keyword argument to avoid any confusion.
46
+ Defaults to None.
47
+ """
48
+
49
+ def __init__(self, blocks: Optional[List] = None, *, page_data: Dict = None):
50
+
51
+ if not (
52
+ (blocks is None)
53
+ or (isinstance(blocks, Iterable) and blocks.__class__.__name__ != "Layout")
54
+ ):
55
+
56
+ if blocks.__class__.__name__ == "Layout":
57
+ error_msg = f"Please check the input: it should be lp.Layout([layout]) instead of lp.Layout(layout)"
58
+ else:
59
+ error_msg = f"Blocks should be a list of layout elements or empty (None), instead got {blocks}.\n"
60
+ raise ValueError(error_msg)
61
+
62
+ if isinstance(blocks, tuple):
63
+ blocks = list(blocks) # <- more robust handling for tuple-like inputs
64
+
65
+ self._blocks = blocks if blocks is not None else []
66
+ self.page_data = page_data or {}
67
+
68
+ def __getitem__(self, key):
69
+ blocks = self._blocks[key]
70
+ if isinstance(key, slice):
71
+ return self.__class__(self._blocks[key], page_data=self.page_data)
72
+ else:
73
+ return blocks
74
+
75
+ def __setitem__(self, key, newvalue):
76
+ self._blocks[key] = newvalue
77
+
78
+ def __delitem__(self, key):
79
+ del self._blocks[key]
80
+
81
+ def __len__(self):
82
+ return len(self._blocks)
83
+
84
+ def __iter__(self):
85
+ for ele in self._blocks:
86
+ yield ele
87
+
88
+ def __repr__(self):
89
+ info_str = ", ".join([f"{key}={val}" for key, val in vars(self).items()])
90
+ return f"{self.__class__.__name__}({info_str})"
91
+
92
+ def __eq__(self, other):
93
+ if isinstance(other, Layout):
94
+ return self._blocks == other._blocks and self.page_data == other.page_data
95
+ else:
96
+ return False
97
+
98
+ def __add__(self, other):
99
+ if isinstance(other, Layout):
100
+ if self.page_data == other.page_data:
101
+ return self.__class__(
102
+ self._blocks + other._blocks, page_data=self.page_data
103
+ )
104
+ elif self.page_data == {} or other.page_data == {}:
105
+ return self.__class__(
106
+ self._blocks + other._blocks,
107
+ page_data=self.page_data or other.page_data,
108
+ )
109
+ else:
110
+ raise ValueError(
111
+ f"Incompatible page_data for two innputs: {self.page_data} vs {other.page_data}."
112
+ )
113
+ elif isinstance(other, list):
114
+ return self.__class__(self._blocks + other, page_data=self.page_data)
115
+ else:
116
+ raise ValueError(
117
+ f"Invalid input type for other {other.__class__.__name__}."
118
+ )
119
+
120
+ def insert(self, key, value):
121
+ self._blocks.insert(key, value)
122
+
123
+ def copy(self):
124
+ return self.__class__(copy(self._blocks), page_data=self.page_data)
125
+
126
+ def relative_to(self, other):
127
+ return self.__class__(
128
+ [ele.relative_to(other) for ele in self], page_data=self.page_data
129
+ )
130
+
131
+ def condition_on(self, other):
132
+ return self.__class__(
133
+ [ele.condition_on(other) for ele in self], page_data=self.page_data
134
+ )
135
+
136
+ def is_in(self, other, soft_margin={}, center=False):
137
+ return self.__class__(
138
+ [ele.is_in(other, soft_margin, center) for ele in self],
139
+ page_data=self.page_data,
140
+ )
141
+
142
+ def sort(self, key=None, reverse=False, inplace=False) -> Optional["Layout"]:
143
+ """Sort the list of blocks based on the given
144
+
145
+ Args:
146
+ key ([type], optional): key specifies a function of one argument that
147
+ is used to extract a comparison key from each list element.
148
+ Defaults to None.
149
+ reverse (bool, optional): reverse is a boolean value. If set to True,
150
+ then the list elements are sorted as if each comparison were reversed.
151
+ Defaults to False.
152
+ inplace (bool, optional): whether to perform the sort inplace. If set
153
+ to False, it will return another object instance with _block sorted in
154
+ the order. Defaults to False.
155
+
156
+ Examples::
157
+ >>> import layoutparser as lp
158
+ >>> i = lp.Interval(4, 5, axis="y")
159
+ >>> l = lp.Layout([i, i.shift(2)])
160
+ >>> l.sort(key=lambda x: x.coordinates[1], reverse=True)
161
+
162
+ """
163
+ if not inplace:
164
+ return self.__class__(
165
+ sorted(self._blocks, key=key, reverse=reverse), page_data=self.page_data
166
+ )
167
+ else:
168
+ self._blocks.sort(key=key, reverse=reverse)
169
+
170
+ def filter_by(self, other, soft_margin={}, center=False):
171
+ """
172
+ Return a `Layout` object containing the elements that are in the `other` object.
173
+
174
+ Args:
175
+ other (:obj:`BaseCoordElement`):
176
+ The block to filter the current elements.
177
+
178
+ Returns:
179
+ :obj:`Layout`:
180
+ A new layout object after filtering.
181
+ """
182
+ return self.__class__(
183
+ [ele for ele in self if ele.is_in(other, soft_margin, center)],
184
+ page_data=self.page_data,
185
+ )
186
+
187
+ def shift(self, shift_distance):
188
+ """
189
+ Shift all layout elements by user specified amounts on x and y axis respectively. If shift_distance is one
190
+ numeric value, the element will by shifted by the same specified amount on both x and y axis.
191
+
192
+ Args:
193
+ shift_distance (:obj:`numeric` or :obj:`Tuple(numeric)` or :obj:`List[numeric]`):
194
+ The number of pixels used to shift the element.
195
+
196
+ Returns:
197
+ :obj:`Layout`:
198
+ A new layout object with all the elements shifted in the specified values.
199
+ """
200
+ return self.__class__(
201
+ [ele.shift(shift_distance) for ele in self], page_data=self.page_data
202
+ )
203
+
204
+ def pad(self, left=0, right=0, top=0, bottom=0, safe_mode=True):
205
+ """Pad all layout elements on the four sides of the polygon with the user-defined pixels. If
206
+ safe_mode is set to True, the function will cut off the excess padding that falls on the negative
207
+ side of the coordinates.
208
+
209
+ Args:
210
+ left (:obj:`int`, `optional`, defaults to 0): The number of pixels to pad on the upper side of the polygon.
211
+ right (:obj:`int`, `optional`, defaults to 0): The number of pixels to pad on the lower side of the polygon.
212
+ top (:obj:`int`, `optional`, defaults to 0): The number of pixels to pad on the left side of the polygon.
213
+ bottom (:obj:`int`, `optional`, defaults to 0): The number of pixels to pad on the right side of the polygon.
214
+ safe_mode (:obj:`bool`, `optional`, defaults to True): A bool value to toggle the safe_mode.
215
+
216
+ Returns:
217
+ :obj:`Layout`:
218
+ A new layout object with all the elements padded in the specified values.
219
+ """
220
+ return self.__class__(
221
+ [ele.pad(left, right, top, bottom, safe_mode) for ele in self],
222
+ page_data=self.page_data,
223
+ )
224
+
225
+ def scale(self, scale_factor):
226
+ """
227
+ Scale all layout element by a user specified amount on x and y axis respectively. If scale_factor is one
228
+ numeric value, the element will by scaled by the same specified amount on both x and y axis.
229
+
230
+ Args:
231
+ scale_factor (:obj:`numeric` or :obj:`Tuple(numeric)` or :obj:`List[numeric]`): The amount for downscaling or upscaling the element.
232
+
233
+ Returns:
234
+ :obj:`Layout`:
235
+ A new layout object with all the elements scaled in the specified values.
236
+ """
237
+ return self.__class__(
238
+ [ele.scale(scale_factor) for ele in self], page_data=self.page_data
239
+ )
240
+
241
+ def crop_image(self, image):
242
+ return [ele.crop_image(image) for ele in self]
243
+
244
+ def get_texts(self):
245
+ """
246
+ Iterate through all the text blocks in the list and append their ocr'ed text results.
247
+
248
+ Returns:
249
+ :obj:`List[str]`: A list of text strings of the text blocks in the list of layout elements.
250
+ """
251
+
252
+ return [ele.text for ele in self if hasattr(ele, "text")]
253
+
254
+ def get_info(self, attr_name):
255
+ """Given user-provided attribute name, check all the elements in the list and return the corresponding
256
+ attribute values.
257
+
258
+ Args:
259
+ attr_name (:obj:`str`): The text string of certain attribute name.
260
+
261
+ Returns:
262
+ :obj:`List`:
263
+ The list of the corresponding attribute value (if exist) of each element in the list.
264
+ """
265
+ return [getattr(ele, attr_name) for ele in self if hasattr(ele, attr_name)]
266
+
267
+ def to_dict(self) -> Dict[str, Any]:
268
+ """Generate a dict representation of the layout object with
269
+ the page_data and all the blocks in its dict representation.
270
+
271
+ Returns:
272
+ :obj:`Dict`:
273
+ The dictionary representation of the layout object.
274
+ """
275
+ return {"page_data": self.page_data, "blocks": [ele.to_dict() for ele in self]}
276
+
277
+ def get_homogeneous_blocks(self) -> List[BaseLayoutElement]:
278
+ """Convert all elements into blocks of the same type based
279
+ on the type casting rule::
280
+
281
+ Interval < Rectangle < Quadrilateral < TextBlock
282
+
283
+ Returns:
284
+ List[BaseLayoutElement]:
285
+ A list of base layout elements of the maximal compatible
286
+ type
287
+ """
288
+
289
+ # Detect the maximal compatible type
290
+ has_textblock = False
291
+ max_coord_level = -1
292
+ for ele in self:
293
+
294
+ if isinstance(ele, TextBlock):
295
+ has_textblock = True
296
+ block = ele.block
297
+ else:
298
+ block = ele
299
+
300
+ max_coord_level = max(
301
+ max_coord_level, BASECOORD_ELEMENT_INDEXMAP[block._name]
302
+ )
303
+ target_coord_name = ALL_BASECOORD_ELEMENTS[max_coord_level]._name
304
+
305
+ if has_textblock:
306
+ new_blocks = []
307
+ for ele in self:
308
+ if isinstance(ele, TextBlock):
309
+ ele = copy(ele)
310
+ if ele.block._name != target_coord_name:
311
+ ele.block = getattr(ele.block, f"to_{target_coord_name}")()
312
+ else:
313
+ if ele._name != target_coord_name:
314
+ ele = getattr(ele, f"to_{target_coord_name}")()
315
+ ele = TextBlock(block)
316
+ new_blocks.append(ele)
317
+ else:
318
+ new_blocks = [
319
+ getattr(ele, f"to_{target_coord_name}")()
320
+ if ele._name != target_coord_name
321
+ else ele
322
+ for ele in self
323
+ ]
324
+
325
+ return new_blocks
326
+
327
+ def to_dataframe(self, enforce_same_type=False) -> pd.DataFrame:
328
+ """Convert the layout object into the dataframe.
329
+ Warning: the page data won't be exported.
330
+
331
+ Args:
332
+ enforce_same_type (:obj:`bool`, optional):
333
+ If true, it will convert all the contained blocks to
334
+ the maximal compatible data type.
335
+ Defaults to False.
336
+
337
+ Returns:
338
+ pd.DataFrame:
339
+ The dataframe representation of layout object
340
+ """
341
+ if enforce_same_type:
342
+ blocks = self.get_homogeneous_blocks()
343
+ else:
344
+ blocks = self
345
+
346
+ df = pd.DataFrame([ele.to_dict() for ele in blocks])
347
+
348
+ return df