deepdoctection 0.45.0__py3-none-any.whl → 0.46__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.

Potentially problematic release.


This version of deepdoctection might be problematic. Click here for more details.

@@ -25,7 +25,7 @@ from .utils.logger import LoggingRecord, logger
25
25
 
26
26
  # pylint: enable=wrong-import-position
27
27
 
28
- __version__ = "0.45.0"
28
+ __version__ = "0.46"
29
29
 
30
30
  _IMPORT_STRUCTURE = {
31
31
  "analyzer": ["config_sanity_checks", "get_dd_analyzer", "ServiceFactory", "update_cfg_from_defaults"],
@@ -271,6 +271,7 @@ _IMPORT_STRUCTURE = {
271
271
  "MultiThreadPipelineComponent",
272
272
  "DoctectionPipe",
273
273
  "LanguageDetectionService",
274
+ "skip_if_category_or_service_extracted",
274
275
  "ImageLayoutService",
275
276
  "LMTokenClassifierService",
276
277
  "LMSequenceClassifierService",
@@ -310,12 +311,14 @@ _IMPORT_STRUCTURE = {
310
311
  "get_tensorpack_requirement",
311
312
  "pytorch_available",
312
313
  "get_pytorch_requirement",
314
+ "pyzmq_available",
313
315
  "lxml_available",
314
316
  "get_lxml_requirement",
315
317
  "apted_available",
316
318
  "get_apted_requirement",
317
319
  "distance_available",
318
320
  "get_distance_requirement",
321
+ "networkx_available",
319
322
  "numpy_v1_available",
320
323
  "get_numpy_v1_requirement",
321
324
  "transformers_available",
@@ -526,6 +526,9 @@ cfg.USE_LM_SEQUENCE_CLASS = False
526
526
  # Enables a token classification pipeline component, e.g. a LayoutLM or Bert-like model
527
527
  cfg.USE_LM_TOKEN_CLASS = False
528
528
 
529
+ # Specifies the selection of the rotation model. There are two models available: A rotation estimator
530
+ # based on Tesseract ('tesseract'), and a rotation estimator based on DocTr ('doctr').
531
+ cfg.ROTATOR.MODEL = "tesseract"
529
532
 
530
533
  # Relevant when LIB = TF. Specifies the layout detection model.
531
534
  # This model should detect multiple or single objects across an entire page.
@@ -22,13 +22,13 @@
22
22
  from __future__ import annotations
23
23
 
24
24
  from os import environ
25
- from typing import TYPE_CHECKING, Union
25
+ from typing import TYPE_CHECKING, Literal, Union
26
26
 
27
27
  from lazy_imports import try_import
28
28
 
29
29
  from ..extern.base import ImageTransformer, ObjectDetector, PdfMiner
30
30
  from ..extern.d2detect import D2FrcnnDetector, D2FrcnnTracingDetector
31
- from ..extern.doctrocr import DoctrTextlineDetector, DoctrTextRecognizer
31
+ from ..extern.doctrocr import DocTrRotationTransformer, DoctrTextlineDetector, DoctrTextRecognizer
32
32
  from ..extern.hfdetr import HFDetrDerivedDetector
33
33
  from ..extern.hflayoutlm import (
34
34
  HFLayoutLmSequenceClassifier,
@@ -78,6 +78,7 @@ if TYPE_CHECKING:
78
78
  from ..extern.hflayoutlm import LayoutSequenceModels, LayoutTokenModels
79
79
  from ..extern.hflm import LmSequenceModels, LmTokenModels
80
80
 
81
+ RotationTransformer = Union[TesseractRotationTransformer, DocTrRotationTransformer]
81
82
 
82
83
  __all__ = [
83
84
  "ServiceFactory",
@@ -190,24 +191,32 @@ class ServiceFactory:
190
191
  return ServiceFactory._build_layout_detector(config, mode)
191
192
 
192
193
  @staticmethod
193
- def _build_rotation_detector() -> TesseractRotationTransformer:
194
+ def _build_rotation_detector(rotator_name: Literal["tesseract", "doctr"]) -> RotationTransformer:
194
195
  """
195
196
  Building a rotation detector.
196
197
 
197
198
  Returns:
198
199
  TesseractRotationTransformer: Rotation detector instance.
199
200
  """
200
- return TesseractRotationTransformer()
201
+
202
+ if rotator_name == "tesseract":
203
+ return TesseractRotationTransformer()
204
+ if rotator_name == "doctr":
205
+ return DocTrRotationTransformer()
206
+ raise ValueError(
207
+ f"You have chosen rotator_name: {rotator_name} which is not allowed. Only tesseract or "
208
+ f"doctr are allowed."
209
+ )
201
210
 
202
211
  @staticmethod
203
- def build_rotation_detector() -> TesseractRotationTransformer:
212
+ def build_rotation_detector(rotator_name: Literal["tesseract", "doctr"]) -> RotationTransformer:
204
213
  """
205
214
  Building a rotation detector.
206
215
 
207
216
  Returns:
208
217
  TesseractRotationTransformer: Rotation detector instance.
209
218
  """
210
- return ServiceFactory._build_rotation_detector()
219
+ return ServiceFactory._build_rotation_detector(rotator_name)
211
220
 
212
221
  @staticmethod
213
222
  def _build_transform_service(transform_predictor: ImageTransformer) -> SimpleTransformService:
@@ -1123,7 +1132,7 @@ class ServiceFactory:
1123
1132
  pipe_component_list: list[PipelineComponent] = []
1124
1133
 
1125
1134
  if config.USE_ROTATOR:
1126
- rotation_detector = ServiceFactory.build_rotation_detector()
1135
+ rotation_detector = ServiceFactory.build_rotation_detector(config.ROTATOR.MODEL)
1127
1136
  transform_service = ServiceFactory.build_transform_service(transform_predictor=rotation_detector)
1128
1137
  pipe_component_list.append(transform_service)
1129
1138
 
@@ -24,15 +24,19 @@ from abc import ABC, abstractmethod
24
24
  from contextlib import contextmanager
25
25
  from typing import Any, Callable, Iterator, no_type_check
26
26
 
27
- import zmq
27
+ from lazy_imports import try_import
28
28
 
29
29
  from ..utils.concurrency import StoppableThread, enable_death_signal, start_proc_mask_signal
30
30
  from ..utils.error import DataFlowTerminatedError
31
+ from ..utils.file_utils import pyzmq_available
31
32
  from ..utils.logger import LoggingRecord, logger
32
33
  from .base import DataFlow, DataFlowReentrantGuard, ProxyDataFlow
33
34
  from .common import RepeatedData
34
35
  from .serialize import PickleSerializer
35
36
 
37
+ with try_import() as import_guard:
38
+ import zmq
39
+
36
40
 
37
41
  @no_type_check
38
42
  def del_weakref(x):
@@ -77,6 +81,8 @@ def _get_pipe_name(name):
77
81
 
78
82
  class _ParallelMapData(ProxyDataFlow, ABC):
79
83
  def __init__(self, df: DataFlow, buffer_size: int, strict: bool = False) -> None:
84
+ if not pyzmq_available():
85
+ raise ModuleNotFoundError("pyzmq is required for running parallel dataflows (multiprocess/multithread).")
80
86
  super().__init__(df)
81
87
  if buffer_size <= 0:
82
88
  raise ValueError(f"buffer_size must be a positive number, got {buffer_size}")
@@ -284,7 +284,7 @@ class BoundingBox:
284
284
  raise BoundingBoxError(
285
285
  f"bounding box must have height and width >0. Check coords "
286
286
  f"ulx: {self.ulx}, uly: {self.uly}, lrx: {self.lrx}, "
287
- f"lry: {self.lry}."
287
+ f"lry: {self.lry}, absolute_coords: {self.absolute_coords}"
288
288
  )
289
289
  if not self.absolute_coords and not (
290
290
  0 <= self.ulx <= 1 and 0 <= self.uly <= 1 and 0 <= self.lrx <= 1 and 0 <= self.lry <= 1
@@ -505,10 +505,10 @@ class BoundingBox:
505
505
  if self.absolute_coords:
506
506
  transformed_box = BoundingBox(
507
507
  absolute_coords=not self.absolute_coords,
508
- ulx=max(self.ulx / image_width, 0.0),
509
- uly=max(self.uly / image_height, 0.0),
510
- lrx=min(self.lrx / image_width, 1.0),
511
- lry=min(self.lry / image_height, 1.0),
508
+ ulx=min(max(self.ulx / image_width, 0.0), 1.0),
509
+ uly=min(max(self.uly / image_height, 0.0), 1.0),
510
+ lrx=max(min(self.lrx / image_width, 1.0), 0.0),
511
+ lry=max(min(self.lry / image_height, 1.0), 0.0),
512
512
  )
513
513
  else:
514
514
  transformed_box = BoundingBox(
@@ -36,7 +36,7 @@ from ..utils.logger import LoggingRecord, logger
36
36
  from ..utils.settings import ObjectTypes, SummaryType, get_type
37
37
  from ..utils.types import ImageDict, PathLikeOrStr, PixelValues
38
38
  from .annotation import Annotation, AnnotationMap, BoundingBox, CategoryAnnotation, ImageAnnotation
39
- from .box import crop_box_from_image, global_to_local_coords, intersection_box
39
+ from .box import BoxCoordinate, crop_box_from_image, global_to_local_coords, intersection_box
40
40
  from .convert import as_dict, convert_b64_to_np_array, convert_np_array_to_b64, convert_pdf_bytes_to_np_array_v2
41
41
 
42
42
 
@@ -318,7 +318,7 @@ class Image:
318
318
  return _Img(self.image)
319
319
 
320
320
  @property
321
- def width(self) -> float:
321
+ def width(self) -> BoxCoordinate:
322
322
  """
323
323
  `width`
324
324
  """
@@ -327,7 +327,7 @@ class Image:
327
327
  return self._bbox.width
328
328
 
329
329
  @property
330
- def height(self) -> float:
330
+ def height(self) -> BoxCoordinate:
331
331
  """
332
332
  `height`
333
333
  """
@@ -335,7 +335,7 @@ class Image:
335
335
  raise ImageError("Height not available. Call set_width_height first")
336
336
  return self._bbox.height
337
337
 
338
- def set_width_height(self, width: float, height: float) -> None:
338
+ def set_width_height(self, width: BoxCoordinate, height: BoxCoordinate) -> None:
339
339
  """
340
340
  Defines bounding box of the image if not already set. Use this, if you do not want to keep the image separated
341
341
  for memory reasons.
@@ -345,7 +345,7 @@ class Image:
345
345
  height: height of image
346
346
  """
347
347
  if self._bbox is None:
348
- self._bbox = BoundingBox(ulx=0.0, uly=0.0, height=height, width=width, absolute_coords=True)
348
+ self._bbox = BoundingBox(ulx=0, uly=0, height=height, width=width, absolute_coords=True)
349
349
  self._self_embedding()
350
350
 
351
351
  def set_embedding(self, image_id: str, bounding_box: BoundingBox) -> None:
@@ -428,6 +428,8 @@ class List(Layout):
428
428
  A list of words order by reading order. Words with no `reading_order` will not be returned"""
429
429
  try:
430
430
  list_items = self.list_items
431
+ if not list_items:
432
+ return super().get_ordered_words()
431
433
  all_words = []
432
434
  list_items.sort(key=lambda x: x.bbox[1])
433
435
  for list_item in list_items:
@@ -755,6 +757,8 @@ class Table(Layout):
755
757
  """
756
758
  try:
757
759
  cells = self.cells
760
+ if not cells:
761
+ return super().get_ordered_words()
758
762
  all_words = []
759
763
  cells.sort(key=lambda x: (x.ROW_NUMBER, x.COLUMN_NUMBER))
760
764
  for cell in cells:
@@ -1054,6 +1058,8 @@ class Page(Image):
1054
1058
  Returns:
1055
1059
  A `Page` instance with all annotations as `ImageAnnotationBaseView` subclasses.
1056
1060
  """
1061
+ if isinstance(image_orig, Page):
1062
+ raise ImageError("Page.from_image() cannot be called on a Page instance.")
1057
1063
 
1058
1064
  if text_container is None:
1059
1065
  text_container = IMAGE_DEFAULTS.TEXT_CONTAINER
@@ -1310,7 +1316,7 @@ class Page(Image):
1310
1316
  If `interactive=False` will return a `np.array`.
1311
1317
  """
1312
1318
 
1313
- category_names_list: list[Union[str, None]] = []
1319
+ category_names_list: list[Tuple[Union[str, None], Union[str, None]]] = []
1314
1320
  box_stack = []
1315
1321
  cells_found = False
1316
1322
 
@@ -1323,22 +1329,23 @@ class Page(Image):
1323
1329
  anns = self.get_annotation(category_names=list(debug_kwargs.keys()))
1324
1330
  for ann in anns:
1325
1331
  box_stack.append(self._ann_viz_bbox(ann))
1326
- category_names_list.append(str(getattr(ann, debug_kwargs[ann.category_name])))
1332
+ val = str(getattr(ann, debug_kwargs[ann.category_name]))
1333
+ category_names_list.append((val, val))
1327
1334
 
1328
1335
  if show_layouts and not debug_kwargs:
1329
1336
  for item in self.layouts:
1330
1337
  box_stack.append(self._ann_viz_bbox(item))
1331
- category_names_list.append(item.category_name.value)
1338
+ category_names_list.append((item.category_name.value, item.category_name.value))
1332
1339
 
1333
1340
  if show_figures and not debug_kwargs:
1334
1341
  for item in self.figures:
1335
1342
  box_stack.append(self._ann_viz_bbox(item))
1336
- category_names_list.append(item.category_name.value)
1343
+ category_names_list.append((item.category_name.value, item.category_name.value))
1337
1344
 
1338
1345
  if show_tables and not debug_kwargs:
1339
1346
  for table in self.tables:
1340
1347
  box_stack.append(self._ann_viz_bbox(table))
1341
- category_names_list.append(LayoutType.TABLE.value)
1348
+ category_names_list.append((LayoutType.TABLE.value, LayoutType.TABLE.value))
1342
1349
  if show_cells:
1343
1350
  for cell in table.cells:
1344
1351
  if cell.category_name in {
@@ -1347,21 +1354,21 @@ class Page(Image):
1347
1354
  }:
1348
1355
  cells_found = True
1349
1356
  box_stack.append(self._ann_viz_bbox(cell))
1350
- category_names_list.append(None)
1357
+ category_names_list.append((None, cell.category_name.value))
1351
1358
  if show_table_structure:
1352
1359
  rows = table.rows
1353
1360
  cols = table.columns
1354
1361
  for row in rows:
1355
1362
  box_stack.append(self._ann_viz_bbox(row))
1356
- category_names_list.append(None)
1363
+ category_names_list.append((None, row.category_name.value))
1357
1364
  for col in cols:
1358
1365
  box_stack.append(self._ann_viz_bbox(col))
1359
- category_names_list.append(None)
1366
+ category_names_list.append((None, col.category_name.value))
1360
1367
 
1361
1368
  if show_cells and not cells_found and not debug_kwargs:
1362
1369
  for ann in self.get_annotation(category_names=[LayoutType.CELL, CellType.SPANNING]):
1363
1370
  box_stack.append(self._ann_viz_bbox(ann))
1364
- category_names_list.append(None)
1371
+ category_names_list.append((None, ann.category_name.value))
1365
1372
 
1366
1373
  if show_words and not debug_kwargs:
1367
1374
  all_words = []
@@ -1379,22 +1386,36 @@ class Page(Image):
1379
1386
  for word in all_words:
1380
1387
  box_stack.append(self._ann_viz_bbox(word))
1381
1388
  if show_token_class:
1382
- category_names_list.append(word.token_class.value if word.token_class is not None else None)
1389
+ category_names_list.append(
1390
+ (word.token_class.value, word.token_class.value)
1391
+ if word.token_class is not None
1392
+ else (None, None)
1393
+ )
1383
1394
  else:
1384
- category_names_list.append(word.token_tag.value if word.token_tag is not None else None)
1395
+ category_names_list.append(
1396
+ (word.token_tag.value, word.token_tag.value) if word.token_tag is not None else (None, None)
1397
+ )
1385
1398
  else:
1386
1399
  for word in all_words:
1387
1400
  if word.token_class is not None and word.token_class != TokenClasses.OTHER:
1388
1401
  box_stack.append(self._ann_viz_bbox(word))
1389
1402
  if show_token_class:
1390
- category_names_list.append(word.token_class.value if word.token_class is not None else None)
1403
+ category_names_list.append(
1404
+ (word.token_class.value, word.token_class.value)
1405
+ if word.token_class is not None
1406
+ else (None, None)
1407
+ )
1391
1408
  else:
1392
- category_names_list.append(word.token_tag.value if word.token_tag is not None else None)
1409
+ category_names_list.append(
1410
+ (word.token_tag.value, word.token_tag.value)
1411
+ if word.token_tag is not None
1412
+ else (None, None)
1413
+ )
1393
1414
 
1394
1415
  if show_residual_layouts and not debug_kwargs:
1395
1416
  for item in self.residual_layouts:
1396
1417
  box_stack.append(item.bbox)
1397
- category_names_list.append(item.category_name.value)
1418
+ category_names_list.append((item.category_name.value, item.category_name.value))
1398
1419
 
1399
1420
  if self.image is not None:
1400
1421
  scale_fx = scaled_width / self.width
@@ -275,6 +275,7 @@ class CocoMetric(MetricBase):
275
275
  get the ultimate F1-score.
276
276
  f1_iou: Use with `f1_score=True` and reset the f1 iou threshold
277
277
  per_category: Whether to calculate metrics per category
278
+ per_category: If set to True, f1 score will be returned by each category.
278
279
  """
279
280
  if max_detections is not None:
280
281
  assert len(max_detections) == 3, max_detections
@@ -263,7 +263,7 @@ class PredictorBase(ABC):
263
263
  requirements = cls.get_requirements()
264
264
  name = cls.__name__ if hasattr(cls, "__name__") else cls.__class__.__name__
265
265
  if not all(requirement[1] for requirement in requirements):
266
- raise ImportError(
266
+ raise ModuleNotFoundError(
267
267
  "\n".join(
268
268
  [f"{name} has the following dependencies:"]
269
269
  + [requirement[2] for requirement in requirements if not requirement[1]]
@@ -334,6 +334,11 @@ class DetectionResult:
334
334
  block: block number. For reading order from some ocr predictors
335
335
  line: line number. For reading order from some ocr predictors
336
336
  uuid: uuid. For assigning detection result (e.g. text to image annotations)
337
+ relationships: A dictionary of relationships. Each key is a relationship type and each value is a list of
338
+ uuids of the related annotations.
339
+ angle: angle of rotation in degrees. Only used for text detection.
340
+ image_width: image width
341
+ image_height: image height
337
342
  """
338
343
 
339
344
  box: Optional[list[float]] = None
@@ -348,6 +353,8 @@ class DetectionResult:
348
353
  uuid: Optional[str] = None
349
354
  relationships: Optional[dict[str, Any]] = None
350
355
  angle: Optional[float] = None
356
+ image_width: Optional[Union[int, float]] = None
357
+ image_height: Optional[Union[int, float]] = None
351
358
 
352
359
 
353
360
  class ObjectDetector(PredictorBase, ABC):
@@ -24,9 +24,10 @@ from __future__ import annotations
24
24
  import os
25
25
  from abc import ABC
26
26
  from pathlib import Path
27
- from typing import Any, Literal, Mapping, Optional, Union
27
+ from typing import Any, Literal, Mapping, Optional, Sequence, Union
28
28
  from zipfile import ZipFile
29
29
 
30
+ import numpy as np
30
31
  from lazy_imports import try_import
31
32
 
32
33
  from ..utils.env_info import ENV_VARS_TRUE
@@ -39,6 +40,7 @@ from ..utils.file_utils import (
39
40
  )
40
41
  from ..utils.fs import load_json
41
42
  from ..utils.settings import LayoutType, ObjectTypes, PageType, TypeOrStr
43
+ from ..utils.transform import RotationTransform
42
44
  from ..utils.types import PathLikeOrStr, PixelValues, Requirement
43
45
  from ..utils.viz import viz_handler
44
46
  from .base import DetectionResult, ImageTransformer, ModelCategories, ObjectDetector, TextRecognizer
@@ -558,12 +560,13 @@ class DocTrRotationTransformer(ImageTransformer):
558
560
  """
559
561
  Args:
560
562
  number_contours: the number of contours used for the orientation estimation
561
- ratio_threshold_for_lines: this is the ratio w/h used to discriminates lines
563
+ ratio_threshold_for_lines: this is the ratio w/h used to discriminate lines
562
564
  """
563
565
  self.number_contours = number_contours
564
566
  self.ratio_threshold_for_lines = ratio_threshold_for_lines
565
567
  self.name = "doctr_rotation_transformer"
566
568
  self.model_id = self.get_model_id()
569
+ self.rotator = RotationTransform(360)
567
570
 
568
571
  def transform_image(self, np_img: PixelValues, specification: DetectionResult) -> PixelValues:
569
572
  """
@@ -579,6 +582,19 @@ class DocTrRotationTransformer(ImageTransformer):
579
582
  """
580
583
  return viz_handler.rotate_image(np_img, specification.angle) # type: ignore
581
584
 
585
+ def transform_coords(self, detect_results: Sequence[DetectionResult]) -> Sequence[DetectionResult]:
586
+ if detect_results:
587
+ if detect_results[0].angle:
588
+ self.rotator.set_angle(detect_results[0].angle) # type: ignore
589
+ self.rotator.set_image_width(detect_results[0].image_width) # type: ignore
590
+ self.rotator.set_image_height(detect_results[0].image_height) # type: ignore
591
+ transformed_coords = self.rotator.apply_coords(
592
+ np.asarray([detect_result.box for detect_result in detect_results], dtype=float)
593
+ )
594
+ for idx, detect_result in enumerate(detect_results):
595
+ detect_result.box = transformed_coords[idx, :].tolist()
596
+ return detect_results
597
+
582
598
  def predict(self, np_img: PixelValues) -> DetectionResult:
583
599
  angle = estimate_orientation(
584
600
  np_img, n_ct=self.number_contours, ratio_threshold_for_lines=self.ratio_threshold_for_lines
@@ -1024,12 +1024,9 @@ class HFLayoutLmv2SequenceClassifier(HFLayoutLmSequenceClassifierBase):
1024
1024
  else:
1025
1025
  raise ValueError(f"images must be list but is {type(images)}")
1026
1026
 
1027
- result = predict_sequence_classes_from_layoutlm(input_ids,
1028
- attention_mask,
1029
- token_type_ids,
1030
- boxes,
1031
- self.model,
1032
- images)
1027
+ result = predict_sequence_classes_from_layoutlm(
1028
+ input_ids, attention_mask, token_type_ids, boxes, self.model, images
1029
+ )
1033
1030
 
1034
1031
  result.class_id += 1
1035
1032
  result.class_name = self.categories.categories[result.class_id]
@@ -1123,12 +1120,9 @@ class HFLayoutLmv3SequenceClassifier(HFLayoutLmSequenceClassifierBase):
1123
1120
  else:
1124
1121
  raise ValueError(f"images must be list but is {type(images)}")
1125
1122
 
1126
- result = predict_sequence_classes_from_layoutlm(input_ids,
1127
- attention_mask,
1128
- token_type_ids,
1129
- boxes,
1130
- self.model,
1131
- images)
1123
+ result = predict_sequence_classes_from_layoutlm(
1124
+ input_ids, attention_mask, token_type_ids, boxes, self.model, images
1125
+ )
1132
1126
 
1133
1127
  result.class_id += 1
1134
1128
  result.class_name = self.categories.categories[result.class_id]
@@ -28,8 +28,9 @@ from errno import ENOENT
28
28
  from itertools import groupby
29
29
  from os import environ, fspath
30
30
  from pathlib import Path
31
- from typing import Any, Mapping, Optional, Union
31
+ from typing import Any, Mapping, Optional, Sequence, Union
32
32
 
33
+ import numpy as np
33
34
  from packaging.version import InvalidVersion, Version, parse
34
35
 
35
36
  from ..utils.context import save_tmp_file, timeout_manager
@@ -37,6 +38,7 @@ from ..utils.error import DependencyError, TesseractError
37
38
  from ..utils.file_utils import _TESS_PATH, get_tesseract_requirement
38
39
  from ..utils.metacfg import config_to_cli_str, set_config_by_yaml
39
40
  from ..utils.settings import LayoutType, ObjectTypes, PageType
41
+ from ..utils.transform import RotationTransform
40
42
  from ..utils.types import PathLikeOrStr, PixelValues, Requirement
41
43
  from ..utils.viz import viz_handler
42
44
  from .base import DetectionResult, ImageTransformer, ModelCategories, ObjectDetector
@@ -450,6 +452,7 @@ class TesseractRotationTransformer(ImageTransformer):
450
452
  self.name = fspath(_TESS_PATH) + "-rotation"
451
453
  self.categories = ModelCategories(init_categories={1: PageType.ANGLE})
452
454
  self.model_id = self.get_model_id()
455
+ self.rotator = RotationTransform(360)
453
456
 
454
457
  def transform_image(self, np_img: PixelValues, specification: DetectionResult) -> PixelValues:
455
458
  """
@@ -465,6 +468,19 @@ class TesseractRotationTransformer(ImageTransformer):
465
468
  """
466
469
  return viz_handler.rotate_image(np_img, specification.angle) # type: ignore
467
470
 
471
+ def transform_coords(self, detect_results: Sequence[DetectionResult]) -> Sequence[DetectionResult]:
472
+ if detect_results:
473
+ if detect_results[0].angle:
474
+ self.rotator.set_angle(detect_results[0].angle) # type: ignore
475
+ self.rotator.set_image_width(detect_results[0].image_width) # type: ignore
476
+ self.rotator.set_image_height(detect_results[0].image_height) # type: ignore
477
+ transformed_coords = self.rotator.apply_coords(
478
+ np.asarray([detect_result.box for detect_result in detect_results], dtype=float)
479
+ )
480
+ for idx, detect_result in enumerate(detect_results):
481
+ detect_result.box = transformed_coords[idx, :].tolist()
482
+ return detect_results
483
+
468
484
  def predict(self, np_img: PixelValues) -> DetectionResult:
469
485
  """
470
486
  Determines the angle of the rotated image. It can only handle angles that are multiples of 90 degrees.
@@ -228,8 +228,8 @@ class OrderGenerator:
228
228
  columns: list[BoundingBox] = []
229
229
  anns.sort(
230
230
  key=lambda x: (
231
- x.bounding_box.transform(image_width, image_height).cy, # type: ignore
232
- x.bounding_box.transform(image_width, image_height).cx, # type: ignore
231
+ x.get_bounding_box(image_id).transform(image_width, image_height).cy,
232
+ x.get_bounding_box(image_id).transform(image_width, image_height).cx,
233
233
  )
234
234
  )
235
235
  for ann in anns:
@@ -309,7 +309,9 @@ class OrderGenerator:
309
309
  filtered_blocks: Sequence[tuple[int, str]]
310
310
  for idx in range(max_block_number + 1):
311
311
  filtered_blocks = list(filter(lambda x: x[0] == idx, blocks)) # type: ignore # pylint: disable=W0640
312
- sorted_blocks.extend(self._sort_anns_grouped_by_blocks(filtered_blocks, anns, image_width, image_height))
312
+ sorted_blocks.extend(
313
+ self._sort_anns_grouped_by_blocks(filtered_blocks, anns, image_width, image_height, image_id)
314
+ )
313
315
  reading_blocks = [(idx + 1, block[1]) for idx, block in enumerate(sorted_blocks)]
314
316
 
315
317
  if logger.isEnabledFor(DEBUG):
@@ -346,7 +348,11 @@ class OrderGenerator:
346
348
 
347
349
  @staticmethod
348
350
  def _sort_anns_grouped_by_blocks(
349
- block: Sequence[tuple[int, str]], anns: Sequence[ImageAnnotation], image_width: float, image_height: float
351
+ block: Sequence[tuple[int, str]],
352
+ anns: Sequence[ImageAnnotation],
353
+ image_width: float,
354
+ image_height: float,
355
+ image_id: Optional[str] = None,
350
356
  ) -> list[tuple[int, str]]:
351
357
  if not block:
352
358
  return []
@@ -356,8 +362,8 @@ class OrderGenerator:
356
362
  block_anns = [ann for ann in anns if ann.annotation_id in ann_ids]
357
363
  block_anns.sort(
358
364
  key=lambda x: (
359
- round(x.bounding_box.transform(image_width, image_height).uly, 2), # type: ignore
360
- round(x.bounding_box.transform(image_width, image_height).ulx, 2), # type: ignore
365
+ round(x.get_bounding_box(image_id).transform(image_width, image_height).uly, 2),
366
+ round(x.get_bounding_box(image_id).transform(image_width, image_height).ulx, 2),
361
367
  )
362
368
  )
363
369
  return [(block_number, ann.annotation_id) for ann in block_anns]
@@ -27,7 +27,7 @@ from dataclasses import asdict
27
27
  from itertools import chain, product
28
28
  from typing import DefaultDict, Optional, Sequence, Union
29
29
 
30
- import networkx as nx # type: ignore
30
+ from lazy_imports import try_import
31
31
 
32
32
  from ..datapoint.annotation import ImageAnnotation
33
33
  from ..datapoint.box import merge_boxes
@@ -35,10 +35,15 @@ from ..datapoint.image import Image, MetaAnnotation
35
35
  from ..extern.base import DetectionResult
36
36
  from ..mapper.maputils import MappingContextManager
37
37
  from ..utils.error import ImageError
38
+ from ..utils.file_utils import networkx_available
38
39
  from ..utils.settings import CellType, LayoutType, ObjectTypes, Relationships, TableType, get_type
39
40
  from .base import PipelineComponent
40
41
  from .registry import pipeline_component_registry
41
42
 
43
+ with try_import() as import_guard:
44
+ import networkx as nx # type: ignore
45
+
46
+
42
47
  __all__ = ["TableSegmentationRefinementService", "generate_html_string"]
43
48
 
44
49
 
@@ -441,6 +446,10 @@ class TableSegmentationRefinementService(PipelineComponent):
441
446
  table_names: Sequence of table object types.
442
447
  cell_names: Sequence of cell object types.
443
448
  """
449
+ if not networkx_available():
450
+ raise ModuleNotFoundError(
451
+ "TableSegmentationRefinementService requires networkx. Please install separately."
452
+ )
444
453
  self.table_name = table_names
445
454
  self.cell_names = cell_names
446
455
  super().__init__("table_segment_refine")
@@ -129,6 +129,12 @@ class TextExtractionService(PipelineComponent):
129
129
  width, height = self.predictor.get_width_height(predictor_input) # type: ignore
130
130
 
131
131
  for detect_result in detect_result_list:
132
+ if width is not None and height is not None:
133
+ box = detect_result.box
134
+ if box:
135
+ if box[0] >= width or box[1] >= height or box[2] >= width or box[3] >= height:
136
+ continue
137
+
132
138
  if isinstance(self.predictor, TextRecognizer):
133
139
  detect_ann_id = detect_result.uuid
134
140
  else:
@@ -77,6 +77,9 @@ class SimpleTransformService(PipelineComponent):
77
77
  score=ann.score,
78
78
  class_id=ann.category_id,
79
79
  uuid=ann.annotation_id,
80
+ angle=detection_result.angle,
81
+ image_width=dp.width, # we need the original width, not the transformed width
82
+ image_height=dp.height, # same with height
80
83
  )
81
84
  )
82
85
  output_detect_results = self.transform_predictor.transform_coords(detect_results)
@@ -8,6 +8,7 @@
8
8
  Utilities for maintaining dependencies and dealing with external library packages. Parts of this file is adapted from
9
9
  <https://github.com/huggingface/transformers/blob/master/src/transformers/file_utils.py>
10
10
  """
11
+ import importlib.metadata
11
12
  import importlib.util
12
13
  import multiprocessing as mp
13
14
  import string
@@ -17,7 +18,6 @@ from shutil import which
17
18
  from types import ModuleType
18
19
  from typing import Any, Union, no_type_check
19
20
 
20
- import importlib_metadata
21
21
  import numpy as np
22
22
  from packaging import version
23
23
 
@@ -72,9 +72,9 @@ def get_tf_version() -> str:
72
72
 
73
73
  for pkg in candidates:
74
74
  try:
75
- tf_version = importlib_metadata.version(pkg)
75
+ tf_version = importlib.metadata.version(pkg)
76
76
  break
77
- except importlib_metadata.PackageNotFoundError:
77
+ except importlib.metadata.PackageNotFoundError:
78
78
  pass
79
79
  return tf_version
80
80
 
@@ -175,6 +175,19 @@ def get_pytorch_requirement() -> Requirement:
175
175
  return "torch", pytorch_available(), _PYTORCH_ERR_MSG
176
176
 
177
177
 
178
+ _PYZMQ_AVAILABLE = importlib.util.find_spec("zmq") is not None
179
+
180
+
181
+ def pyzmq_available() -> bool:
182
+ """
183
+ Returns whether pyzmq is installed.
184
+
185
+ Returns:
186
+ bool: True if pyzmq is installed, False otherwise.
187
+ """
188
+ return bool(_PYZMQ_AVAILABLE)
189
+
190
+
178
191
  # lxml
179
192
  _LXML_AVAILABLE = importlib.util.find_spec("lxml") is not None
180
193
  _LXML_ERR_MSG = f"lxml must be installed. {_GENERIC_ERR_MSG}"
@@ -232,7 +245,7 @@ _DISTANCE_ERR_MSG = f"distance must be installed. {_GENERIC_ERR_MSG}"
232
245
 
233
246
  def distance_available() -> bool:
234
247
  """
235
- Returns whether `distance` is available.
248
+ Returns True if `distance` is available.
236
249
 
237
250
  Returns:
238
251
  bool: `True` if `distance` is available, False otherwise.
@@ -250,6 +263,22 @@ def get_distance_requirement() -> Requirement:
250
263
  return "distance", distance_available(), _DISTANCE_ERR_MSG
251
264
 
252
265
 
266
+ # networkx
267
+ _NETWORKX_AVAILABLE = importlib.util.find_spec("networkx") is not None
268
+
269
+
270
+ def networkx_available() -> bool:
271
+ """
272
+ Checks if networkx is installed.
273
+
274
+ Returns:
275
+ bool: True if networkx is installed, False otherwise.
276
+ :return:
277
+ """
278
+ return bool(_NETWORKX_AVAILABLE)
279
+
280
+
281
+ # numpy
253
282
  _NUMPY_V1_ERR_MSG = "numpy v1 must be installed."
254
283
 
255
284
 
@@ -143,6 +143,43 @@ class FileFormatter(logging.Formatter):
143
143
 
144
144
 
145
145
  _LOG_DIR = None
146
+
147
+
148
+ def _coerce_log_level(val: Any) -> Union[int, str]:
149
+ """Normalize environment log level values.
150
+
151
+ Accepts integer values (e.g., ``20``), numeric strings (``"20"``),
152
+ or names case-insensitively (``"info"``, ``"Warn"``, ...). Returns
153
+ either an integer level number or a valid uppercase level name
154
+ accepted by the :mod:`logging` module.
155
+
156
+ Args:
157
+ val: The raw value from the environment variable ``LOG_LEVEL``.
158
+
159
+ Returns:
160
+ int | str: The corresponding logging level as an int or an
161
+ uppercase string. Defaults to ``"INFO"`` if the input is invalid.
162
+ """
163
+ if isinstance(val, int):
164
+ return val
165
+ if val is None:
166
+ return "INFO"
167
+ s = str(val).strip()
168
+ if s.isdigit():
169
+ return int(s)
170
+ name = s.upper()
171
+ if name == "WARN":
172
+ name = "WARNING"
173
+ if name in logging._nameToLevel: # pylint: disable=W0212
174
+ return name
175
+ lvl = logging.getLevelName(name)
176
+ return lvl if isinstance(lvl, int) else "INFO"
177
+
178
+
179
+ # resolve level from LOG_LEVEL only
180
+ _ENV_LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
181
+ _RESOLVED_LOG_LEVEL = _coerce_log_level(_ENV_LOG_LEVEL)
182
+
146
183
  _CONFIG_DICT: dict[str, Any] = {
147
184
  "version": 1,
148
185
  "disable_existing_loggers": False,
@@ -155,7 +192,7 @@ _CONFIG_DICT: dict[str, Any] = {
155
192
  },
156
193
  "root": {
157
194
  "handlers": ["streamhandler"],
158
- "level": os.environ.get("LOG_LEVEL", "INFO"),
195
+ "level": _RESOLVED_LOG_LEVEL,
159
196
  "propagate": os.environ.get("LOG_PROPAGATE", "False") in ENV_VARS_TRUE,
160
197
  },
161
198
  }
@@ -408,8 +408,35 @@ class RotationTransform(BaseTransform):
408
408
  angle: Angle to rotate the image. Must be one of 90, 180, 270, or 360 degrees.
409
409
  """
410
410
  self.angle = angle
411
- self.image_width: Optional[int] = None
412
- self.image_height: Optional[int] = None
411
+ self.image_width: Optional[Union[int, float]] = None
412
+ self.image_height: Optional[Union[int, float]] = None
413
+
414
+ def set_angle(self, angle: Literal[90, 180, 270, 360]) -> None:
415
+ """
416
+ Set angle
417
+
418
+ Args:
419
+ angle: One of 90, 180, 270, or 360 degrees.
420
+ """
421
+ self.angle = angle
422
+
423
+ def set_image_width(self, image_width: Union[int, float]) -> None:
424
+ """
425
+ Set image width
426
+
427
+ Args:
428
+ image_width: Either a positive integer or 1.
429
+ """
430
+ self.image_width = image_width
431
+
432
+ def set_image_height(self, image_height: Union[int, float]) -> None:
433
+ """
434
+ Set image height
435
+
436
+ Args:
437
+ image_height: Either a positive integer or 1.
438
+ """
439
+ self.image_height = image_height
413
440
 
414
441
  def apply_image(self, img: PixelValues) -> PixelValues:
415
442
  """
@@ -442,17 +469,16 @@ class RotationTransform(BaseTransform):
442
469
  raise ValueError("Initialize image_width and image_height first")
443
470
 
444
471
  if self.angle == 90:
445
- coords[:, [0, 1, 2, 3]] = coords[:, [1, 0, 3, 2]]
472
+ self.image_width = self.image_height
473
+ coords[:, [0, 1, 2, 3]] = coords[:, [1, 2, 3, 0]]
446
474
  coords[:, [1, 3]] = self.image_width - coords[:, [1, 3]]
447
- coords[:, [0, 1, 2, 3]] = coords[:, [0, 3, 2, 1]]
448
475
  elif self.angle == 180:
449
- coords[:, [0, 2]] = self.image_width - coords[:, [0, 2]]
450
- coords[:, [1, 3]] = self.image_height - coords[:, [1, 3]]
451
- coords[:, [0, 1, 2, 3]] = coords[:, [2, 3, 0, 1]]
476
+ coords[:, [0, 2]] = self.image_width - coords[:, [2, 0]]
477
+ coords[:, [1, 3]] = self.image_height - coords[:, [3, 1]]
452
478
  elif self.angle == 270:
453
- coords[:, [0, 1, 2, 3]] = coords[:, [1, 0, 3, 2]]
479
+ self.image_height = self.image_width
480
+ coords[:, [0, 1, 2, 3]] = coords[:, [3, 0, 1, 2]]
454
481
  coords[:, [0, 2]] = self.image_height - coords[:, [0, 2]]
455
- coords[:, [0, 1, 2, 3]] = coords[:, [2, 1, 0, 3]]
456
482
 
457
483
  return coords
458
484
 
@@ -473,17 +499,16 @@ class RotationTransform(BaseTransform):
473
499
  raise ValueError("Initialize image_width and image_height first")
474
500
 
475
501
  if self.angle == 90:
476
- coords[:, [0, 1, 2, 3]] = coords[:, [1, 0, 3, 2]]
477
- coords[:, [0, 2]] = self.image_width - coords[:, [0, 2]]
478
- coords[:, [0, 1, 2, 3]] = coords[:, [2, 1, 0, 3]]
502
+ self.image_height = self.image_width
503
+ coords[:, [0, 1, 2, 3]] = coords[:, [3, 0, 1, 2]]
504
+ coords[:, [0, 2]] = self.image_height - coords[:, [0, 2]]
479
505
  elif self.angle == 180:
480
- coords[:, [0, 2]] = self.image_width - coords[:, [0, 2]]
481
- coords[:, [1, 3]] = self.image_height - coords[:, [1, 3]]
482
- coords[:, [0, 1, 2, 3]] = coords[:, [2, 3, 0, 1]]
506
+ coords[:, [0, 2]] = self.image_width - coords[:, [2, 0]]
507
+ coords[:, [1, 3]] = self.image_height - coords[:, [3, 1]]
483
508
  elif self.angle == 270:
484
- coords[:, [0, 1, 2, 3]] = coords[:, [1, 0, 3, 2]]
485
- coords[:, [1, 3]] = self.image_height - coords[:, [1, 3]]
486
- coords[:, [0, 1, 2, 3]] = coords[:, [0, 3, 2, 1]]
509
+ self.image_width = self.image_height
510
+ coords[:, [0, 1, 2, 3]] = coords[:, [1, 2, 3, 0]]
511
+ coords[:, [1, 3]] = self.image_width - coords[:, [1, 3]]
487
512
  return coords
488
513
 
489
514
  def clone(self) -> RotationTransform:
@@ -20,10 +20,11 @@ Visualisation utils. Copied and pasted from
20
20
  """
21
21
 
22
22
  import base64
23
+ import hashlib
23
24
  import os
24
25
  import sys
25
26
  from io import BytesIO
26
- from typing import Any, Optional, Sequence, no_type_check
27
+ from typing import Any, Optional, Sequence, Tuple, Union, no_type_check
27
28
 
28
29
  import numpy as np
29
30
  import numpy.typing as npt
@@ -177,17 +178,23 @@ _COLORS = (
177
178
  )
178
179
 
179
180
 
180
- def random_color(rgb: bool = True, maximum: int = 255) -> tuple[int, int, int]:
181
+ def random_color(
182
+ rgb: bool = True, maximum: int = 255, deterministic_input_str: Optional[str] = None
183
+ ) -> tuple[int, int, int]:
181
184
  """
182
185
  Args:
183
186
  rgb: Whether to return RGB colors or BGR colors.
184
187
  maximum: Either 255 or 1.
188
+ deterministic_input_str: A string to use for deterministic color generation.
185
189
 
186
190
  Returns:
187
191
  A tuple of three integers representing the color.
188
192
  """
189
-
190
- idx = np.random.randint(0, len(_COLORS))
193
+ if deterministic_input_str:
194
+ hash_digest = hashlib.md5(deterministic_input_str.encode("utf-8")).hexdigest()
195
+ idx = int(hash_digest, 16) % len(_COLORS)
196
+ else:
197
+ idx = np.random.randint(0, len(_COLORS))
191
198
  ret = _COLORS[idx] * maximum
192
199
  if not rgb:
193
200
  ret = ret[::-1]
@@ -197,7 +204,7 @@ def random_color(rgb: bool = True, maximum: int = 255) -> tuple[int, int, int]:
197
204
  def draw_boxes(
198
205
  np_image: PixelValues,
199
206
  boxes: npt.NDArray[float32],
200
- category_names_list: Optional[list[Optional[str]]] = None,
207
+ category_names_list: Optional[list[Tuple[Union[str, None], Union[str, None]]]] = None,
201
208
  color: Optional[BGR] = None,
202
209
  font_scale: float = 1.0,
203
210
  rectangle_thickness: int = 4,
@@ -210,7 +217,8 @@ def draw_boxes(
210
217
  Args:
211
218
  np_image: Image as `np.ndarray`.
212
219
  boxes: A numpy array of shape Nx4 where each row is `[x1, y1, x2, y2]`.
213
- category_names_list: List of N category names.
220
+ category_names_list: List of N tuples. The first element is the category name, whereas the second element is
221
+ the value, that is going to be displayed in the text box..
214
222
  color: A 3-tuple BGR color (in range `[0, 255]`).
215
223
  font_scale: Font scale of text box.
216
224
  rectangle_thickness: Thickness of bounding box.
@@ -230,13 +238,14 @@ def draw_boxes(
230
238
  category_to_color = {}
231
239
  if box_color_by_category and category_names_list is not None:
232
240
  category_names = set(category_names_list)
233
- category_to_color = {category: random_color() for category in category_names}
234
-
241
+ category_to_color = {
242
+ category[1]: random_color(deterministic_input_str=category[1]) for category in category_names
243
+ }
235
244
  boxes = np.array(boxes, dtype="int32")
236
245
  if category_names_list is not None:
237
246
  assert len(category_names_list) == len(boxes), f"{len(category_names_list)} != {len(boxes)}"
238
247
  else:
239
- category_names_list = [None] * len(boxes)
248
+ category_names_list = [(None, None)] * len(boxes)
240
249
  areas = (boxes[:, 2] - boxes[:, 0] + 1) * (boxes[:, 3] - boxes[:, 1] + 1)
241
250
  sorted_inds = np.argsort(-areas) # draw large ones first
242
251
  assert areas.min() > 0, areas.min()
@@ -255,12 +264,12 @@ def draw_boxes(
255
264
  np_image = cv2.cvtColor(np_image, cv2.COLOR_GRAY2BGR).astype(np.uint8)
256
265
  for i in sorted_inds:
257
266
  box = boxes[i, :]
258
- choose_color = category_to_color.get(category_names_list[i]) if category_to_color is not None else color
267
+ choose_color = category_to_color.get(category_names_list[i][1]) if category_to_color is not None else color
259
268
  if choose_color is None:
260
269
  choose_color = random_color()
261
- if category_names_list[i] is not None:
270
+ if category_names_list[i][0] is not None:
262
271
  np_image = viz_handler.draw_text(
263
- np_image, (box[0], box[1]), category_names_list[i], color=choose_color, font_scale=font_scale
272
+ np_image, (box[0], box[1]), category_names_list[i][0], color=choose_color, font_scale=font_scale
264
273
  )
265
274
  np_image = viz_handler.draw_rectangle(
266
275
  np_image, (box[0], box[1], box[2], box[3]), choose_color, rectangle_thickness
@@ -423,7 +432,7 @@ class VizPackageHandler:
423
432
 
424
433
  @staticmethod
425
434
  def _cv2_read_image(path: PathLikeOrStr) -> PixelValues:
426
- return cv2.imread(os.fspath(path), cv2.IMREAD_COLOR).astype(np.uint8) # type: ignore
435
+ return cv2.imread(os.fspath(path), cv2.IMREAD_COLOR).astype(np.uint8) # type: ignore
427
436
 
428
437
  @staticmethod
429
438
  def _pillow_read_image(path: PathLikeOrStr) -> PixelValues:
@@ -517,7 +526,7 @@ class VizPackageHandler:
517
526
  @staticmethod
518
527
  def _cv2_convert_b64_to_np(image: B64Str) -> PixelValues:
519
528
  np_array = np.fromstring(base64.b64decode(image), np.uint8) # type: ignore
520
- np_array = cv2.imdecode(np_array, cv2.IMREAD_COLOR).astype(np.float32) # type: ignore
529
+ np_array = cv2.imdecode(np_array, cv2.IMREAD_COLOR).astype(np.float32) # type: ignore
521
530
  return np_array.astype(uint8)
522
531
 
523
532
  @staticmethod
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepdoctection
3
- Version: 0.45.0
3
+ Version: 0.46
4
4
  Summary: Repository for Document AI
5
5
  Home-page: https://github.com/deepdoctection/deepdoctection
6
6
  Author: Dr. Janis Meyer
@@ -19,18 +19,15 @@ Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: catalogue==2.0.10
21
21
  Requires-Dist: huggingface_hub>=0.26.0
22
- Requires-Dist: importlib-metadata>=5.0.0
23
22
  Requires-Dist: jsonlines==3.1.0
24
23
  Requires-Dist: lazy-imports==0.3.1
25
24
  Requires-Dist: mock==4.0.3
26
- Requires-Dist: networkx>=2.7.1
27
25
  Requires-Dist: numpy>2.0
28
26
  Requires-Dist: packaging>=20.0
29
27
  Requires-Dist: Pillow>=10.0.0
30
28
  Requires-Dist: pypdf>=6.0.0
31
29
  Requires-Dist: pypdfium2>=4.30.0
32
30
  Requires-Dist: pyyaml>=6.0.1
33
- Requires-Dist: pyzmq>=16
34
31
  Requires-Dist: scipy>=1.13.1
35
32
  Requires-Dist: termcolor>=1.1
36
33
  Requires-Dist: tabulate>=0.7.7
@@ -38,18 +35,15 @@ Requires-Dist: tqdm>=4.64.0
38
35
  Provides-Extra: tf
39
36
  Requires-Dist: catalogue==2.0.10; extra == "tf"
40
37
  Requires-Dist: huggingface_hub>=0.26.0; extra == "tf"
41
- Requires-Dist: importlib-metadata>=5.0.0; extra == "tf"
42
38
  Requires-Dist: jsonlines==3.1.0; extra == "tf"
43
39
  Requires-Dist: lazy-imports==0.3.1; extra == "tf"
44
40
  Requires-Dist: mock==4.0.3; extra == "tf"
45
- Requires-Dist: networkx>=2.7.1; extra == "tf"
46
41
  Requires-Dist: numpy>2.0; extra == "tf"
47
42
  Requires-Dist: packaging>=20.0; extra == "tf"
48
43
  Requires-Dist: Pillow>=10.0.0; extra == "tf"
49
44
  Requires-Dist: pypdf>=6.0.0; extra == "tf"
50
45
  Requires-Dist: pypdfium2>=4.30.0; extra == "tf"
51
46
  Requires-Dist: pyyaml>=6.0.1; extra == "tf"
52
- Requires-Dist: pyzmq>=16; extra == "tf"
53
47
  Requires-Dist: scipy>=1.13.1; extra == "tf"
54
48
  Requires-Dist: termcolor>=1.1; extra == "tf"
55
49
  Requires-Dist: tabulate>=0.7.7; extra == "tf"
@@ -62,25 +56,24 @@ Requires-Dist: python-doctr==0.10.0; extra == "tf"
62
56
  Requires-Dist: pycocotools>=2.0.2; extra == "tf"
63
57
  Requires-Dist: boto3==1.34.102; extra == "tf"
64
58
  Requires-Dist: pdfplumber>=0.11.0; extra == "tf"
59
+ Requires-Dist: pyzmq>=16; extra == "tf"
65
60
  Requires-Dist: jdeskew>=0.2.2; extra == "tf"
66
61
  Requires-Dist: apted==1.0.3; extra == "tf"
67
62
  Requires-Dist: distance==0.1.3; extra == "tf"
68
63
  Requires-Dist: lxml>=4.9.1; extra == "tf"
64
+ Requires-Dist: networkx>=2.7.1; extra == "tf"
69
65
  Provides-Extra: pt
70
66
  Requires-Dist: catalogue==2.0.10; extra == "pt"
71
67
  Requires-Dist: huggingface_hub>=0.26.0; extra == "pt"
72
- Requires-Dist: importlib-metadata>=5.0.0; extra == "pt"
73
68
  Requires-Dist: jsonlines==3.1.0; extra == "pt"
74
69
  Requires-Dist: lazy-imports==0.3.1; extra == "pt"
75
70
  Requires-Dist: mock==4.0.3; extra == "pt"
76
- Requires-Dist: networkx>=2.7.1; extra == "pt"
77
71
  Requires-Dist: numpy>2.0; extra == "pt"
78
72
  Requires-Dist: packaging>=20.0; extra == "pt"
79
73
  Requires-Dist: Pillow>=10.0.0; extra == "pt"
80
74
  Requires-Dist: pypdf>=6.0.0; extra == "pt"
81
75
  Requires-Dist: pypdfium2>=4.30.0; extra == "pt"
82
76
  Requires-Dist: pyyaml>=6.0.1; extra == "pt"
83
- Requires-Dist: pyzmq>=16; extra == "pt"
84
77
  Requires-Dist: scipy>=1.13.1; extra == "pt"
85
78
  Requires-Dist: termcolor>=1.1; extra == "pt"
86
79
  Requires-Dist: tabulate>=0.7.7; extra == "pt"
@@ -92,10 +85,12 @@ Requires-Dist: python-doctr==0.10.0; extra == "pt"
92
85
  Requires-Dist: pycocotools>=2.0.2; extra == "pt"
93
86
  Requires-Dist: boto3==1.34.102; extra == "pt"
94
87
  Requires-Dist: pdfplumber>=0.11.0; extra == "pt"
88
+ Requires-Dist: pyzmq>=16; extra == "pt"
95
89
  Requires-Dist: jdeskew>=0.2.2; extra == "pt"
96
90
  Requires-Dist: apted==1.0.3; extra == "pt"
97
91
  Requires-Dist: distance==0.1.3; extra == "pt"
98
92
  Requires-Dist: lxml>=4.9.1; extra == "pt"
93
+ Requires-Dist: networkx>=2.7.1; extra == "pt"
99
94
  Provides-Extra: docs
100
95
  Requires-Dist: tensorpack==0.11; extra == "docs"
101
96
  Requires-Dist: boto3==1.34.102; extra == "docs"
@@ -1,9 +1,9 @@
1
- deepdoctection/__init__.py,sha256=nyR805N1k7HNYBtc8gqshSBvRxpK0JTKHWchetqQjno,13125
1
+ deepdoctection/__init__.py,sha256=mFZGAnUHzhgUshyF-ggZMExtd4ZyCdtg1-hriRty8Ek,13229
2
2
  deepdoctection/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  deepdoctection/analyzer/__init__.py,sha256=wg0BcFwdCeREwzZfa--Yx8HUJ9LPv5z5PmLwtkZdPH8,772
4
- deepdoctection/analyzer/config.py,sha256=_JqF0-6G-IQ9BmQit9OiUMhZDw7pd-eWlC2RPVpwVWQ,43932
4
+ deepdoctection/analyzer/config.py,sha256=5abZTB_U_FaZmNVkKUexSVZSmx_93Uj28VB-349jrCs,44153
5
5
  deepdoctection/analyzer/dd.py,sha256=2BGvZpl9o9khcaOV52-DPHMrs0DsqUO8cpdqFVHHzDQ,5176
6
- deepdoctection/analyzer/factory.py,sha256=DVrXVN-h0apWQG3shmmXwyvAcqLKIvMsW8bNG5cwI5s,47668
6
+ deepdoctection/analyzer/factory.py,sha256=s4RjPgPhB8_raiUbEyACRuF9S_DRQzAczWKDRmzsyhc,48189
7
7
  deepdoctection/configs/__init__.py,sha256=TX_P6tqDOF1LK1mi9ruAl7x0mtv1Asm8cYWCz3Pe2dk,646
8
8
  deepdoctection/configs/conf_dd_one.yaml,sha256=DHqAIKH3jRam54QO7qib2zutmpyFA8TqdV5UvIV191A,3688
9
9
  deepdoctection/configs/conf_tesseract.yaml,sha256=oF6szDyoi15FHvq7yFUNIEjfA_jNLhGxoowiRsz_zY4,35
@@ -13,15 +13,15 @@ deepdoctection/dataflow/base.py,sha256=ZLRijyHI1J7tBfnE-q7eqUieYMMERjtK-c1oK40dB
13
13
  deepdoctection/dataflow/common.py,sha256=DKD_pRZBCt2vO3oNZcOvdoC3jThabTNcNbTS16mpVR0,10351
14
14
  deepdoctection/dataflow/custom.py,sha256=xlw1Op4J3a8PNIlzY5stAY6olpBTN8KMhj1KQ7gf8tA,6792
15
15
  deepdoctection/dataflow/custom_serialize.py,sha256=zWDx1_mkPpeot9VN-4P1C2sFtK6vYUaFoSs6UiiLMZA,23234
16
- deepdoctection/dataflow/parallel_map.py,sha256=Xhem9lvNDKVd_x02Ih9qB4J6bEnxNbc8uHIro8mX9UU,15783
16
+ deepdoctection/dataflow/parallel_map.py,sha256=8cTaj9tJjpPFHYtfNNaekQZCywIVc3aa2hMHa5nsFbA,16058
17
17
  deepdoctection/dataflow/serialize.py,sha256=G5kfkFHyhy3E3AusTvTizBi0EVDU2YZov9a-LmXPjy4,4592
18
18
  deepdoctection/dataflow/stats.py,sha256=AN5cbagveaDGqCXaYj6iWITpn-a2eO_AumA-vQNQ_XE,9764
19
19
  deepdoctection/datapoint/__init__.py,sha256=ruyV4DTOkUFWhkJ5VO_eJmrAxrzgygzTtr4W-WkZybs,1615
20
20
  deepdoctection/datapoint/annotation.py,sha256=f32BNmzUGJoNMeGst2RGC2jmjJpzzjxyBRKFG8FCubY,23092
21
- deepdoctection/datapoint/box.py,sha256=QAS8sK2Ge4_ysW6zOYkLlzNwhSyw_mhYcYsxscClEno,31453
21
+ deepdoctection/datapoint/box.py,sha256=rJUcQfCIwIpYsyifxMZ_r-NNRpX-itISATA3OKmVyak,31533
22
22
  deepdoctection/datapoint/convert.py,sha256=6ENXX3tBdY8ogb2NBPxsOsQMGnQux8ol5nrUfWS5tYE,7352
23
- deepdoctection/datapoint/image.py,sha256=N5VH2oeKQWIt5FQvFaeu-FL8eckv7LQS0ZJsHSuVwjI,37187
24
- deepdoctection/datapoint/view.py,sha256=x7BuWWHWOMQa_dZiKxlNIoq1NxZ-Z2F9nOW6vsJOxwE,61910
23
+ deepdoctection/datapoint/image.py,sha256=kH3dPQlI7-Q1lA40cjnp5FT84z03GSsiwoVmWyhN7Qo,37230
24
+ deepdoctection/datapoint/view.py,sha256=m7R9XmH4qX6IOr1hNSog6h5JDx07DAM2GIEk6rDjSbs,63022
25
25
  deepdoctection/datasets/__init__.py,sha256=4ifjIwWCPYiS31GzUlVDScrkNOrb1eo5xHlRXNyg_58,994
26
26
  deepdoctection/datasets/adapter.py,sha256=VSLM_980aHi4TpgOxfxiBHiF_fUXyh348PXet6zTo-4,7779
27
27
  deepdoctection/datasets/base.py,sha256=oLv2o9QiKVN44kO7Llj-z00_TQRYBsVlvBL3ZQoscUQ,30670
@@ -45,23 +45,23 @@ deepdoctection/datasets/instances/xsl/pascal_voc.xsl,sha256=DlzFV2P8NtQKXVe96i-m
45
45
  deepdoctection/eval/__init__.py,sha256=deGj63ejU9f3nthBU6GI25QIQidKWJmIW4q8fpn12bU,920
46
46
  deepdoctection/eval/accmetric.py,sha256=TlOFUU9y9BBjJKVsRMyoVKpLZl6AflNsZ4thqSEie4k,19957
47
47
  deepdoctection/eval/base.py,sha256=mYVvzD_wVPwsrBqcl1O4Vqqhg1yGtlG6hkuMjVZvt-k,5290
48
- deepdoctection/eval/cocometric.py,sha256=H-BsLeV9S93tG6jzUN-3FCPXYiUUoTAYuznE5SvS9Bc,11070
48
+ deepdoctection/eval/cocometric.py,sha256=xB7M5w4VFI67EPfsmS92EpTlayQRqjIiHS_T0ENsfq4,11156
49
49
  deepdoctection/eval/eval.py,sha256=UUL-wk39RONLMOOyH3WjjpHunZJiQluXZFqir8eaDtY,19808
50
50
  deepdoctection/eval/registry.py,sha256=us6EGN_tAia1Mk1mwWQwDeE-xqxcuopztdi8n-ieGbg,1100
51
51
  deepdoctection/eval/tedsmetric.py,sha256=EcNeJynsmxyl5bOH3bjy2wE647ONf0SF5OZyGbVu35Q,9963
52
52
  deepdoctection/eval/tp_eval_callback.py,sha256=lqrOn2tdaRiF_Vr_9CwBr2ryatcWu3mQKya8YZ2pA9A,5261
53
53
  deepdoctection/extern/__init__.py,sha256=1RVkuC0MPlz_g4nhU-nc0sPIRR72JWeDgZtyy4BWw8w,1011
54
- deepdoctection/extern/base.py,sha256=LomTR9HXcBU55MPDIA8D1rIamk7DUmToJmgcRXzCoeU,31650
54
+ deepdoctection/extern/base.py,sha256=8bhTb4PiZlSbSZYnH65FE55juq6hM1TzbCkZHoIUE8w,32077
55
55
  deepdoctection/extern/d2detect.py,sha256=O8XN_sUrQThMmd9-t97lzZvTGVSMG-1DD_VR5TV9V8c,22375
56
56
  deepdoctection/extern/deskew.py,sha256=L_jU0rXh03qzwaT79EIqE_zYMUVeFwWDbsGbtahuL2k,3124
57
- deepdoctection/extern/doctrocr.py,sha256=jB0mnvGmmygoUu9e9zw2_HtAgQUdCJHbxMSt1cfK5bA,25381
57
+ deepdoctection/extern/doctrocr.py,sha256=vF-OA9UzH_NizkSoATvCdGEKbFReThGenoJOKFCHMUA,26299
58
58
  deepdoctection/extern/fastlang.py,sha256=0nBFZTwMS5s9fhjgMc_p0y18V6wZwQme0r6B6B3uFro,4952
59
59
  deepdoctection/extern/hfdetr.py,sha256=N3eLNI5BsQS9_7YZyBeWndSgUydJij7ugZA9p4V1xaQ,14316
60
- deepdoctection/extern/hflayoutlm.py,sha256=htPfwwJ5VpzYP6CZ86YHwNfvJ7fdhDF_rxTKUa8rG9w,60488
60
+ deepdoctection/extern/hflayoutlm.py,sha256=ZXXX9BTF5zjcyZaDbhGizfDC_xpvD2LyEivElwYuIGc,59972
61
61
  deepdoctection/extern/hflm.py,sha256=ftr5jLb39521KtHZOEyTWuaE7bnbSwm3EQSHlcynXIM,27585
62
62
  deepdoctection/extern/model.py,sha256=kMIlx07_kdwZHLYB3QUG0DT_VSv2aZuKIIbv3fs0WqA,18233
63
63
  deepdoctection/extern/pdftext.py,sha256=ljzPQn3yYAlS6MoZqzixD-fO2GlHwu1aMiOQ6qMIzbg,7513
64
- deepdoctection/extern/tessocr.py,sha256=SuPmngsJg38riL4b09z6_FIzJH6H3RIwoighG2GPMYM,17457
64
+ deepdoctection/extern/tessocr.py,sha256=r2UTxOCAHtZP863urD5IwTrJxKk7C9fhv5p5suMTCHA,18376
65
65
  deepdoctection/extern/texocr.py,sha256=wVOuu6eUGao0mUbC8vrgdCsKfY1GqA1Am9560YgWyXU,5915
66
66
  deepdoctection/extern/tpdetect.py,sha256=Kr00n80V_OfE-EGfpjiVw1eAQ2n2tuT-hSco-dLSR9E,8516
67
67
  deepdoctection/extern/pt/__init__.py,sha256=3Cu0ZHjbYsJomru7-RQXEHihEQLegZrmLetlHiqS58I,742
@@ -111,13 +111,13 @@ deepdoctection/pipe/doctectionpipe.py,sha256=ik5F92F3klI5Nve_AnyIRj-ApMoKHSR2Sjc
111
111
  deepdoctection/pipe/language.py,sha256=VZvw1hYrs4F1g2aSmqt16jAzgigedui2dptcRtvASfY,5949
112
112
  deepdoctection/pipe/layout.py,sha256=pm53RUyMCERHJVWRJmeDUfjf-6DlRuTtUGETHpyr1UY,6391
113
113
  deepdoctection/pipe/lm.py,sha256=XtvaqjPK-2exWOmzznCZORL5MjkP-33fd0MlJpTtbMA,20617
114
- deepdoctection/pipe/order.py,sha256=m31RLoQNTpUTMpuyrAZKcTnRhyPLZ_Bmb1Ngxs7JkbY,41129
115
- deepdoctection/pipe/refine.py,sha256=AazkdLz5F1H8OIO8d1oFY4pqOprP0zW42ZeXVfeUtew,23422
114
+ deepdoctection/pipe/order.py,sha256=Sfp4SVbQ9LZNjCayZfwV4MObUQTopBK_zIXBCBp69ZQ,41226
115
+ deepdoctection/pipe/refine.py,sha256=KhTiTwQTleUJrDE6MO094jq6p_9POxOyYdovejGnJtM,23738
116
116
  deepdoctection/pipe/registry.py,sha256=uT5fnHjffoNGk2JPuD2-pMYtO3Iko7-wrwVZVCWLtok,906
117
117
  deepdoctection/pipe/segment.py,sha256=ACMHUDigyEMuNXp8crwFXbWCew1-oAdlAa5fV7PfL50,61964
118
118
  deepdoctection/pipe/sub_layout.py,sha256=YH05W24yLvAdEwC5CLGiVSOLlo0O-mNeIjRZPd9FEIQ,14135
119
- deepdoctection/pipe/text.py,sha256=4iRk5K18WxB2VUSARALrmfpj1hR0CyVoj-NJW7O7hHA,11150
120
- deepdoctection/pipe/transform.py,sha256=KU5WQ-90AaltSQLuRz0D91OkhSkb6hHRNfSNMW3ANeo,4716
119
+ deepdoctection/pipe/text.py,sha256=l4OoAFWpY7wp_oZplzuA7BOs52BkEXIDWiIsq6zlBK0,11444
120
+ deepdoctection/pipe/transform.py,sha256=LG5qpbTK0sdnIRYU8upqxAaLcA497CHo-rXpRnssQgc,4941
121
121
  deepdoctection/train/__init__.py,sha256=YFTRAZF1F7cEAKTdAIi1BLyYb6rSRcwq09Ui5Lu8d6E,1071
122
122
  deepdoctection/train/d2_frcnn_train.py,sha256=edmyNTBRMM_TuL_1D6G2TSY9CBqNndIuyKree_KAso0,15508
123
123
  deepdoctection/train/hf_detr_train.py,sha256=El-VHggdBObttFQwFIfQs5xm7aaxpC5IzNUJ1gF4Z6E,13278
@@ -129,21 +129,21 @@ deepdoctection/utils/context.py,sha256=5QfdzxsiSPnNs1qtJdgjguIoD8srLQ2W8oeDzwp9F
129
129
  deepdoctection/utils/develop.py,sha256=4myrqBDypM6tQ2a2Jo3Q20RuE_W2czykpXBwgXPrxNw,3568
130
130
  deepdoctection/utils/env_info.py,sha256=b1WohrfQuoL-BPN0_s8Rjtwzx-WKvCyaX2I4qYl1Emc,19878
131
131
  deepdoctection/utils/error.py,sha256=sIry8F5MZ0yLvKfAwVz90IorKWVvjoRqcC0L8qq8mLk,2480
132
- deepdoctection/utils/file_utils.py,sha256=PzUAE7eaiPl-m4SKXNF5_s3Ks7B0WeolmRaIJ7FNO2U,26276
132
+ deepdoctection/utils/file_utils.py,sha256=Qr6Q9ruzi7xMDYh5W1V2_9HZvn2-VzSjH0eu6dhJTpY,26844
133
133
  deepdoctection/utils/fs.py,sha256=KTS9FJzZk9le_vmIPr9IisJw0AyTfjkyX1KoWQy4DNs,12729
134
134
  deepdoctection/utils/identifier.py,sha256=Jt12MeZf7eC1qciY5Fp_AYUGxYVcjsy7xNBUvJil7dU,2270
135
- deepdoctection/utils/logger.py,sha256=ddQ0xBStluf8OvoRlEB8YkqyRR-ZYgyJYLClTmJJMAU,10290
135
+ deepdoctection/utils/logger.py,sha256=Mj7FA4B879rYBFbANaoH0UwP4oxLOPt7zxqobKJ4910,11418
136
136
  deepdoctection/utils/metacfg.py,sha256=5M390--ZMoyJEt5oZOwFMGt2i8OF_ayeb0NVmUO_3OQ,7235
137
137
  deepdoctection/utils/mocks.py,sha256=IkN3-IzAl4eX0ibgKIHg8IY7ykVw6BnpF6XnxKnKaZI,2389
138
138
  deepdoctection/utils/pdf_utils.py,sha256=BrxTuY9j0COyIRkJchJ0tt2h6ZsA2an6z-H8E8QwgUQ,13490
139
139
  deepdoctection/utils/settings.py,sha256=nzD2OMxfsL50CvKGnbwn8IWW-t5wGfCS439HFian274,12920
140
140
  deepdoctection/utils/tqdm.py,sha256=kx3Ivf0x85S0ZmEaN5mImu0V6isOgygOU8iyr2U99XU,1850
141
- deepdoctection/utils/transform.py,sha256=jgeCyQWLN9q79jCGW7jysyKUKcJ1AVMk8OslF-3fbag,16095
141
+ deepdoctection/utils/transform.py,sha256=2naYQSyIhVmqhocoYgMZA6mSC-XJb8l_fYMWvJFR1oo,16624
142
142
  deepdoctection/utils/types.py,sha256=Nsr2J7XSZazXho94y0oc01LBQxh0ve67c4Yx2gMlSXU,2952
143
143
  deepdoctection/utils/utils.py,sha256=NBUb1qbx8Jm-AvYN1Sdbk0huXhbAKxZ-ZtOcMespsMM,7064
144
- deepdoctection/utils/viz.py,sha256=Aduyr65LoI4l9Fv4HCm4Sz9Fa_rL5mR5mQwLN4rqLdM,27385
145
- deepdoctection-0.45.0.dist-info/licenses/LICENSE,sha256=GQ0rUvuGdrMNEI3iHK5UQx6dIMU1QwAuyXsxUHn5MEQ,11351
146
- deepdoctection-0.45.0.dist-info/METADATA,sha256=1obCXEh1LnL5mDI_95SHiu5KrehKqP2r3SL5j4Hm2OE,14972
147
- deepdoctection-0.45.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
148
- deepdoctection-0.45.0.dist-info/top_level.txt,sha256=hs2DdoOL9h4mnHhmO82BT4pz4QATIoOZ20PZmlnxFI8,15
149
- deepdoctection-0.45.0.dist-info/RECORD,,
144
+ deepdoctection/utils/viz.py,sha256=C_zHbFhn9rpspj9GdS9rffLap2x_cpzqdepd5xbkoAE,27990
145
+ deepdoctection-0.46.dist-info/licenses/LICENSE,sha256=GQ0rUvuGdrMNEI3iHK5UQx6dIMU1QwAuyXsxUHn5MEQ,11351
146
+ deepdoctection-0.46.dist-info/METADATA,sha256=dATdyi6eLfJfY1y0GYVhxY3Yttfvl0wSwjwBKgWvMAw,14761
147
+ deepdoctection-0.46.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
148
+ deepdoctection-0.46.dist-info/top_level.txt,sha256=hs2DdoOL9h4mnHhmO82BT4pz4QATIoOZ20PZmlnxFI8,15
149
+ deepdoctection-0.46.dist-info/RECORD,,