Skip to content

Commit 944d853

Browse files
zhiltsov-maxnmanovic
authored andcommitted
[Datumaro] COCO 'merge instance polygons' option (#938)
* Add polygon merging option to coco converter * Add test, refactor coco, add support for cli args * Drop colormap application in datumaro format * Add cli support in voc converter * Add cli support in yolo converter * Add converter cli options in project cli * Add image data type conversion in image saving
1 parent 5d6699d commit 944d853

File tree

15 files changed

+323
-146
lines changed

15 files changed

+323
-146
lines changed

datumaro/datumaro/cli/project/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,14 @@ def build_import_parser(parser):
5454
help="Source project format (options: %s)" % (', '.join(importers_list)))
5555
parser.add_argument('-d', '--dest', default='.', dest='dst_dir',
5656
help="Directory to save the new project to (default: current dir)")
57-
parser.add_argument('extra_args', nargs=argparse.REMAINDER,
58-
help="Additional arguments for importer")
5957
parser.add_argument('-n', '--name', default=None,
6058
help="Name of the new project (default: same as project dir)")
6159
parser.add_argument('--overwrite', action='store_true',
6260
help="Overwrite existing files in the save directory")
6361
parser.add_argument('--copy', action='store_true',
6462
help="Make a deep copy instead of saving source links")
63+
# parser.add_argument('extra_args', nargs=argparse.REMAINDER,
64+
# help="Additional arguments for importer (pass '-- -h' for help)")
6565
return parser
6666

6767
def import_command(args):
@@ -111,8 +111,8 @@ def build_export_parser(parser):
111111
help="Output format")
112112
parser.add_argument('-p', '--project', dest='project_dir', default='.',
113113
help="Directory of the project to operate on (default: current dir)")
114-
parser.add_argument('--save-images', action='store_true',
115-
help="Save images")
114+
parser.add_argument('extra_args', nargs=argparse.REMAINDER, default=None,
115+
help="Additional arguments for converter (pass '-- -h' for help)")
116116
return parser
117117

118118
def export_command(args):
@@ -125,7 +125,7 @@ def export_command(args):
125125
save_dir=dst_dir,
126126
output_format=args.output_format,
127127
filter_expr=args.filter,
128-
save_images=args.save_images)
128+
cmdline_args=args.extra_args)
129129
log.info("Project exported to '%s' as '%s'" % \
130130
(dst_dir, args.output_format))
131131

datumaro/datumaro/components/converter.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,16 @@
44
# SPDX-License-Identifier: MIT
55

66
class Converter:
7+
def __init__(self, cmdline_args=None):
8+
pass
9+
710
def __call__(self, extractor, save_dir):
811
raise NotImplementedError()
12+
13+
def _parse_cmdline(self, cmdline):
14+
parser = self.build_cmdline_parser()
15+
16+
if len(cmdline) != 0 and cmdline[0] == '--':
17+
cmdline = cmdline[1:]
18+
args = parser.parse_args(cmdline)
19+
return vars(args)

datumaro/datumaro/components/converters/datumaro.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
)
2020
from datumaro.components.formats.datumaro import DatumaroPath
2121
from datumaro.util.image import save_image
22-
from datumaro.util.mask_tools import apply_colormap
2322

2423

2524
def _cast(value, type_conv, default=None):
@@ -118,13 +117,6 @@ def _save_mask(self, mask):
118117
if mask is None:
119118
return mask_id
120119

121-
if self._converter._apply_colormap:
122-
categories = self._converter._extractor.categories()
123-
categories = categories[AnnotationType.mask]
124-
colormap = categories.colormap
125-
126-
mask = apply_colormap(mask, colormap)
127-
128120
mask_id = self._next_mask_id
129121
self._next_mask_id += 1
130122

@@ -232,12 +224,10 @@ def _convert_points_categories(self, obj):
232224
return converted
233225

234226
class _Converter:
235-
def __init__(self, extractor, save_dir,
236-
save_images=False, apply_colormap=False):
227+
def __init__(self, extractor, save_dir, save_images=False,):
237228
self._extractor = extractor
238229
self._save_dir = save_dir
239230
self._save_images = save_images
240-
self._apply_colormap = apply_colormap
241231

242232
def convert(self):
243233
os.makedirs(self._save_dir, exist_ok=True)
@@ -282,13 +272,27 @@ def _save_image(self, item):
282272
save_image(image_path, image)
283273

