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.
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/PKG-INFO +2 -2
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/README.md +1 -1
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/__init__.py +1 -1
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/base.py +147 -3
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/coco_and_labelme.py +117 -88
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/labelme_and_yolo.py +112 -199
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/rle_converter.py +1 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/yolo_and_coco.py +177 -152
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/base.py +32 -2
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/coco_handler.py +82 -1
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/labelme_handler.py +84 -3
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/yolo_handler.py +114 -8
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/visualize/base.py +109 -39
- dataflow_cv-1.2.0/dataflow/visualize/coco_visualizer.py +94 -0
- dataflow_cv-1.2.0/dataflow/visualize/labelme_visualizer.py +91 -0
- dataflow_cv-1.2.0/dataflow/visualize/yolo_visualizer.py +100 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/PKG-INFO +2 -2
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/pyproject.toml +1 -1
- dataflow_cv-1.1.0/dataflow/visualize/coco_visualizer.py +0 -96
- dataflow_cv-1.1.0/dataflow/visualize/labelme_visualizer.py +0 -92
- dataflow_cv-1.1.0/dataflow/visualize/yolo_visualizer.py +0 -102
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/LICENSE +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/__init__.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/__init__.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/convert.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/evaluate.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/utils.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/commands/visualize.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/exceptions.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/cli/main.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/__init__.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/convert/utils.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/__init__.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/base.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/evaluator.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/metrics.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/result.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/evaluate/utils.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/__init__.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/models.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/label/utils.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/util/__init__.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/util/file_util.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/util/logging_util.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/visualize/__init__.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow/visualize/utils.py +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/SOURCES.txt +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/dependency_links.txt +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/entry_points.txt +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/not-zip-safe +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/requires.txt +0 -0
- {dataflow_cv-1.1.0 → dataflow_cv-1.2.0}/dataflow_cv.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
-
**
|
|
315
|
+
**370 tests, 75% code coverage.**
|
|
316
316
|
|
|
317
317
|
```bash
|
|
318
318
|
pytest # All tests
|
|
@@ -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
|
-
|
|
61
|
-
|
|
51
|
+
Auto-selects pipeline:
|
|
52
|
+
- COCO→LabelMe: streaming (per-file .json output)
|
|
53
|
+
- LabelMe→COCO: batch (single JSON output)
|
|
62
54
|
"""
|
|
63
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
295
|
-
self,
|
|
296
|
-
|
|
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
|
-
|
|
294
|
+
import json
|
|
295
|
+
|
|
296
|
+
super()._ensure_categories_for_streaming(
|
|
297
|
+
source_handler, source_path, kwargs
|
|
298
|
+
)
|
|
299
299
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
305
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
-
|
|
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
|