Skip to content

Commit

Permalink
Make remap labels more accurate, allow explicit label deletion, add d…
Browse files Browse the repository at this point in the history
…ocs, update tests (#203)
  • Loading branch information
Maxim Zhiltsov authored Apr 5, 2021
1 parent 4e2e24a commit b14dfa0
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 29 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [Unreleased]
### Added
-

### Changed
-

### Deprecated
-

### Removed
-

### Fixed
- Allowed explicit label removal in `remap_labels` transform (<https://github.com/openvinotoolkit/datumaro/pull/203>)

### Security
-

## 31/03/2021 - Release v0.1.8
### Added
-
Expand Down
37 changes: 25 additions & 12 deletions datumaro/plugins/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
)
from datumaro.components.cli_plugin import CliPlugin
import datumaro.util.mask_tools as mask_tools
from datumaro.util import parse_str_enum_value, NOTSET
from datumaro.util.annotation_util import find_group_leader, find_instances


Expand Down Expand Up @@ -433,7 +434,22 @@ def transform_item(self, item):
class RemapLabels(Transform, CliPlugin):
"""
Changes labels in the dataset.|n
|n
A label can be:|n
- renamed (and joined with existing) -|n
|s|swhen specified '--label <old_name>:<new_name>'|n
- deleted - when specified '--label <name>:' or default action is 'delete'|n
|s|sand the label is not mentioned in the list. When a label|n
|s|sis deleted, all the associated annotations are removed|n
- kept unchanged - when specified '--label <name>:<name>'|n
|s|sor default action is 'keep' and the label is not mentioned in the list|n
Annotations with no label are managed by the default action policy.|n
|n
Examples:|n
- Remove the 'person' label (and corresponding annotations):|n
|s|sremap_labels -l person: --default keep|n
- Rename 'person' to 'pedestrian' and 'human' to 'pedestrian', join:|n
|s|sremap_labels -l person:pedestrian -l human:pedestrian --default keep|n
- Rename 'person' to 'car' and 'cat' to 'dog', keep 'bus', remove others:|n
|s|sremap_labels -l person:car -l bus:bus -l cat:dog --default delete
"""
Expand Down Expand Up @@ -463,9 +479,9 @@ def build_cmdline_parser(cls, **kwargs):
def __init__(self, extractor, mapping, default=None):
super().__init__(extractor)

assert isinstance(default, (str, self.DefaultAction))
if isinstance(default, str):
default = self.DefaultAction[default]
default = parse_str_enum_value(default, self.DefaultAction,
self.DefaultAction.keep)
self._default_action = default

assert isinstance(mapping, (dict, list))
if isinstance(mapping, list):
Expand Down Expand Up @@ -503,10 +519,10 @@ def _make_label_id_map(self, src_label_cat, label_mapping, default_action):
dst_label_cat = LabelCategories(attributes=src_label_cat.attributes)
id_mapping = {}
for src_index, src_label in enumerate(src_label_cat.items):
dst_label = label_mapping.get(src_label.name)
if not dst_label and default_action == self.DefaultAction.keep:
dst_label = label_mapping.get(src_label.name, NOTSET)
if dst_label is NOTSET and default_action == self.DefaultAction.keep:
dst_label = src_label.name # keep unspecified as is
if not dst_label:
elif not dst_label or dst_label is NOTSET:
continue

dst_index = dst_label_cat.find(dst_label)[0]
Expand All @@ -518,7 +534,7 @@ def _make_label_id_map(self, src_label_cat, label_mapping, default_action):
if log.getLogger().isEnabledFor(log.DEBUG):
log.debug("Label mapping:")
for src_id, src_label in enumerate(src_label_cat.items):
if id_mapping.get(src_id):
if id_mapping.get(src_id) is not None:
log.debug("#%s '%s' -> #%s '%s'",
src_id, src_label.name, id_mapping[src_id],
dst_label_cat.items[id_mapping[src_id]].name
Expand All @@ -535,14 +551,11 @@ def categories(self):
def transform_item(self, item):
annotations = []
for ann in item.annotations:
if ann.type in { AnnotationType.label, AnnotationType.mask,
AnnotationType.points, AnnotationType.polygon,
AnnotationType.polyline, AnnotationType.bbox
} and ann.label is not None:
if getattr(ann, 'label') is not None:
conv_label = self._map_id(ann.label)
if conv_label is not None:
annotations.append(ann.wrap(label=conv_label))
else:
elif self._default_action is self.DefaultAction.keep:
annotations.append(ann.wrap())
return item.wrap(annotations=annotations)

Expand Down
45 changes: 28 additions & 17 deletions tests/test_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,15 +336,18 @@ def test_remap_labels(self):
Bbox(1, 2, 3, 4, label=2),
Mask(image=np.array([1]), label=3),

# Should be kept
# Should be deleted
Polygon([1, 1, 2, 2, 3, 4], label=4),
PolyLine([1, 3, 4, 2, 5, 6])

# Should be kept
PolyLine([1, 3, 4, 2, 5, 6]),
Bbox(4, 3, 2, 1, label=5),
])
], categories={
AnnotationType.label: LabelCategories.from_iterable(
'label%s' % i for i in range(5)),
'label%s' % i for i in range(6)),
AnnotationType.mask: MaskCategories(
colormap=mask_tools.generate_colormap(5)),
colormap=mask_tools.generate_colormap(6)),
})

dst_dataset = Dataset.from_iterable([
Expand All @@ -353,37 +356,45 @@ def test_remap_labels(self):
Bbox(1, 2, 3, 4, label=0),
Mask(image=np.array([1]), label=1),

Polygon([1, 1, 2, 2, 3, 4], label=2),
PolyLine([1, 3, 4, 2, 5, 6], label=None)
PolyLine([1, 3, 4, 2, 5, 6], label=None),
Bbox(4, 3, 2, 1, label=2),
]),
], categories={
AnnotationType.label: LabelCategories.from_iterable(
['label0', 'label9', 'label4']),
['label0', 'label9', 'label5']),
AnnotationType.mask: MaskCategories(colormap={
k: v for k, v in mask_tools.generate_colormap(5).items()
if k in { 0, 1, 3, 4 }
k: v for k, v in mask_tools.generate_colormap(6).items()
if k in { 0, 1, 3, 5 }
})
})

actual = transforms.RemapLabels(src_dataset, mapping={
'label1': 'label9',
'label2': 'label0',
'label3': 'label9',
'label1': 'label9', # rename & join with new label9 (from label3)
'label2': 'label0', # rename & join with existing label0
'label3': 'label9', # rename & join with new label9 (form label1)
'label4': '', # delete the label and associated annotations
# 'label5' - unchanged
}, default='keep')

compare_datasets(self, dst_dataset, actual)

def test_remap_labels_delete_unspecified(self):
source_dataset = Dataset.from_iterable([
DatasetItem(id=1, annotations=[ Label(0) ])
], categories=['label0'])
DatasetItem(id=1, annotations=[
Label(0, id=0), # will be removed
Label(1, id=1),
Bbox(1, 2, 3, 4, label=None),
])
], categories=['label0', 'label1'])

target_dataset = Dataset.from_iterable([
DatasetItem(id=1),
], categories=[])
DatasetItem(id=1, annotations=[
Label(0, id=1),
]),
], categories=['label1'])

actual = transforms.RemapLabels(source_dataset,
mapping={}, default='delete')
mapping={ 'label1': 'label1' }, default='delete')

compare_datasets(self, target_dataset, actual)

Expand Down

0 comments on commit b14dfa0

Please sign in to comment.