284274
class DatumaroConverter(Converter):
285-
def __init__(self, save_images=False, apply_colormap=False):
275+
def __init__(self, save_images=False, cmdline_args=None):
286276
super().__init__()
287-
self._save_images = save_images
288-
self._apply_colormap = apply_colormap
277+
278+
self._options = {
279+
'save_images': save_images,
280+
}
281+
282+
if cmdline_args is not None:
283+
self._options.update(self._parse_cmdline(cmdline_args))
284+
285+
@classmethod
286+
def build_cmdline_parser(cls, parser=None):
287+
import argparse
288+
if not parser:
289+
parser = argparse.ArgumentParser()
290+
291+
parser.add_argument('--save-images', action='store_true',
292+
help="Save images (default: %(default)s)")
293+
294+
return parser
289295

290296
def __call__(self, extractor, save_dir):
291-
converter = _Converter(extractor, save_dir,
292-
apply_colormap=self._apply_colormap,
293-
save_images=self._save_images)
297+
converter = _Converter(extractor, save_dir, **self._options)
294298
converter.convert()

datumaro/datumaro/components/converters/ms_coco.py

Lines changed: 89 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from datumaro.components.extractor import (
1515
DEFAULT_SUBSET_NAME, AnnotationType, PointsObject, BboxObject
1616
)
17-
from datumaro.components.formats.ms_coco import CocoAnnotationType, CocoPath
17+
from datumaro.components.formats.ms_coco import CocoTask, CocoPath
1818
from datumaro.util import find
1919
from datumaro.util.image import save_image
2020
import datumaro.util.mask_tools as mask_tools
@@ -29,8 +29,9 @@ def _cast(value, type_conv, default=None):
2929
return default
3030

3131
class _TaskConverter:
32-
def __init__(self):
32+
def __init__(self, context):
3333
self._min_ann_id = 1
34+
self._context = context
3435

3536
data = {
3637
'licenses': [],
@@ -191,6 +192,13 @@ def save_annotations(self, item):
191192
rle = mask_utils.merge(rles)
192193
area = mask_utils.area(rle)
193194

195+
if self._context._merge_polygons:
196+
binary_mask = mask_utils.decode(rle).astype(np.bool)
197+
binary_mask = np.asfortranarray(binary_mask, dtype=np.uint8)
198+
segmentation = mask_tools.convert_mask_to_rle(binary_mask)
199+
is_crowd = True
200+
bbox = [int(i) for i in mask_utils.toBbox(rle)]
201+
194202
if ann.group is not None:
195203
# Mark the group as visited to prevent repeats
196204
for a in annotations[:]:
@@ -201,6 +209,18 @@ def save_annotations(self, item):
201209
is_crowd = False
202210
segmentation = [ann.get_polygon()]
203211
area = ann.area()
212+
213+
if self._context._merge_polygons:
214+
h, w, _ = item.image.shape
215+
rles = mask_utils.frPyObjects(segmentation, h, w)
216+
rle = mask_utils.merge(rles)
217+
area = mask_utils.area(rle)
218+
binary_mask = mask_utils.decode(rle).astype(np.bool)
219+
binary_mask = np.asfortranarray(binary_mask, dtype=np.uint8)
220+
segmentation = mask_tools.convert_mask_to_rle(binary_mask)
221+
is_crowd = True
222+
bbox = [int(i) for i in mask_utils.toBbox(rle)]
223+
204224
if bbox is None:
205225
bbox = ann.get_bbox()
206226

@@ -340,22 +360,30 @@ def save_annotations(self, item):
340360

341361
class _Converter:
342362
_TASK_CONVERTER = {
343-
CocoAnnotationType.image_info: _ImageInfoConverter,
344-
CocoAnnotationType.instances: _InstancesConverter,
345-
CocoAnnotationType.person_keypoints: _KeypointsConverter,
346-
CocoAnnotationType.captions: _CaptionsConverter,
347-
CocoAnnotationType.labels: _LabelsConverter,
363+
CocoTask.image_info: _ImageInfoConverter,
364+
CocoTask.instances: _InstancesConverter,
365+
CocoTask.person_keypoints: _KeypointsConverter,
366+
CocoTask.captions: _CaptionsConverter,
367+
CocoTask.labels: _LabelsConverter,
348368
}
349369

350-
def __init__(self, extractor, save_dir, save_images=False, task=None):
351-
if not task:
352-
task = list(self._TASK_CONVERTER.keys())
353-
elif task in CocoAnnotationType:
354-
task = [task]
355-
self._task = task
370+
def __init__(self, extractor, save_dir,
371+
tasks=None, save_images=False, merge_polygons=False):
372+
assert tasks is None or isinstance(tasks, (CocoTask, list))
373+
if tasks is None:
374+
tasks = list(self._TASK_CONVERTER)
375+
elif isinstance(tasks, CocoTask):
376+
tasks = [tasks]
377+
else:
378+
for t in tasks:
379+
assert t in CocoTask
380+
self._tasks = tasks
381+
356382
self._extractor = extractor
357383
self._save_dir = save_dir
384+
358385
self._save_images = save_images
386+
self._merge_polygons = merge_polygons
359387

360388
def make_dirs(self):
361389
self._images_dir = osp.join(self._save_dir, CocoPath.IMAGES_DIR)
@@ -365,11 +393,13 @@ def make_dirs(self):
365393
os.makedirs(self._ann_dir, exist_ok=True)
366394

367395
def make_task_converter(self, task):
368-
return self._TASK_CONVERTER[task]()
396+
if task not in self._TASK_CONVERTER:
397+
raise NotImplementedError()
398+
return self._TASK_CONVERTER[task](self)
369399

370400
def make_task_converters(self):
371401
return {
372-
task: self.make_task_converter(task) for task in self._task
402+
task: self.make_task_converter(task) for task in self._tasks
373403
}
374404

375405
def save_image(self, item, filename):
@@ -411,32 +441,56 @@ def convert(self):
411441
'%s_%s.json' % (task.name, subset_name)))
412442

