Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Aug 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>)

Expand Down
61 changes: 23 additions & 38 deletions datumaro/datumaro/plugins/voc_format/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -102,6 +102,8 @@ def __init__(self, extractor, save_dir,
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):
Expand Down Expand Up @@ -446,7 +448,7 @@ def save_label_map(self):
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()
Expand All @@ -456,71 +458,54 @@ def _load_categories(self, label_map_source=None):
# 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():
# use source colormap
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):
Expand Down
15 changes: 8 additions & 7 deletions datumaro/datumaro/plugins/voc_format/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, \
Expand Down Expand Up @@ -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()
Expand All @@ -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
76 changes: 17 additions & 59 deletions datumaro/tests/test_voc_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,53 +472,6 @@ def categories(self):
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):
Expand Down Expand Up @@ -602,8 +555,8 @@ def __iter__(self):

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)

Expand All @@ -616,11 +569,11 @@ def test_dataset_with_fixed_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, 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):
Expand All @@ -633,14 +586,19 @@ def categories(self):
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,
Expand All @@ -649,12 +607,12 @@ def __iter__(self):
'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(),
Expand Down