deepdoctection 0.40.0__py3-none-any.whl → 0.42.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.

Potentially problematic release.


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

@@ -157,8 +157,8 @@ def match_anns_by_intersection(
157
157
 
158
158
  def match_anns_by_distance(
159
159
  dp: Image,
160
- parent_ann_category_names: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]]=None,
161
- child_ann_category_names: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]]=None,
160
+ parent_ann_category_names: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
161
+ child_ann_category_names: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
162
162
  parent_ann_ids: Optional[Union[Sequence[str], str]] = None,
163
163
  child_ann_ids: Optional[Union[str, Sequence[str]]] = None,
164
164
  parent_ann_service_ids: Optional[Union[str, Sequence[str]]] = None,
@@ -95,11 +95,21 @@ def tf_nms_image_annotations(
95
95
  """
96
96
  if len(anns) == 1:
97
97
  return [anns[0].annotation_id]
98
+
98
99
  if not anns:
99
100
  return []
100
- ann_ids = np.array([ann.annotation_id for ann in anns], dtype="object")
101
101
 
102
- boxes = convert_to_tensor([ann.get_bounding_box(image_id).to_list(mode="xyxy") for ann in anns])
102
+ # First, identify priority annotations that should always be kept
103
+ priority_ann_ids = []
104
+
105
+ if prio:
106
+ for ann in anns:
107
+ if ann.category_name == prio:
108
+ priority_ann_ids.append(ann.annotation_id)
109
+
110
+ # If all annotations are priority or none are left for NMS, return all priority IDs
111
+ if len(priority_ann_ids) == len(anns):
112
+ return priority_ann_ids
103
113
 
104
114
  def priority_to_confidence(ann: ImageAnnotation, priority: str) -> float:
105
115
  if ann.category_name == priority:
@@ -108,10 +118,24 @@ def tf_nms_image_annotations(
108
118
  return ann.score
109
119
  raise ValueError("score cannot be None")
110
120
 
121
+ # Perform NMS only on non-priority annotations
122
+ ann_ids = np.array([ann.annotation_id for ann in anns], dtype="object")
123
+
124
+ # Get boxes for non-priority annotations
125
+ boxes = convert_to_tensor([ann.get_bounding_box(image_id).to_list(mode="xyxy") for ann in anns if ann.bounding_box
126
+ is not None])
127
+
111
128
  scores = convert_to_tensor([priority_to_confidence(ann, prio) for ann in anns])
112
129
  class_mask = convert_to_tensor(len(boxes), dtype=uint8)
130
+
113
131
  keep = non_max_suppression(boxes, scores, class_mask, iou_threshold=threshold)
114
- ann_ids_keep = ann_ids[keep]
115
- if not isinstance(ann_ids_keep, str):
116
- return ann_ids_keep.tolist()
117
- return []
132
+ kept_ids = ann_ids[keep]
133
+
134
+ # Convert to list if necessary
135
+ if isinstance(kept_ids, str):
136
+ kept_ids = [kept_ids]
137
+ elif not isinstance(kept_ids, list):
138
+ kept_ids = kept_ids.tolist()
139
+
140
+ # Combine priority annotations with surviving non-priority annotations
141
+ return list(set(priority_ann_ids + kept_ids))
@@ -30,6 +30,7 @@ import numpy as np
30
30
  from ..dataflow import DataFlow, MapData
31
31
  from ..datapoint.image import Image
32
32
  from ..datapoint.view import IMAGE_DEFAULTS, Page
33
+ from ..extern.base import DetectionResult
33
34
  from ..mapper.match import match_anns_by_distance, match_anns_by_intersection
34
35
  from ..mapper.misc import to_image
35
36
  from ..utils.settings import LayoutType, ObjectTypes, Relationships, TypeOrStr, get_type
@@ -51,8 +52,9 @@ class ImageCroppingService(PipelineComponent):
51
52
  """
52
53
 
53
54
  def __init__(
54
- self, category_names: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
55
- service_ids: Optional[Sequence[str]] = None
55
+ self,
56
+ category_names: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
57
+ service_ids: Optional[Sequence[str]] = None,
56
58
  ) -> None:
57
59
  """
58
60
  :param category_names: A single name or a list of category names to crop
@@ -105,11 +107,11 @@ class IntersectionMatcher:
105
107
  """
106
108
 
107
109
  def __init__(
108
- self,
109
- matching_rule: Literal["iou", "ioa"],
110
- threshold: float,
111
- use_weighted_intersections: bool = False,
112
- max_parent_only: bool = False,
110
+ self,
111
+ matching_rule: Literal["iou", "ioa"],
112
+ threshold: float,
113
+ use_weighted_intersections: bool = False,
114
+ max_parent_only: bool = False,
113
115
  ) -> None:
114
116
  """
115
117
  :param matching_rule: "iou" or "ioa"
@@ -129,12 +131,12 @@ class IntersectionMatcher:
129
131
  self.max_parent_only = max_parent_only
130
132
 
131
133
  def match(
132
- self,
133
- dp: Image,
134
- parent_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
135
- child_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
136
- parent_ann_service_ids: Optional[Union[str, Sequence[str]]] = None,
137
- child_ann_service_ids: Optional[Union[str, Sequence[str]]] = None,
134
+ self,
135
+ dp: Image,
136
+ parent_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
137
+ child_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
138
+ parent_ann_service_ids: Optional[Union[str, Sequence[str]]] = None,
139
+ child_ann_service_ids: Optional[Union[str, Sequence[str]]] = None,
138
140
  ) -> list[tuple[str, str]]:
139
141
  """
140
142
  The matching algorithm
@@ -187,12 +189,12 @@ class NeighbourMatcher:
187
189
  """
188
190
 
189
191
  def match(
190
- self,
191
- dp: Image,
192
- parent_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
193
- child_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
194
- parent_ann_service_ids: Optional[Union[str, Sequence[str]]] = None,
195
- child_ann_service_ids: Optional[Union[str, Sequence[str]]] = None,
192
+ self,
193
+ dp: Image,
194
+ parent_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
195
+ child_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
196
+ parent_ann_service_ids: Optional[Union[str, Sequence[str]]] = None,
197
+ child_ann_service_ids: Optional[Union[str, Sequence[str]]] = None,
196
198
  ) -> list[tuple[str, str]]:
197
199
  """
198
200
  The matching algorithm
@@ -232,6 +234,8 @@ class FamilyCompound:
232
234
  child_categories: Optional[Union[ObjectTypes, Sequence[ObjectTypes]]] = field(default=None)
233
235
  parent_ann_service_ids: Optional[Union[str, Sequence[str]]] = field(default=None)
234
236
  child_ann_service_ids: Optional[Union[str, Sequence[str]]] = field(default=None)
237
+ create_synthetic_parent: bool = field(default=False)
238
+ synthetic_parent: Optional[ObjectTypes] = field(default=None)
235
239
 
236
240
  def __post_init__(self) -> None:
237
241
  if isinstance(self.parent_categories, str):
@@ -256,9 +260,9 @@ class MatchingService(PipelineComponent):
256
260
  """
257
261
 
258
262
  def __init__(
259
- self,
260
- family_compounds: Sequence[FamilyCompound],
261
- matcher: Union[IntersectionMatcher, NeighbourMatcher],
263
+ self,
264
+ family_compounds: Sequence[FamilyCompound],
265
+ matcher: Union[IntersectionMatcher, NeighbourMatcher],
262
266
  ) -> None:
263
267
  """
264
268
  :param family_compounds: A list of FamilyCompounds
@@ -286,6 +290,28 @@ class MatchingService(PipelineComponent):
286
290
 
287
291
  for pair in matched_pairs:
288
292
  self.dp_manager.set_relationship_annotation(family_compound.relationship_key, pair[0], pair[1])
293
+ if family_compound.synthetic_parent:
294
+ parent_anns = dp.get_annotation(category_names=family_compound.parent_categories)
295
+ child_anns = dp.get_annotation(category_names=family_compound.child_categories)
296
+ child_ann_ids = []
297
+ for parent in parent_anns:
298
+ if family_compound.relationship_key in parent.relationships:
299
+ child_ann_ids.extend(parent.get_relationship(family_compound.relationship_key))
300
+ detect_result_list = []
301
+ for child_ann in child_anns:
302
+ if child_ann.annotation_id not in child_ann_ids:
303
+ detect_result_list.append(DetectionResult(
304
+ class_name=family_compound.synthetic_parent,
305
+ box=child_ann.get_bounding_box(dp.image_id).to_list(mode="xyxy"),
306
+ absolute_coords=child_ann.get_bounding_box(dp.image_id).absolute_coords,
307
+ relationships={family_compound.relationship_key: child_ann.annotation_id}))
308
+ for detect_result in detect_result_list:
309
+ annotation_id = self.dp_manager.set_image_annotation(detect_result)
310
+ if annotation_id is not None and detect_result.relationships is not None:
311
+ self.dp_manager.set_relationship_annotation(family_compound.relationship_key,
312
+ annotation_id,
313
+ detect_result.relationships.get(
314
+ family_compound.relationship_key, None))
289
315
 
290
316
  def clone(self) -> PipelineComponent:
291
317
  return self.__class__(self.family_compounds, self.matcher)
@@ -315,10 +341,10 @@ class PageParsingService(PipelineComponent):
315
341
  """
316
342
 
317
343
  def __init__(
318
- self,
319
- text_container: TypeOrStr,
320
- floating_text_block_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
321
- include_residual_text_container: bool = True,
344
+ self,
345
+ text_container: TypeOrStr,
346
+ floating_text_block_categories: Optional[Union[TypeOrStr, Sequence[TypeOrStr]]] = None,
347
+ include_residual_text_container: bool = True,
322
348
  ):
323
349
  """
324
350
  :param text_container: name of an image annotation that has a CHARS sub category. These annotations will be
@@ -400,10 +426,10 @@ class AnnotationNmsService(PipelineComponent):
400
426
  """
401
427
 
402
428
  def __init__(
403
- self,
404
- nms_pairs: Sequence[Sequence[TypeOrStr]],
405
- thresholds: Union[float, Sequence[float]],
406
- priority: Optional[Sequence[Union[Optional[TypeOrStr]]]] = None,
429
+ self,
430
+ nms_pairs: Sequence[Sequence[TypeOrStr]],
431
+ thresholds: Union[float, Sequence[float]],
432
+ priority: Optional[Sequence[Union[Optional[TypeOrStr]]]] = None,
407
433
  ):
408
434
  """
409
435
  :param nms_pairs: Groups of categories, either as string or by `ObjectType`.
@@ -441,6 +441,7 @@ def segment_table(
441
441
  matching_rule=segment_rule,
442
442
  threshold=threshold_rows,
443
443
  use_weighted_intersections=True,
444
+ # Rows and columns are child annotations of the table.
444
445
  parent_ann_ids=child_ann_ids,
445
446
  child_ann_ids=child_ann_ids,
446
447
  )
@@ -452,6 +453,7 @@ def segment_table(
452
453
  matching_rule=segment_rule,
453
454
  threshold=threshold_cols,
454
455
  use_weighted_intersections=True,
456
+ # Rows and columns are child annotations of the table.
455
457
  parent_ann_ids=child_ann_ids,
456
458
  child_ann_ids=child_ann_ids,
457
459
  )
@@ -624,6 +626,7 @@ def segment_pubtables(
624
626
  matching_rule=segment_rule,
625
627
  threshold=threshold_rows,
626
628
  use_weighted_intersections=True,
629
+ # Rows and columns are child annotations of the table.
627
630
  parent_ann_ids=child_ann_ids,
628
631
  child_ann_ids=child_ann_ids,
629
632
  )
@@ -635,6 +638,7 @@ def segment_pubtables(
635
638
  matching_rule=segment_rule,
636
639
  threshold=threshold_cols,
637
640
  use_weighted_intersections=True,
641
+ # Rows and columns are child annotations of the table.
638
642
  parent_ann_ids=child_ann_ids,
639
643
  child_ann_ids=child_ann_ids,
640
644
  )
@@ -153,8 +153,8 @@ class SubImageLayoutService(PipelineComponent):
153
153
  **Example**
154
154
 
155
155
  detect_result_generator = DetectResultGenerator(categories_items)
156
- d_items = TPFrcnnDetector(item_config_path, item_weights_path, {"1": LayoutType.row,
157
- "2": LayoutType.column})
156
+ d_items = TPFrcnnDetector(item_config_path, item_weights_path, {1: LayoutType.row,
157
+ 2: LayoutType.column})
158
158
  item_component = SubImageLayoutService(d_items, LayoutType.table, detect_result_generator)
159
159
  """
160
160
 
@@ -162,6 +162,7 @@ class SubImageLayoutService(PipelineComponent):
162
162
  self,
163
163
  sub_image_detector: ObjectDetector,
164
164
  sub_image_names: Union[str, Sequence[TypeOrStr]],
165
+ service_ids: Optional[Sequence[str]] = None,
165
166
  detect_result_generator: Optional[DetectResultGenerator] = None,
166
167
  padder: Optional[PadTransform] = None,
167
168
  ):
@@ -170,7 +171,8 @@ class SubImageLayoutService(PipelineComponent):
170
171
  :param sub_image_names: Category names of ImageAnnotations to be presented to the detector.
171
172
  Attention: The selected ImageAnnotations must have: attr:`image` and: attr:`image.image`
172
173
  not None.
173
- :param category_id_mapping: Mapping of category IDs. Usually, the category ids start with 1.
174
+ :param service_ids: List of service ids to be used for filtering the ImageAnnotations. If None, all
175
+ ImageAnnotations will be used.
174
176
  :param detect_result_generator: 'DetectResultGenerator' instance. 'categories' attribute has to be the same as
175
177
  the 'categories' attribute of the 'sub_image_detector'. The generator will be
176
178
  responsible to create 'DetectionResult' for some categories, if they have not
@@ -184,6 +186,7 @@ class SubImageLayoutService(PipelineComponent):
184
186
  if isinstance(sub_image_names, str)
185
187
  else tuple((get_type(cat) for cat in sub_image_names))
186
188
  )
189
+ self.service_ids = service_ids
187
190
  self.detect_result_generator = detect_result_generator
188
191
  self.padder = padder
189
192
  self.predictor = sub_image_detector
@@ -205,7 +208,7 @@ class SubImageLayoutService(PipelineComponent):
205
208
  - Optionally invoke the DetectResultGenerator
206
209
  - Generate ImageAnnotations and dump to parent image and sub image.
207
210
  """
208
- sub_image_anns = dp.get_annotation(category_names=self.sub_image_name)
211
+ sub_image_anns = dp.get_annotation(category_names=self.sub_image_name, service_ids=self.service_ids)
209
212
  for sub_image_ann in sub_image_anns:
210
213
  np_image = self.prepare_np_image(sub_image_ann)
211
214
  detect_result_list = self.predictor.predict(np_image)
@@ -246,6 +249,7 @@ class SubImageLayoutService(PipelineComponent):
246
249
  return self.__class__(
247
250
  predictor,
248
251
  self.sub_image_name,
252
+ self.service_ids,
249
253
  self.detect_result_generator,
250
254
  padder_clone,
251
255
  )
@@ -1,5 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
- # File: transform.py
2
+ # File: test_transform.py
3
3
 
4
4
  # Copyright 2022 Dr. Janis Meyer. All rights reserved.
5
5
  #
@@ -22,6 +22,7 @@ on images (e.g. deskew, de-noising or more general GAN like operations.
22
22
 
23
23
  from __future__ import annotations
24
24
 
25
+ from .. import DetectionResult
25
26
  from ..datapoint.image import Image
26
27
  from ..extern.base import ImageTransformer
27
28
  from .base import MetaAnnotation, PipelineComponent
@@ -49,25 +50,46 @@ class SimpleTransformService(PipelineComponent):
49
50
  super().__init__(self._get_name(transform_predictor.name), self.transform_predictor.model_id)
50
51
 
51
52
  def serve(self, dp: Image) -> None:
52
- if dp.annotations:
53
- raise RuntimeError(
54
- "SimpleTransformService receives datapoints with ÌmageAnnotations. This violates the "
55
- "pipeline building API but this can currently be catched only at runtime. "
56
- "Please make sure that this component is the first one in the pipeline."
57
- )
58
-
59
53
  if dp.image is not None:
60
54
  detection_result = self.transform_predictor.predict(dp.image)
61
- transformed_image = self.transform_predictor.transform(dp.image, detection_result)
55
+ transformed_image = self.transform_predictor.transform_image(dp.image, detection_result)
62
56
  self.dp_manager.datapoint.clear_image(True)
63
57
  self.dp_manager.datapoint.image = transformed_image
64
- self.dp_manager.set_summary_annotation(
65
- summary_key=self.transform_predictor.get_category_names()[0],
66
- summary_name=self.transform_predictor.get_category_names()[0],
67
- summary_number=None,
68
- summary_value=getattr(detection_result, self.transform_predictor.get_category_names()[0].value, None),
69
- summary_score=detection_result.score,
70
- )
58
+ for category in self.transform_predictor.get_category_names():
59
+ self.dp_manager.set_summary_annotation(
60
+ summary_key=category,
61
+ summary_name=category,
62
+ summary_number=None,
63
+ summary_value=getattr(detection_result, category.value, None),
64
+ summary_score=detection_result.score,
65
+ )
66
+ detect_results = []
67
+ for ann in dp.get_annotation():
68
+ box = ann.get_bounding_box()
69
+ if not box.absolute_coords:
70
+ box = box.transform(dp.width, dp.height)
71
+ detect_results.append(
72
+ DetectionResult(
73
+ box=box.to_list(mode="xyxy"),
74
+ class_name=ann.category_name, # type: ignore
75
+ score=ann.score,
76
+ class_id=ann.category_id,
77
+ uuid=ann.annotation_id,
78
+ )
79
+ )
80
+ output_detect_results = self.transform_predictor.transform_coords(detect_results)
81
+ for detect_result in output_detect_results:
82
+ ann = dp.get_annotation(annotation_ids=detect_result.uuid)[0]
83
+ transformed_ann_id = self.dp_manager.set_image_annotation(detect_result)
84
+ if transformed_ann_id is None:
85
+ print("here")
86
+ transformed_ann = self.dp_manager.datapoint.get_annotation(annotation_ids=transformed_ann_id)[0]
87
+
88
+ for key, sub_ann in ann.sub_categories.items():
89
+ transformed_ann.dump_sub_category(key, sub_ann)
90
+ if ann.image is not None:
91
+ dp.image_ann_to_image(transformed_ann.annotation_id, ann.image.image is not None)
92
+ ann.deactivate()
71
93
 
72
94
  def clone(self) -> SimpleTransformService:
73
95
  return self.__class__(self.transform_predictor)
@@ -276,7 +276,7 @@ def train_hf_detr(
276
276
 
277
277
  if path_weights != "":
278
278
  model = TableTransformerForObjectDetection.from_pretrained(
279
- pretrained_model_name_or_path=path_weights, config=config
279
+ pretrained_model_name_or_path=path_weights, config=config, ignore_mismatched_sizes=True
280
280
  )
281
281
  else:
282
282
  model = TableTransformerForObjectDetection(config)
@@ -67,6 +67,11 @@ class PageType(ObjectTypes):
67
67
  DOCUMENT_TYPE = "document_type"
68
68
  LANGUAGE = "language"
69
69
  ANGLE = "angle"
70
+ SIZE = "size"
71
+ PAD_TOP = "pad_top"
72
+ PAD_BOTTOM = "pad_bottom"
73
+ PAD_LEFT = "pad_left"
74
+ PAD_RIGHT = "pad_right"
70
75
 
71
76
 
72
77
  @object_types_registry.register("SummaryType")