diff --git a/CHANGELOG.md b/CHANGELOG.md index 6336a8943b21..51b0a519f15c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Synchronization with remote git repo () - A problem with mask to polygons conversion when polygons are too small () - Unable to upload video with uneven size () +- Fixed an issue with `z_order` having no effect on segmentations () ### Security - diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index d8152086b496..55f98accbb90 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -498,7 +498,6 @@ def convert_attrs(label, cvat_attrs): anno_label = map_label(shape_obj.label) anno_attr = convert_attrs(shape_obj.label, shape_obj.attributes) anno_attr['occluded'] = shape_obj.occluded - anno_attr['z_order'] = shape_obj.z_order if hasattr(shape_obj, 'track_id'): anno_attr['track_id'] = shape_obj.track_id @@ -507,17 +506,21 @@ def convert_attrs(label, cvat_attrs): anno_points = shape_obj.points if shape_obj.type == ShapeType.POINTS: anno = datumaro.Points(anno_points, - label=anno_label, attributes=anno_attr, group=anno_group) + label=anno_label, attributes=anno_attr, group=anno_group, + z_order=shape_obj.z_order) elif shape_obj.type == ShapeType.POLYLINE: anno = datumaro.PolyLine(anno_points, - label=anno_label, attributes=anno_attr, group=anno_group) + label=anno_label, attributes=anno_attr, group=anno_group, + z_order=shape_obj.z_order) elif shape_obj.type == ShapeType.POLYGON: anno = datumaro.Polygon(anno_points, - label=anno_label, attributes=anno_attr, group=anno_group) + label=anno_label, attributes=anno_attr, group=anno_group, + z_order=shape_obj.z_order) elif shape_obj.type == ShapeType.RECTANGLE: x0, y0, x1, y1 = anno_points anno = datumaro.Bbox(x0, y0, x1 - x0, y1 - y0, - label=anno_label, attributes=anno_attr, group=anno_group) + label=anno_label, attributes=anno_attr, group=anno_group, + z_order=shape_obj.z_order) elif shape_obj.type == ShapeType.CUBOID: continue # Datumaro does not support cuboids else: @@ -590,6 +593,7 @@ def import_dm_annotations(dm_dataset, task_data): label=label_cat.items[ann.label].name, points=ann.points, occluded=ann.attributes.get('occluded') == True, + z_order=ann.z_order, group=group_map.get(ann.group, 0), attributes=[task_data.Attribute(name=n, value=str(v)) for n, v in ann.attributes.items()], diff --git a/datumaro/datumaro/components/extractor.py b/datumaro/datumaro/components/extractor.py index fc35b05c28db..1f18af0f7448 100644 --- a/datumaro/datumaro/components/extractor.py +++ b/datumaro/datumaro/components/extractor.py @@ -270,21 +270,22 @@ def from_instance_masks(instance_masks, if instance_ids is not None: assert len(instance_ids) == len(instance_masks) else: - instance_ids = range(1, len(instance_masks) + 1) + instance_ids = [None] * len(instance_masks) if instance_labels is not None: assert len(instance_labels) == len(instance_masks) else: instance_labels = [None] * len(instance_masks) - instance_masks = sorted(instance_masks, key=lambda m: m.z_order) + instance_masks = sorted( + zip(instance_masks, instance_ids, instance_labels), + key=lambda m: m[0].z_order) - instance_mask = [m.as_instance_mask(id) for m, id in - zip(instance_masks, instance_ids)] + instance_mask = [m.as_instance_mask(id if id is not None else 1 + idx) + for idx, (m, id, _) in enumerate(instance_masks)] instance_mask = merge_masks(instance_mask) - cls_mask = [m.as_class_mask(c) for m, c in - zip(instance_masks, instance_labels)] + cls_mask = [m.as_class_mask(c) for m, _, c in instance_masks] cls_mask = merge_masks(cls_mask) return __class__(class_mask=cls_mask, instance_mask=instance_mask) diff --git a/datumaro/datumaro/plugins/cvat_format/converter.py b/datumaro/datumaro/plugins/cvat_format/converter.py index b8ef22695748..c04eef49a2b6 100644 --- a/datumaro/datumaro/plugins/cvat_format/converter.py +++ b/datumaro/datumaro/plugins/cvat_format/converter.py @@ -271,7 +271,7 @@ def _write_shape(self, shape): )), ])) - shape_data['z_order'] = str(int(shape.attributes.get('z_order', 0))) + shape_data['z_order'] = str(int(shape.z_order)) if shape.group: shape_data['group_id'] = str(shape.group) diff --git a/datumaro/datumaro/plugins/cvat_format/extractor.py b/datumaro/datumaro/plugins/cvat_format/extractor.py index 0a181d7478b1..c268b31dcacb 100644 --- a/datumaro/datumaro/plugins/cvat_format/extractor.py +++ b/datumaro/datumaro/plugins/cvat_format/extractor.py @@ -238,8 +238,6 @@ def consumed(expected_state, tag): "Expected 'meta' section in the annotation file, path: %s" % states common_attrs = ['occluded'] - if has_z_order: - common_attrs.append('z_order') if mode == 'interpolation': common_attrs.append('keyframe') common_attrs.append('outside') @@ -260,8 +258,6 @@ def _parse_shape_ann(cls, ann, categories): attributes = ann.get('attributes', {}) if 'occluded' in categories[AnnotationType.label].attributes: attributes['occluded'] = ann.get('occluded', False) - if 'z_order' in categories[AnnotationType.label].attributes: - attributes['z_order'] = ann.get('z_order', 0) if 'outside' in categories[AnnotationType.label].attributes: attributes['outside'] = ann.get('outside', False) if 'keyframe' in categories[AnnotationType.label].attributes: @@ -272,24 +268,25 @@ def _parse_shape_ann(cls, ann, categories): label = ann.get('label') label_id = categories[AnnotationType.label].find(label)[0] + z_order = ann.get('z_order', 0) points = ann.get('points', []) if ann_type == 'polyline': - return PolyLine(points, label=label_id, + return PolyLine(points, label=label_id, z_order=z_order, id=ann_id, attributes=attributes, group=group) elif ann_type == 'polygon': - return Polygon(points, label=label_id, + return Polygon(points, label=label_id, z_order=z_order, id=ann_id, attributes=attributes, group=group) elif ann_type == 'points': - return Points(points, label=label_id, + return Points(points, label=label_id, z_order=z_order, id=ann_id, attributes=attributes, group=group) elif ann_type == 'box': x, y = points[0], points[1] w, h = points[2] - x, points[3] - y - return Bbox(x, y, w, h, label=label_id, + return Bbox(x, y, w, h, label=label_id, z_order=z_order, id=ann_id, attributes=attributes, group=group) else: diff --git a/datumaro/datumaro/plugins/datumaro_format/converter.py b/datumaro/datumaro/plugins/datumaro_format/converter.py index abf532a4225b..1976bb1e7f56 100644 --- a/datumaro/datumaro/plugins/datumaro_format/converter.py +++ b/datumaro/datumaro/plugins/datumaro_format/converter.py @@ -12,7 +12,7 @@ from datumaro.components.converter import Converter from datumaro.components.extractor import ( - DEFAULT_SUBSET_NAME, Annotation, + DEFAULT_SUBSET_NAME, Annotation, _Shape, Label, Mask, RleMask, Points, Polygon, PolyLine, Bbox, Caption, LabelCategories, MaskCategories, PointsCategories ) @@ -131,43 +131,38 @@ def _convert_mask_object(self, obj): # serialize as compressed COCO mask 'counts': rle['counts'].decode('ascii'), 'size': list(int(c) for c in rle['size']), - } + }, + 'z_order': obj.z_order, }) return converted - def _convert_polyline_object(self, obj): + def _convert_shape_object(self, obj): + assert isinstance(obj, _Shape) converted = self._convert_annotation(obj) converted.update({ 'label_id': cast(obj.label, int), 'points': [float(p) for p in obj.points], + 'z_order': obj.z_order, }) return converted - def _convert_polygon_object(self, obj): - converted = self._convert_annotation(obj) + def _convert_polyline_object(self, obj): + return self._convert_shape_object(obj) - converted.update({ - 'label_id': cast(obj.label, int), - 'points': [float(p) for p in obj.points], - }) - return converted + def _convert_polygon_object(self, obj): + return self._convert_shape_object(obj) def _convert_bbox_object(self, obj): - converted = self._convert_annotation(obj) - - converted.update({ - 'label_id': cast(obj.label, int), - 'bbox': [float(p) for p in obj.get_bbox()], - }) + converted = self._convert_shape_object(obj) + converted.pop('points', None) + converted['bbox'] = [float(p) for p in obj.get_bbox()] return converted def _convert_points_object(self, obj): - converted = self._convert_annotation(obj) + converted = self._convert_shape_object(obj) converted.update({ - 'label_id': cast(obj.label, int), - 'points': [float(p) for p in obj.points], 'visibility': [int(v.value) for v in obj.visibility], }) return converted diff --git a/datumaro/datumaro/plugins/datumaro_format/extractor.py b/datumaro/datumaro/plugins/datumaro_format/extractor.py index 954e280716a1..7be72b301edb 100644 --- a/datumaro/datumaro/plugins/datumaro_format/extractor.py +++ b/datumaro/datumaro/plugins/datumaro_format/extractor.py @@ -107,41 +107,41 @@ def _load_annotations(self, item): attributes = ann.get('attributes') group = ann.get('group') + label_id = ann.get('label_id') + z_order = ann.get('z_order') + points = ann.get('points') + if ann_type == AnnotationType.label: - label_id = ann.get('label_id') loaded.append(Label(label=label_id, id=ann_id, attributes=attributes, group=group)) elif ann_type == AnnotationType.mask: - label_id = ann.get('label_id') rle = ann['rle'] rle['counts'] = rle['counts'].encode('ascii') loaded.append(RleMask(rle=rle, label=label_id, - id=ann_id, attributes=attributes, group=group)) + id=ann_id, attributes=attributes, group=group, + z_order=z_order)) elif ann_type == AnnotationType.polyline: - label_id = ann.get('label_id') - points = ann.get('points') loaded.append(PolyLine(points, label=label_id, - id=ann_id, attributes=attributes, group=group)) + id=ann_id, attributes=attributes, group=group, + z_order=z_order)) elif ann_type == AnnotationType.polygon: - label_id = ann.get('label_id') - points = ann.get('points') loaded.append(Polygon(points, label=label_id, - id=ann_id, attributes=attributes, group=group)) + id=ann_id, attributes=attributes, group=group, + z_order=z_order)) elif ann_type == AnnotationType.bbox: - label_id = ann.get('label_id') - x, y, w, h = ann.get('bbox') + x, y, w, h = ann['bbox'] loaded.append(Bbox(x, y, w, h, label=label_id, - id=ann_id, attributes=attributes, group=group)) + id=ann_id, attributes=attributes, group=group, + z_order=z_order)) elif ann_type == AnnotationType.points: - label_id = ann.get('label_id') - points = ann.get('points') loaded.append(Points(points, label=label_id, - id=ann_id, attributes=attributes, group=group)) + id=ann_id, attributes=attributes, group=group, + z_order=z_order)) elif ann_type == AnnotationType.caption: caption = ann.get('caption') diff --git a/datumaro/tests/test_cvat_format.py b/datumaro/tests/test_cvat_format.py index 4c9545d6f838..c2879ef8f575 100644 --- a/datumaro/tests/test_cvat_format.py +++ b/datumaro/tests/test_cvat_format.py @@ -110,20 +110,20 @@ def __iter__(self): return iter([ DatasetItem(id=0, subset='train', image=np.ones((8, 8, 3)), annotations=[ - Bbox(0, 2, 4, 2, label=0, + Bbox(0, 2, 4, 2, label=0, z_order=1, attributes={ - 'occluded': True, 'z_order': 1, + 'occluded': True, 'a1': True, 'a2': 'v3' }), - PolyLine([1, 2, 3, 4, 5, 6, 7, 8], - attributes={'occluded': False, 'z_order': 0}), + PolyLine([1, 2, 3, 4, 5, 6, 7, 8], z_order=0, + attributes={'occluded': False}), ]), DatasetItem(id=1, subset='train', image=np.ones((10, 10, 3)), annotations=[ - Polygon([1, 2, 3, 4, 6, 5], - attributes={'occluded': False, 'z_order': 1}), - Points([1, 2, 3, 4, 5, 6], label=1, - attributes={'occluded': False, 'z_order': 2}), + Polygon([1, 2, 3, 4, 6, 5], z_order=1, + attributes={'occluded': False}), + Points([1, 2, 3, 4, 5, 6], label=1, z_order=2, + attributes={'occluded': False}), ]), ]) @@ -163,7 +163,7 @@ def test_can_save_and_load(self): for i in range(10): label_categories.add(str(i)) label_categories.items[2].attributes.update(['a1', 'a2']) - label_categories.attributes.update(['z_order', 'occluded']) + label_categories.attributes.update(['occluded']) class SrcExtractor(Extractor): def __iter__(self): @@ -194,9 +194,9 @@ def __iter__(self): DatasetItem(id=2, subset='s2', image=np.ones((5, 10, 3)), annotations=[ - Polygon([0, 0, 4, 0, 4, 4], + Polygon([0, 0, 4, 0, 4, 4], z_order=1, label=3, group=4, - attributes={ 'z_order': 1, 'occluded': False }), + attributes={ 'occluded': False }), PolyLine([5, 0, 9, 0, 5, 5]), # will be skipped as no label ] ), @@ -213,15 +213,15 @@ def __iter__(self): return iter([ DatasetItem(id=0, subset='s1', image=np.zeros((5, 10, 3)), annotations=[ - Polygon([0, 0, 4, 0, 4, 4], + Polygon([0, 0, 4, 0, 4, 4], z_order=0, label=1, group=4, - attributes={ 'z_order': 0, 'occluded': True }), - Polygon([5, 0, 9, 0, 5, 5], + attributes={ 'occluded': True }), + Polygon([5, 0, 9, 0, 5, 5], z_order=0, label=2, group=4, - attributes={ 'z_order': 0, 'occluded': False }), - Points([1, 1, 3, 2, 2, 3], + attributes={ 'occluded': False }), + Points([1, 1, 3, 2, 2, 3], z_order=0, label=2, - attributes={ 'z_order': 0, 'occluded': False, + attributes={ 'occluded': False, 'a1': 'x', 'a2': 42 }), Label(1), Label(2, attributes={ 'a1': 'y', 'a2': 44 }), @@ -229,20 +229,20 @@ def __iter__(self): ), DatasetItem(id=1, subset='s1', annotations=[ - PolyLine([0, 0, 4, 0, 4, 4], + PolyLine([0, 0, 4, 0, 4, 4], z_order=0, label=3, group=4, - attributes={ 'z_order': 0, 'occluded': False }), - Bbox(5, 0, 1, 9, + attributes={ 'occluded': False }), + Bbox(5, 0, 1, 9, z_order=0, label=3, group=4, - attributes={ 'z_order': 0, 'occluded': False }), + attributes={ 'occluded': False }), ] ), DatasetItem(id=2, subset='s2', image=np.ones((5, 10, 3)), annotations=[ - Polygon([0, 0, 4, 0, 4, 4], + Polygon([0, 0, 4, 0, 4, 4], z_order=1, label=3, group=4, - attributes={ 'z_order': 1, 'occluded': False }), + attributes={ 'occluded': False }), ] ), diff --git a/datumaro/tests/test_datumaro_format.py b/datumaro/tests/test_datumaro_format.py index 84146fc09f58..d6a81a53445d 100644 --- a/datumaro/tests/test_datumaro_format.py +++ b/datumaro/tests/test_datumaro_format.py @@ -27,12 +27,12 @@ def __iter__(self): 'x': 1, 'y': '2', }), - Bbox(1, 2, 3, 4, label=4, id=4, attributes={ + Bbox(1, 2, 3, 4, label=4, id=4, z_order=1, attributes={ 'score': 1.0, }), Bbox(5, 6, 7, 8, id=5, group=5), - Points([1, 2, 2, 0, 1, 1], label=0, id=5), - Mask(label=3, id=5, image=np.ones((2, 3))), + Points([1, 2, 2, 0, 1, 1], label=0, id=5, z_order=4), + Mask(label=3, id=5, z_order=2, image=np.ones((2, 3))), ]), DatasetItem(id=21, subset='train', annotations=[ @@ -43,8 +43,8 @@ def __iter__(self): DatasetItem(id=2, subset='val', annotations=[ - PolyLine([1, 2, 3, 4, 5, 6, 7, 8], id=11), - Polygon([1, 2, 3, 4, 5, 6, 7, 8], id=12), + PolyLine([1, 2, 3, 4, 5, 6, 7, 8], id=11, z_order=1), + Polygon([1, 2, 3, 4, 5, 6, 7, 8], id=12, z_order=4), ]), DatasetItem(id=42, subset='test'), diff --git a/datumaro/tests/test_voc_format.py b/datumaro/tests/test_voc_format.py index 564708843f37..9703acaacad1 100644 --- a/datumaro/tests/test_voc_format.py +++ b/datumaro/tests/test_voc_format.py @@ -417,12 +417,12 @@ def __iter__(self): DatasetItem(id=1, subset='a', annotations=[ # overlapping masks, the first should be truncated # the second and third are different instances + Mask(image=np.array([[0, 0, 0, 1, 0]]), label=3, + z_order=3), Mask(image=np.array([[0, 1, 1, 1, 0]]), label=4, z_order=1), Mask(image=np.array([[1, 1, 0, 0, 0]]), label=3, z_order=2), - Mask(image=np.array([[0, 0, 0, 1, 0]]), label=3, - z_order=2), ]), ]) @@ -451,12 +451,12 @@ def __iter__(self): DatasetItem(id=1, subset='a', annotations=[ # overlapping masks, the first should be truncated # the second and third are different instances + Mask(image=np.array([[0, 0, 0, 1, 0]]), label=3, + z_order=3), Mask(image=np.array([[0, 1, 1, 1, 0]]), label=4, z_order=1), Mask(image=np.array([[1, 1, 0, 0, 0]]), label=3, z_order=2), - Mask(image=np.array([[0, 0, 0, 1, 0]]), label=3, - z_order=2), ]), ])