dataflow-cv 1.1.0__tar.gz → 1.2.0__tar.gz

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 (53) hide show
  1. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/PKG-INFO +2 -2
  2. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/README.md +1 -1
  3. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/__init__.py +1 -1
  4. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/base.py +147 -3
  5. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/coco_and_labelme.py +117 -88
  6. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/labelme_and_yolo.py +112 -199
  7. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/rle_converter.py +1 -0
  8. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/yolo_and_coco.py +177 -152
  9. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/base.py +32 -2
  10. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/coco_handler.py +82 -1
  11. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/labelme_handler.py +84 -3
  12. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/yolo_handler.py +114 -8
  13. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/visualize/base.py +109 -39
  14. dataflow_cv-1.2.0/dataflow/visualize/coco_visualizer.py +94 -0
  15. dataflow_cv-1.2.0/dataflow/visualize/labelme_visualizer.py +91 -0
  16. dataflow_cv-1.2.0/dataflow/visualize/yolo_visualizer.py +100 -0
  17. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/PKG-INFO +2 -2
  18. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/pyproject.toml +1 -1
  19. dataflow_cv-1.1.0/dataflow/visualize/coco_visualizer.py +0 -96
  20. dataflow_cv-1.1.0/dataflow/visualize/labelme_visualizer.py +0 -92
  21. dataflow_cv-1.1.0/dataflow/visualize/yolo_visualizer.py +0 -102
  22. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/LICENSE +0 -0
  23. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/__init__.py +0 -0
  24. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/__init__.py +0 -0
  25. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/convert.py +0 -0
  26. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/evaluate.py +0 -0
  27. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/utils.py +0 -0
  28. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/visualize.py +0 -0
  29. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/exceptions.py +0 -0
  30. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/main.py +0 -0
  31. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/__init__.py +0 -0
  32. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/utils.py +0 -0
  33. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/__init__.py +0 -0
  34. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/base.py +0 -0
  35. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/evaluator.py +0 -0
  36. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/metrics.py +0 -0
  37. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/result.py +0 -0
  38. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/utils.py +0 -0
  39. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/__init__.py +0 -0
  40. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/models.py +0 -0
  41. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/utils.py +0 -0
  42. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/util/__init__.py +0 -0
  43. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/util/file_util.py +0 -0
  44. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/util/logging_util.py +0 -0
  45. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/visualize/__init__.py +0 -0
  46. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/visualize/utils.py +0 -0
  47. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/SOURCES.txt +0 -0
  48. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/dependency_links.txt +0 -0
  49. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/entry_points.txt +0 -0
  50. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/not-zip-safe +0 -0
  51. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/requires.txt +0 -0
  52. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/top_level.txt +0 -0
  53. {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataflow-cv
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: A computer vision dataset processing library
5
5
  Author-email: DataFlow-CV Team <example@example.com>
6
6
  License: MIT
@@ -312,7 +312,7 @@ For detailed developer guidance including advanced test commands, debugging, and
312
312
 
313
313
  ### 🧪 Testing
314
314
 
315
- **362 tests, 75% code coverage.**
315
+ **370 tests, 75% code coverage.**
316
316
 
317
317
  ```bash
318
318
  pytest # All tests
@@ -269,7 +269,7 @@ For detailed developer guidance including advanced test commands, debugging, and
269
269
 
270
270
  ### 🧪 Testing
271
271
 
272
- **362 tests, 75% code coverage.**
272
+ **370 tests, 75% code coverage.**
273
273
 
274
274
  ```bash
275
275
  pytest # All tests
@@ -1,4 +1,4 @@
1
1
  """DataFlow-CV: A computer vision dataset processing library."""
2
2
 
3
- __version__ = "1.1.0"
3
+ __version__ = "1.2.0"
4
4
  __author__ = "DataFlow-CV Team"
@@ -9,10 +9,11 @@ import logging
9
9
  from abc import ABC, abstractmethod
10
10
  from dataclasses import dataclass, field
11
11
  from pathlib import Path
12
- from typing import Any, Dict, List, Optional, Tuple
12
+ from typing import Any, Dict, Iterator, List, Optional, Tuple
13
13
 
14
- from ..label.base import AnnotationResult
15
- from ..label.models import AnnotationFormat, DatasetAnnotations
14
+ from ..label.base import AnnotationResult, BaseAnnotationHandler
15
+ from ..label.models import (AnnotationFormat, DatasetAnnotations,
16
+ ImageAnnotation)
16
17
  from ..util.file_util import FileOperations
17
18
 
18
19
 
@@ -122,6 +123,149 @@ class BaseConverter(ABC):
122
123
 
123
124
  self.file_ops = FileOperations(logger=self.logger)
124
125
 
126
+ # Initialize conversion stats for batch convert()
127
+ self.conversion_stats: Dict[str, Any] = {}
128
+
129
+ # Source annotations retained for target handler creation
130
+ # (category extraction, image copying). Cleared in try/finally.
131
+ self._source_annotations_for_target: Optional[DatasetAnnotations] = None
132
+
133
+ def _ensure_categories_for_streaming(
134
+ self,
135
+ source_handler: BaseAnnotationHandler,
136
+ source_path: str,
137
+ kwargs: Dict,
138
+ ) -> None:
139
+ """Ensure categories are available before streaming iteration.
140
+
141
+ Called by ``stream_convert()`` before ``create_target_handler()``.
142
+ Subclasses may override to pre-load categories from source files
143
+ that donʼt expose them until ``read()`` / ``iter_images()`` runs.
144
+
145
+ Default: try ``source_handler.categories`` if available and it is
146
+ a genuine dict (not a mock).
147
+ """
148
+ cats = getattr(source_handler, "categories", None)
149
+ if isinstance(cats, dict) and cats:
150
+ self._source_annotations_for_target = DatasetAnnotations(
151
+ format=AnnotationFormat.UNKNOWN,
152
+ categories=cats.copy(),
153
+ )
154
+
155
+ @abstractmethod
156
+ def _convert_single_image(
157
+ self, image_ann: ImageAnnotation, **kwargs
158
+ ) -> ImageAnnotation:
159
+ """Convert a single ImageAnnotation from source to target format.
160
+
161
+ Operates on one image at a time for the streaming pipeline.
162
+ The coordinate transformation must match the batch
163
+ ``convert_annotations()`` for the same direction.
164
+
165
+ Args:
166
+ image_ann: Single ImageAnnotation with source-native coordinates.
167
+ **kwargs: Additional conversion parameters.
168
+
169
+ Returns:
170
+ New ImageAnnotation with target-native coordinates.
171
+ """
172
+ pass
173
+
174
+ def stream_convert(
175
+ self, source_path: str, target_path: str, **kwargs
176
+ ) -> ConversionResult:
177
+ """Convert annotations using streaming (per-image) pipeline.
178
+
179
+ Sources images one at a time via ``handler.iter_images()``, converts
180
+ each via ``_convert_single_image()``, and writes immediately via
181
+ ``target_handler.write_one()``.
182
+
183
+ Applicable when the target format supports per-file output
184
+ (YOLO .txt, LabelMe .json). Not applicable for COCO target.
185
+
186
+ Args:
187
+ source_path: Path to source annotations.
188
+ target_path: Path to target output.
189
+ **kwargs: Conversion parameters (class_file, image_dir, etc.).
190
+
191
+ Returns:
192
+ ConversionResult with conversion statistics.
193
+ """
194
+ start_time = datetime.datetime.now()
195
+ result = ConversionResult(
196
+ success=False,
197
+ source_format=self.source_format,
198
+ target_format=self.target_format,
199
+ source_path=source_path,
200
+ target_path=target_path,
201
+ )
202
+
203
+ num_images = 0
204
+ num_objects = 0
205
+
206
+ try:
207
+ # 1. Validate inputs
208
+ if not self.validate_inputs(source_path, target_path, kwargs):
209
+ result.add_error("Input validation failed")
210
+ return result
211
+
212
+ # 2. Create source handler
213
+ source_handler = self.create_source_handler(source_path, kwargs)
214
+
215
+ # 3. Extract categories from source (needed for target handler)
216
+ self._ensure_categories_for_streaming(
217
+ source_handler, source_path, kwargs
218
+ )
219
+
220
+ # 4. Create target handler
221
+ target_handler = self.create_target_handler(target_path, kwargs)
222
+
223
+ # 5. Stream: iterate, convert, write per image
224
+ write_dir = getattr(
225
+ target_handler, "label_dir",
226
+ Path(target_path)
227
+ )
228
+
229
+ for image_ann in source_handler.iter_images():
230
+ target_ann = self._convert_single_image(
231
+ image_ann, **kwargs
232
+ )
233
+ write_result = target_handler.write_one(
234
+ target_ann, write_dir
235
+ )
236
+ if not write_result.success:
237
+ err = (
238
+ f"Failed to write {target_ann.image_id}: "
239
+ f"{write_result.message}"
240
+ )
241
+ if self.strict_mode:
242
+ result.add_error(err)
243
+ return result
244
+ else:
245
+ result.add_warning(err)
246
+ continue
247
+
248
+ num_images += 1
249
+ num_objects += len(target_ann.objects)
250
+
251
+ result.success = True
252
+ result.num_images_converted = num_images
253
+ result.num_objects_converted = num_objects
254
+
255
+ duration = (datetime.datetime.now() - start_time).total_seconds()
256
+ result.add_metadata("duration_seconds", f"{duration:.2f}")
257
+
258
+ except ValueError as e:
259
+ result.add_error(str(e))
260
+ result.num_images_converted = num_images
261
+ result.num_objects_converted = num_objects
262
+ except Exception as e:
263
+ result.add_error(f"Unexpected error: {e}")
264
+ if self.verbose:
265
+ self.logger.exception("Streaming conversion failed")
266
+
267
+ return result
268
+
125
269
  def convert(self, source_path: str, target_path: str, **kwargs) -> ConversionResult:
126
270
  """
127
271
  Convert annotations from source format to target format (enhanced with verbose logging).
@@ -9,7 +9,7 @@ import logging
9
9
  from pathlib import Path
10
10
  from typing import Any, Dict, List, Optional, Tuple
11
11
 
12
- from ..label.base import AnnotationResult
12
+ from ..label.base import AnnotationResult, BaseAnnotationHandler
13
13
  from ..label.coco_handler import CocoAnnotationHandler
14
14
  from ..label.labelme_handler import LabelMeAnnotationHandler
15
15
  from ..label.models import (AnnotationFormat, BoundingBox, DatasetAnnotations,
@@ -46,24 +46,23 @@ class CocoAndLabelMeConverter(BaseConverter):
46
46
  self.logger.debug(f"Initialized converter, direction: {direction}")
47
47
 
48
48
  def convert(self, source_path: str, target_path: str, **kwargs) -> ConversionResult:
49
- """
50
- Convert annotations between COCO and LabelMe formats.
51
-
52
- Args:
53
- source_path: Path to source annotations
54
- target_path: Path for target annotations
55
- **kwargs: Additional conversion parameters:
56
- - class_file: Required for LabelMe→COCO, optional for COCO→LabelMe
57
- - image_dir: Optional for both directions
58
- - do_rle: Whether to output RLE format (default False, only for LabelMe→COCO)
49
+ """Convert annotations between COCO and LabelMe formats.
59
50
 
60
- Returns:
61
- ConversionResult with conversion status and details
51
+ Auto-selects pipeline:
52
+ - COCO→LabelMe: streaming (per-file .json output)
53
+ - LabelMe→COCO: batch (single JSON output)
62
54
  """
63
- # Store source_annotations for use in create_target_handler if needed
55
+ if self.source_to_target:
56
+ # COCO → LabelMe: streaming pipeline
57
+ return self.stream_convert(source_path, target_path, **kwargs)
58
+ else:
59
+ # LabelMe → COCO: batch pipeline (COCO is single JSON)
60
+ return self._batch_convert(source_path, target_path, **kwargs)
61
+
62
+ def _batch_convert(self, source_path: str, target_path: str, **kwargs) -> ConversionResult:
63
+ """Batch pipeline: read ALL → convert ALL → write ALL (for COCO target)."""
64
64
  self._source_annotations_for_target = None
65
65
 
66
- # 1. Validate inputs
67
66
  if not self.validate_inputs(source_path, target_path, kwargs):
68
67
  return self._create_conversion_result(
69
68
  success=False,
@@ -73,7 +72,6 @@ class CocoAndLabelMeConverter(BaseConverter):
73
72
  log_file_path=self.log_file_path,
74
73
  )
75
74
 
76
- # 2. Read data using source handler
77
75
  source_handler = self.create_source_handler(source_path, kwargs)
78
76
  read_result = source_handler.read()
79
77
  if not read_result.success:
@@ -85,7 +83,6 @@ class CocoAndLabelMeConverter(BaseConverter):
85
83
  log_file_path=self.log_file_path,
86
84
  )
