[Datumaro] Mandatory sorting for black color in voc masks (#2048)

* Remove `guess`, add mandatory sorting and black label

* update changelog

Co-authored-by: Nikita Manovich <nikita.manovich@intel.com>
main
Maxim Zhiltsov 6 years ago committed by GitHub
parent 2510d4d659
commit 55073fb16e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Issue loading openvino models for semi-automatic and automatic annotation (<https://github.com/opencv/cvat/pull/1996>)
- Basic functions of CVAT works without activated nuclio dashboard
- Fixed a case in which exported masks could have wrong color order (<https://github.com/opencv/cvat/issues/2032>)
- Fixed error with creating task with labels with the same name (<https://github.com/opencv/cvat/pull/2031>)
- Django RQ dashboard view (<https://github.com/opencv/cvat/pull/2069>)

@ -47,7 +47,7 @@ def _write_xml_bbox(bbox, parent_elem):
return bbox_elem
LabelmapType = Enum('LabelmapType', ['voc', 'source', 'guess'])
LabelmapType = Enum('LabelmapType', ['voc', 'source'])
class VocConverter(Converter):
DEFAULT_IMAGE_EXT = VocPath.IMAGE_EXT
@ -102,6 +102,8 @@ class VocConverter(Converter):
self._apply_colormap = apply_colormap
self._allow_attributes = allow_attributes
if label_map is None:
label_map = LabelmapType.source
self._load_categories(label_map)
def apply(self):
@ -446,7 +448,7 @@ class VocConverter(Converter):
path = osp.join(self._save_dir, VocPath.LABELMAP_FILE)
write_label_map(path, self._label_map)
def _load_categories(self, label_map_source=None):
def _load_categories(self, label_map_source):
if label_map_source == LabelmapType.voc.name:
# use the default VOC colormap
label_map = make_voc_label_map()
@ -456,10 +458,8 @@ class VocConverter(Converter):
# generate colormap for input labels
labels = self._extractor.categories() \
.get(AnnotationType.label, LabelCategories())
label_map = OrderedDict()
label_map['background'] = [None, [], []]
for item in labels.items:
label_map[item.name] = [None, [], []]
label_map = OrderedDict((item.name, [None, [], []])
for item in labels.items)
elif label_map_source == LabelmapType.source.name and \
AnnotationType.mask in self._extractor.categories():
@ -467,60 +467,45 @@ class VocConverter(Converter):
labels = self._extractor.categories()[AnnotationType.label]
colors = self._extractor.categories()[AnnotationType.mask]
label_map = OrderedDict()
has_black = False
for idx, item in enumerate(labels.items):
color = colors.colormap.get(idx)
if idx is not None:
if color == (0, 0, 0):
has_black = True
if color is not None:
label_map[item.name] = [color, [], []]
if not has_black and 'background' not in label_map:
label_map['background'] = [(0, 0, 0), [], []]
label_map.move_to_end('background', last=False)
elif label_map_source in [LabelmapType.guess.name, None]:
# generate colormap for union of VOC and input dataset labels
label_map = make_voc_label_map()
rebuild_colormap = False
source_labels = self._extractor.categories() \
.get(AnnotationType.label, LabelCategories())
for label in source_labels.items:
if label.name not in label_map:
rebuild_colormap = True
if label.attributes or label.name not in label_map:
label_map[label.name] = [None, [], label.attributes]
if rebuild_colormap:
for item in label_map.values():
item[0] = None
elif isinstance(label_map_source, dict):
label_map = label_map_source
label_map = OrderedDict(
sorted(label_map_source.items(), key=lambda e: e[0]))
elif isinstance(label_map_source, str) and osp.isfile(label_map_source):
label_map = parse_label_map(label_map_source)
has_black = find(label_map.items(),
lambda e: e[0] == 'background' or e[1][0] == (0, 0, 0))
if not has_black and 'background' not in label_map:
label_map['background'] = [(0, 0, 0), [], []]
label_map.move_to_end('background', last=False)
else:
raise Exception("Wrong labelmap specified, "
"expected one of %s or a file path" % \
', '.join(t.name for t in LabelmapType))
# There must always be a label with color (0, 0, 0) at index 0
bg_label = find(label_map.items(), lambda x: x[1][0] == (0, 0, 0))
if bg_label is not None:
bg_label = bg_label[0]
else:
bg_label = 'background'
if bg_label not in label_map:
has_colors = any(v[0] is not None for v in label_map.values())
color = (0, 0, 0) if has_colors else None
label_map[bg_label] = [color, [], []]
label_map.move_to_end(bg_label, last=False)
self._categories = make_voc_categories(label_map)
self._label_map = label_map
# Update colors with assigned values
colormap = self._categories[AnnotationType.mask].colormap
for label_id, color in colormap.items():
label_desc = label_map[
self._categories[AnnotationType.label].items[label_id].name]
label_desc[0] = color
self._label_map = label_map
self._label_id_mapping = self._make_label_id_map()
def _is_label(self, s):

@ -137,6 +137,9 @@ def parse_label_map(path):
label_desc = line.strip().split(':')
name = label_desc[0]
if name in label_map:
raise ValueError("Label '%s' is already defined" % name)
if 1 < len(label_desc) and len(label_desc[1]) != 0:
color = label_desc[1].split(',')
assert len(color) == 3, \
@ -173,7 +176,6 @@ def write_label_map(path, label_map):
f.write('%s\n' % ':'.join([label_name, color_rgb, parts, actions]))
# pylint: disable=pointless-statement
def make_voc_categories(label_map=None):
if label_map is None:
label_map = make_voc_label_map()
@ -190,16 +192,15 @@ def make_voc_categories(label_map=None):
label_categories.add(part)
categories[AnnotationType.label] = label_categories
has_colors = sum(v[0] is not None for v in label_map.values())
if not has_colors:
has_colors = any(v[0] is not None for v in label_map.values())
if not has_colors: # generate new colors
colormap = generate_colormap(len(label_map))
else:
else: # only copy defined colors
label_id = lambda label: label_categories.find(label)[0]
colormap = { label_id(name): desc[0]
for name, desc in label_map.items() }
for name, desc in label_map.items() if desc[0] is not None }
mask_categories = MaskCategories(colormap)
mask_categories.inverse_colormap # force init
mask_categories.inverse_colormap # pylint: disable=pointless-statement
categories[AnnotationType.mask] = mask_categories
return categories
# pylint: enable=pointless-statement

@ -472,53 +472,6 @@ class VocConverterTest(TestCase):
partial(VocConverter.convert, label_map='voc'),
test_dir, target_dataset=DstExtractor())
def test_dataset_with_guessed_labelmap(self):
class SrcExtractor(TestExtractorBase):
def __iter__(self):
yield DatasetItem(id=1, annotations=[
Bbox(2, 3, 4, 5, label=0, id=1),
Bbox(1, 2, 3, 4, label=1, id=2),
])
def categories(self):
label_cat = LabelCategories()
label_cat.add(VOC.VocLabel(1).name)
label_cat.add('non_voc_label')
return {
AnnotationType.label: label_cat,
}
class DstExtractor(TestExtractorBase):
def __iter__(self):
yield DatasetItem(id=1, annotations=[
Bbox(2, 3, 4, 5, label=self._label(VOC.VocLabel(1).name),
id=1, group=1, attributes={
'truncated': False,
'difficult': False,
'occluded': False,
}
),
Bbox(1, 2, 3, 4, label=self._label('non_voc_label'),
id=2, group=2, attributes={
'truncated': False,
'difficult': False,
'occluded': False,
}
),
])
def categories(self):
label_map = VOC.make_voc_label_map()
label_map['non_voc_label'] = [None, [], []]
for label_desc in label_map.values():
label_desc[0] = None # rebuild colormap
return VOC.make_voc_categories(label_map)
with TestDir() as test_dir:
self._test_save_and_load(SrcExtractor(),
partial(VocConverter.convert, label_map='guess'),
test_dir, target_dataset=DstExtractor())
def test_dataset_with_source_labelmap_undefined(self):
class SrcExtractor(TestExtractorBase):
def __iter__(self):
@ -602,8 +555,8 @@ class VocConverterTest(TestCase):
def categories(self):
label_map = OrderedDict()
label_map['label_1'] = [(1, 2, 3), [], []]
label_map['background'] = [(0, 0, 0), [], []]
label_map['label_1'] = [(1, 2, 3), [], []]
label_map['label_2'] = [(3, 2, 1), [], []]
return VOC.make_voc_categories(label_map)
@ -616,11 +569,11 @@ class VocConverterTest(TestCase):
class SrcExtractor(TestExtractorBase):
def __iter__(self):
yield DatasetItem(id=1, annotations=[
Bbox(2, 3, 4, 5, label=0, id=1),
Bbox(1, 2, 3, 4, label=1, id=2, group=2,
Bbox(2, 3, 4, 5, label=self._label('foreign_label'), id=1),
Bbox(1, 2, 3, 4, label=self._label('label'), id=2, group=2,
attributes={'act1': True}),
Bbox(2, 3, 4, 5, label=2, id=3, group=2),
Bbox(2, 3, 4, 6, label=3, id=4, group=2),
Bbox(2, 3, 4, 5, label=self._label('label_part1'), group=2),
Bbox(2, 3, 4, 6, label=self._label('label_part2'), group=2),
])
def categories(self):
@ -633,14 +586,19 @@ class VocConverterTest(TestCase):
AnnotationType.label: label_cat,
}
label_map = {
'label': [None, ['label_part1', 'label_part2'], ['act1', 'act2']]
}
label_map = OrderedDict([
('label', [None, ['label_part1', 'label_part2'], ['act1', 'act2']])
])
dst_label_map = OrderedDict([
('background', [None, [], []]),
('label', [None, ['label_part1', 'label_part2'], ['act1', 'act2']])
])
class DstExtractor(TestExtractorBase):
def __iter__(self):
yield DatasetItem(id=1, annotations=[
Bbox(1, 2, 3, 4, label=0, id=1, group=1,
Bbox(1, 2, 3, 4, label=self._label('label'), id=1, group=1,
attributes={
'act1': True,
'act2': False,
@ -649,12 +607,12 @@ class VocConverterTest(TestCase):
'occluded': False,
}
),
Bbox(2, 3, 4, 5, label=1, group=1),
Bbox(2, 3, 4, 6, label=2, group=1),
Bbox(2, 3, 4, 5, label=self._label('label_part1'), group=1),
Bbox(2, 3, 4, 6, label=self._label('label_part2'), group=1),
])
def categories(self):
return VOC.make_voc_categories(label_map)
return VOC.make_voc_categories(dst_label_map)
with TestDir() as test_dir:
self._test_save_and_load(SrcExtractor(),

Loading…
Cancel
Save