413443
class CocoConverter(Converter):
414-
def __init__(self, task=None, save_images=False):
444+
def __init__(self,
445+
tasks=None, save_images=False, merge_polygons=False,
446+
cmdline_args=None):
415447
super().__init__()
416-
self._task = task
417-
self._save_images = save_images
448+
449+
self._options = {
450+
'tasks': tasks,
451+
'save_images': save_images,
452+
'merge_polygons': merge_polygons,
453+
}
454+
455+
if cmdline_args is not None:
456+
self._options.update(self._parse_cmdline(cmdline_args))
457+
458+
@staticmethod
459+
def _split_tasks_string(s):
460+
return [CocoTask[i.strip()] for i in s.split(',')]
461+
462+
@classmethod
463+
def build_cmdline_parser(cls, parser=None):
464+
import argparse
465+
if not parser:
466+
parser = argparse.ArgumentParser()
467+
468+
parser.add_argument('--save-images', action='store_true',
469+
help="Save images (default: %(default)s)")
470+
parser.add_argument('--merge-polygons', action='store_true',
471+
help="Merge instance polygons into a mask (default: %(default)s)")
472+
parser.add_argument('--tasks', type=cls._split_tasks_string,
473+
default=None,
474+
help="COCO task filter, comma-separated list of {%s} "
475+
"(default: all)" % ', '.join([t.name for t in CocoTask]))
476+
477+
return parser
418478

419479
def __call__(self, extractor, save_dir):
420-
converter = _Converter(extractor, save_dir,
421-
save_images=self._save_images, task=self._task)
480+
converter = _Converter(extractor, save_dir, **self._options)
422481
converter.convert()
423482

424-
def CocoInstancesConverter(save_images=False):
425-
return CocoConverter(CocoAnnotationType.instances,
426-
save_images=save_images)
483+
def CocoInstancesConverter(**kwargs):
484+
return CocoConverter(CocoTask.instances, **kwargs)
427485

428-
def CocoImageInfoConverter(save_images=False):
429-
return CocoConverter(CocoAnnotationType.image_info,
430-
save_images=save_images)
486+
def CocoImageInfoConverter(**kwargs):
487+
return CocoConverter(CocoTask.image_info, **kwargs)
431488

432-
def CocoPersonKeypointsConverter(save_images=False):
433-
return CocoConverter(CocoAnnotationType.person_keypoints,
434-
save_images=save_images)
489+
def CocoPersonKeypointsConverter(**kwargs):
490+
return CocoConverter(CocoTask.person_keypoints, **kwargs)
435491

436-
def CocoCaptionsConverter(save_images=False):
437-
return CocoConverter(CocoAnnotationType.captions,
438-
save_images=save_images)
492+
def CocoCaptionsConverter(**kwargs):
493+
return CocoConverter(CocoTask.captions, **kwargs)
439494

440-
def CocoLabelsConverter(save_images=False):
441-
return CocoConverter(CocoAnnotationType.labels,
442-
save_images=save_images)
495+
def CocoLabelsConverter(**kwargs):
496+
return CocoConverter(CocoTask.labels, **kwargs)

0 commit comments

Comments
 (0)