87
85
 
88
- # 3. Convert data (format-specific conversions like category mapping)
89
86
  annotations = read_result.data
90
87
  converted_annotations = self.convert_annotations(annotations, kwargs)
91
88
 
@@ -94,22 +91,15 @@ class CocoAndLabelMeConverter(BaseConverter):
94
91
  f"Conversion completed, object count: {converted_annotations.num_objects}"
95
92
  )
96
93
 
97
- # 4. Write data using target handler
98
- # Wrap in try/finally to guarantee _source_annotations_for_target cleanup
99
94
  self._source_annotations_for_target = converted_annotations
100
95
  try:
101
96
  target_handler = self.create_target_handler(target_path, kwargs)
102
-
103
- if self.verbose:
104
- self.logger.debug(
105
- f"Created target handler: {target_handler.__class__.__name__}"
106
- )
107
-
108
- write_result = target_handler.write(converted_annotations, target_path)
97
+ write_result = target_handler.write(
98
+ converted_annotations, target_path
99
+ )
109
100
  finally:
110
101
  self._source_annotations_for_target = None
111
102
 
112
- # 5. Create result
113
103
  result = self._create_conversion_result(
114
104
  success=write_result.success,
115
105
  source_path=source_path,
@@ -127,14 +117,13 @@ class CocoAndLabelMeConverter(BaseConverter):
127
117
  f"Objects converted: {converted_annotations.num_objects}"
128
118
  )
129
119
 
130
- # 6. Add RLE accuracy warning if do_rle is True (LabelMe → COCO only)
131
- if not self.source_to_target: # LabelMe → COCO
132
- do_rle = kwargs.get("do_rle", False)
133
- if do_rle:
134
- rle_converter = RLEConverter(logger=self.logger)
135
- warning_msg = rle_converter.get_rle_accuracy_warning()
136
- result.add_warning(warning_msg)
137
- self.logger.warning(f"RLE conversion accuracy loss: {warning_msg}")
120
+ # RLE accuracy warning (LabelMe → COCO)
121
+ do_rle = kwargs.get("do_rle", False)
122
+ if do_rle:
123
+ rle_converter = RLEConverter(logger=self.logger)
124
+ warning_msg = rle_converter.get_rle_accuracy_warning()
125
+ result.add_warning(warning_msg)
126
+ self.logger.warning(f"RLE conversion accuracy loss: {warning_msg}")
138
127
 
139
128
  return result
140
129
 
@@ -291,67 +280,107 @@ class CocoAndLabelMeConverter(BaseConverter):
291
280
 
292
281
  return handler
293
282
 
294
- def convert_annotations(
295
- self, source_annotations: DatasetAnnotations, kwargs: Dict
296
- ) -> DatasetAnnotations:
283
+ def _ensure_categories_for_streaming(
284
+ self,
285
+ source_handler: BaseAnnotationHandler,
286
+ source_path: str,
287
+ kwargs: Dict,
288
+ ) -> None:
289
+ """Ensure COCO categories are loaded before streaming.
290
+
291
+ For COCO→LabelMe, reads categories from the COCO JSON file so
292
+ ``create_target_handler()`` can generate ``classes.txt``.
297
293
  """
298
- Convert annotation data between COCO and LabelMe formats.
294
+ import json
295
+
296
+ super()._ensure_categories_for_streaming(
297
+ source_handler, source_path, kwargs
298
+ )
299
299
 
300
- Both formats use absolute pixel coordinates with identical semantics
301
- for bbox (top-left origin) and polygon points. No coordinate
302
- transformation needed — only the format field changes.
300
+ if (
301
+ self.source_format == "coco"
302
+ and (
303
+ not self._source_annotations_for_target
304
+ or not self._source_annotations_for_target.categories
305
+ )
306
+ ):
307
+ try:
308
+ with open(source_path, "r", encoding="utf-8") as f:
309
+ coco_data = json.load(f)
310
+ categories_dict = {}
311
+ for cat in coco_data.get("categories", []):
312
+ cat_id = cat.get("id")
313
+ cat_name = cat.get("name", "")
314
+ if cat_id is not None:
315
+ categories_dict[cat_id] = cat_name
316
+ if categories_dict:
317
+ self._source_annotations_for_target = DatasetAnnotations(
318
+ format=AnnotationFormat.COCO,
319
+ categories=categories_dict,
320
+ )
321
+ except Exception:
322
+ pass
323
+
324
+ def _convert_single_image(
325
+ self, image_ann: ImageAnnotation, **kwargs
326
+ ) -> ImageAnnotation:
327
+ """Convert a single ImageAnnotation between COCO and LabelMe.
303
328
 
304
- Note: RLE segmentation data (COCO-specific) is preserved as-is but
305
- will be skipped by LabelMe reader if encountered.
329
+ Both formats share the same absolute-pixel coordinate semantics.
330
+ Only the structural representation differs coordinate values pass
331
+ through unchanged.
306
332
  """
307
- # Determine target format
308
- if self.target_format == "labelme":
309
- target_format = AnnotationFormat.LABELME
310
- else:
311
- target_format = AnnotationFormat.COCO
333
+ new_objects = []
334
+ for obj in image_ann.objects:
335
+ new_bbox = None
336
+ new_seg = None
337
+
338
+ if obj.bbox:
339
+ new_bbox = BoundingBox(
340
+ x=obj.bbox.x, y=obj.bbox.y,
341
+ width=obj.bbox.width, height=obj.bbox.height,
342
+ )
312
343
 
313
- # COCO and LabelMe share the same coordinate semantics (absolute pixels),
314
- # so annotation data can be passed through directly
344
+ if obj.segmentation:
345
+ new_seg = Segmentation(
346
+ points=obj.segmentation.points.copy(),
347
+ rle=obj.segmentation.rle,
348
+ )
349
+
350
+ new_objects.append(ObjectAnnotation(
351
+ class_id=obj.class_id,
352
+ class_name=obj.class_name,
353
+ bbox=new_bbox,
354
+ segmentation=new_seg,
355
+ confidence=obj.confidence,
356
+ is_crowd=obj.is_crowd,
357
+ ))
358
+
359
+ return ImageAnnotation(
360
+ image_id=image_ann.image_id,
361
+ image_path=image_ann.image_path,
362
+ width=image_ann.width,
363
+ height=image_ann.height,
364
+ objects=new_objects,
365
+ )
366
+
367
+ def convert_annotations(
368
+ self, source_annotations: DatasetAnnotations, kwargs: Dict
369
+ ) -> DatasetAnnotations:
370
+ """Convert annotation data between COCO and LabelMe formats.
371
+
372
+ Both formats use absolute pixel coordinates with identical semantics.
373
+ Delegates to ``_convert_single_image()`` per image.
374
+ """
375
+ target_format = (
376
+ AnnotationFormat.LABELME
377
+ if self.target_format == "labelme"
378
+ else AnnotationFormat.COCO
379
+ )
315
380
  target = DatasetAnnotations(format=target_format)
316
381
  target.categories = source_annotations.categories.copy()
317
382
 
318
383
  for img in source_annotations.images:
319
- new_objects = []
320
- for obj in img.objects:
321
- new_bbox = None
322
- new_seg = None
323
-
324
- if obj.bbox:
325
- # Same semantics: (x, y, w, h) = top-left, absolute pixels
326
- new_bbox = BoundingBox(
327
- x=obj.bbox.x, y=obj.bbox.y,
328
- width=obj.bbox.width, height=obj.bbox.height,
329
- )
330
-
331
- if obj.segmentation:
332
- # Same semantics: absolute pixel points
333
- new_seg = Segmentation(
334
- points=obj.segmentation.points.copy(),
335
- rle=obj.segmentation.rle,
336
- )
337
-
338
- new_obj = ObjectAnnotation(
339
- class_id=obj.class_id,
340
- class_name=obj.class_name,
341
- bbox=new_bbox,
342
- segmentation=new_seg,
343
- confidence=obj.confidence,
344
- is_crowd=obj.is_crowd,
345
- )
346
- new_objects.append(new_obj)
347
-
348
- new_img = ImageAnnotation(
349
- image_id=img.image_id,
350
- image_path=img.image_path,
351
- width=img.width,
352
- height=img.height,
353
- objects=new_objects,
354
- )
355
- target.add_image(new_img)
384
+ target.add_image(self._convert_single_image(img, **kwargs))
356
385
 
357
386
  return target