diff --git a/cvat/apps/dataset_manager/annotation.py b/cvat/apps/dataset_manager/annotation.py index 95c127c6085..e34d47cd981 100644 --- a/cvat/apps/dataset_manager/annotation.py +++ b/cvat/apps/dataset_manager/annotation.py @@ -436,24 +436,6 @@ def _modify_unmached_object(obj, end_frame): obj["interpolated_shapes"].append(last_interpolated_shape) obj["interpolated_shapes"].append(shape) - @staticmethod - def normalize_shape(shape): - points = list(shape["points"]) - if len(points) == 2: - points.extend(points) # duplicate points for single point case - points = np.asarray(points).reshape(-1, 2) - broken_line = geometry.LineString(points) - points = [] - for off in range(0, 100, 1): - p = broken_line.interpolate(off / 100, True) - points.append(p.x) - points.append(p.y) - - shape = copy(shape) - shape["points"] = points - - return shape - @staticmethod def get_interpolated_shapes(track, start_frame, end_frame): def copy_shape(source, frame, points=None): diff --git a/cvat/apps/dataset_manager/tests/assets/annotations.json b/cvat/apps/dataset_manager/tests/assets/annotations.json index c70dbcb598b..2a7a0e212f8 100644 --- a/cvat/apps/dataset_manager/tests/assets/annotations.json +++ b/cvat/apps/dataset_manager/tests/assets/annotations.json @@ -1,4 +1,1094 @@ { + "CVAT for images 1.1": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [5.54, 3.5, 19.64, 11.19], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": true, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polyline", + "occluded": false, + "z_order": 1, + "points": [27.15, 26.7, 53.25, 24.8], + "frame": 0, + "label_id": null, + "group": 2, + "source": "manual", + "attributes": [] + }, + { + "type": "points", + "occluded": false, + "z_order": 1, + "points": [42.95, 33.59], + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "cuboid", + "occluded": false, + "z_order": 2, + "points": [ + 51.65, + 37.3, + 51.65, + 46.8, + 70.25, + 37.2, + 70.25, + 46.8, + 72.11, + 36.34, + 72.11, + 45.74, + 53.51, + 36.34, + 53.51, + 45.74 + ], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "CVAT for video 1.1": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [4.75, 4.8, 13.06, 11.39], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polyline", + "occluded": false, + "z_order": 1, + "points": [30.99, 31.37, 47.51, 26.94, 50.21, 22.73], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "points", + "occluded": false, + "z_order": 2, + "points": [59.82, 31.26], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 2, + "source": "manual", + "shapes": [ + { + "type": "cuboid", + "occluded": false, + "z_order": 2, + "points": [ + 52.48, + 37.95, + 52.48, + 44.43, + 67.38, + 37.85, + 67.38, + 44.43, + 68.87, + 37.29, + 68.87, + 43.67, + 53.97, + 37.29, + 53.97, + 43.67 + ], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [65.65, 7.07, 78.83, 16.14], + "frame": 0, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [65.65, 7.07, 78.83, 16.14], + "frame": 1, + "outside": true, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [65.65, 7.073, 78.83, 16.14], + "frame": 2, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CamVid 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [35.0, 22.5, 53.32, 30.63, 22.34, 29.45, 47.43, 38.21], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "COCO 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [7.29, 8.58, 18.45, 19.22], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [27.03, 26.07, 63.94, 37.87, 18.34, 34.97], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "ICDAR Localization 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.0, 8.79, 20.5, 15.69], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [35.0, 22.5, 53.32, 30.63, 22.34, 29.45, 47.43, 38.21], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "ICDAR Recognition 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [] + }, + "ICDAR Segmentation 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.0, 12.1, 25.6, 21.9], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [35.0, 22.5, 53.32, 30.63, 22.34, 29.45], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "ImageNet 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [] + }, + "LabelMe 3.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.0, 8.79, 20.5, 15.69], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [35.0, 22.5, 53.32, 30.63, 22.34, 29.45], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "Market-1501 1.0": { + "version": 0, + "tags": [ + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [] + }, + "MOT 1.1": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.4, 12.09, 17.2, 18.19], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [10.5, 27.29, 23.3, 33.49], + "frame": 0, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "MOTS PNG 1.0": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [19.4, 19.2, 41.7, 22.0, 38.8, 29.5, 21.5, 29.3], + "frame": 0, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "PASCAL VOC 1.1": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [9.4, 12.09, 17.2, 18.19], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "Segmentation mask 1.1": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [11.9, 21.5, 35.2, 21.9, 33.6, 31.9, 12.4, 30.47], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "TFRecord 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [16.5, 17.2, 38.89, 25.63], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "YOLO 1.1": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [8.3, 9.1, 19.2, 14.8], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "WiderFace 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [7.55, 9.75, 16.44, 15.85], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [3.55, 27.75, 11.33, 33.71], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "VGGFace2 1.0": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "points", + "occluded": false, + "z_order": 0, + "points": [28.05, 18.0, 36.65, 17.7, 36.85, 23.7, 26.95, 23.2, 30.35, 28.9], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [57.15, 20.9, 74.25, 32.0], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "Datumaro 1.0": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [5.54, 3.5, 19.64, 11.19], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": true, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "polyline", + "occluded": false, + "z_order": 1, + "points": [27.15, 26.7, 53.25, 24.8], + "frame": 0, + "label_id": null, + "group": 2, + "source": "manual", + "attributes": [] + }, + { + "type": "points", + "occluded": false, + "z_order": 1, + "points": [42.95, 33.59], + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "cuboid", + "occluded": false, + "z_order": 2, + "points": [ + 51.65, + 37.3, + 51.65, + 46.8, + 70.25, + 37.2, + 70.25, + 46.8, + 72.11, + 36.34, + 72.11, + 45.74, + 53.51, + 36.34, + 53.51, + 45.74 + ], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "CVAT for images 1.1 many jobs": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "frame": 8, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [5.54, 3.5, 19.64, 11.19], + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "attributes": [] + }, + { + "type": "polygon", + "occluded": true, + "z_order": 0, + "points": [25.04, 13.7, 35.85, 20.2, 16.65, 19.8], + "frame": 11, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [] + }, + "CVAT for video 1.1 many jobs": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": true, + "z_order": 0, + "points": [4.75, 4.8, 13.06, 11.39], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 10, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for video 1.1 slice track": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 0, + "outside": true, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for images 1.1 merge": { + "version": 0, + "tags": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.95, 8.09, 18.65, 13.39], + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [11.545, 11.7, 19.44, 17.4], + "frame": 3, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 0, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 6, + "outside": true, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 3, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.65, 23.59, 22.65, 29.79], + "frame": 3, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [9.65, 23.59, 22.65, 29.79], + "frame": 9, + "outside": true, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for images 1.1 tag": { + "version": 0, + "tags": [ + { + "frame": 1, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + }, + { + "frame": 8, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "shapes": [], + "tracks": [] + }, + "CVAT for video 1.1 slice track keyframe": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 0, + "outside": false, + "outside": true, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [66.45, 147.08, 182.16, 204.56], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "empty annotation": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [] + }, + "CVAT for images 1.1 different types": { + "version": 0, + "tags": [], + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [11.545, 11.7, 19.44, 17.4], + "frame": 3, + "label_id": null, + "group": 0, + "source": "manual", + "attributes": [] + } + ], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 0, + "source": "manual", + "shapes": [ + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 0, + "outside": false, + "attributes": [] + }, + { + "type": "rectangle", + "occluded": false, + "z_order": 0, + "points": [4.54, 19.59, 21.34, 26.89], + "frame": 6, + "outside": true, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, + "CVAT for video 1.1 polygon": { + "version": 0, + "tags": [], + "shapes": [], + "tracks": [ + { + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 1, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + }, + { + "frame": 0, + "label_id": null, + "group": 1, + "source": "manual", + "shapes": [ + { + "type": "polygon", + "occluded": false, + "z_order": 0, + "points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08], + "frame": 1, + "outside": false, + "attributes": [] + } + ], + "attributes": [] + } + ] + }, "CVAT for video 1.1 polygon": { "version": 0, "tags": [], diff --git a/cvat/apps/dataset_manager/tests/assets/tasks.json b/cvat/apps/dataset_manager/tests/assets/tasks.json index 0e24e234f29..09e2d866287 100644 --- a/cvat/apps/dataset_manager/tests/assets/tasks.json +++ b/cvat/apps/dataset_manager/tests/assets/tasks.json @@ -54,6 +54,190 @@ } ] }, + "icdar_localization_and_recognition": { + "name": "icdar localization/recogntion task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "icdar", + "attributes": [ + { + "name": "text", + "mutable": false, + "input_type": "text", + "values": ["word_1", "word_2", "word_3"] + } + ] + } + ] + }, + "icdar_segmentation": { + "name": "icdar segmentation task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "icdar", + "attributes": [ + { + "name": "text", + "mutable": false, + "input_type": "text", + "values": ["word_1", "word_2", "word_3"] + }, + { + "name": "index", + "mutable": false, + "input_type": "number", + "values": ["0", "1", "2"] + }, + { + "name": "color", + "mutable": false, + "input_type": "text", + "values": ["100 110 240", "10 15 20", "120 128 64"] + }, + { + "name": "center", + "mutable": false, + "input_type": "text", + "values": ["1 2", "2 4", "10 45"] + } + ] + } + ] + }, + "market1501": { + "name": "market1501 task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "market-1501", + "attributes": [ + { + "name": "query", + "mutable": false, + "input_type": "select", + "values": ["True", "False"] + }, + { + "name": "camera_id", + "mutable": false, + "input_type": "number", + "values": ["1", "5", "2"] + }, + { + "name": "person_id", + "mutable": false, + "input_type": "number", + "values": ["1", "6", "1"] + } + ] + } + ] + }, + "wrong_checkbox_value": { + "name": "wrong checkbox value task", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "assignee_id": 2, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [ + { + "name": "select_name", + "mutable": false, + "input_type": "select", + "default_value": "bmw", + "values": ["bmw", "mazda", "renault"] + }, + { + "name": "radio_name", + "mutable": false, + "input_type": "radio", + "default_value": "x1", + "values": ["x1", "x2", "x3"] + }, + { + "name": "check_name", + "mutable": true, + "input_type": "checkbox", + "default_value": "false", + "values": ["false"] + }, + { + "name": "text_name", + "mutable": false, + "input_type": "text", + "default_value": "qwerty", + "values": ["qwerty"] + }, + { + "name": "number_name", + "mutable": false, + "input_type": "number", + "default_value": "-4", + "values": ["-4", "4", "1"] + } + ] + }, + { + "name": "person", + "color": "#c06060", + "attributes": [] + } + ] + }, + "no attributes": { + "name": "no attributes", + "overlap": 0, + "segment_size": 100, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + }, + "many jobs": { + "name": "many jobs", + "overlap": 0, + "segment_size": 5, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + }, + "change overlap and segment size": { + "name": "change overlap and segment size", + "overlap": 3, + "segment_size": 6, + "owner_id": 1, + "labels": [ + { + "name": "car", + "color": "#2080c0", + "attributes": [] + } + ] + }, "widerface with all attributes": { "name": "widerface task", "overlap": 0, diff --git a/cvat/apps/dataset_manager/tests/test_annotation.py b/cvat/apps/dataset_manager/tests/test_annotation.py index 4f4dc84c123..f7263dc3cc4 100644 --- a/cvat/apps/dataset_manager/tests/test_annotation.py +++ b/cvat/apps/dataset_manager/tests/test_annotation.py @@ -165,4 +165,5 @@ def test_line_interpolation(self): ] } - self._check_interpolation(track) \ No newline at end of file + self._check_interpolation(track) + diff --git a/cvat/apps/dataset_manager/tests/test_rest_api_formats.py b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py index 14af4154922..880585b25f2 100644 --- a/cvat/apps/dataset_manager/tests/test_rest_api_formats.py +++ b/cvat/apps/dataset_manager/tests/test_rest_api_formats.py @@ -5,29 +5,35 @@ import copy import json import os.path as osp +import os +import av +import numpy as np import random import xml.etree.ElementTree as ET import zipfile from io import BytesIO +import itertools from datumaro.components.dataset import Dataset -from datumaro.util.test_utils import TestDir, compare_datasets +from datumaro.util.test_utils import compare_datasets, TestDir from django.contrib.auth.models import Group, User from PIL import Image from rest_framework import status from rest_framework.test import APIClient, APITestCase +import cvat.apps.dataset_manager as dm from cvat.apps.dataset_manager.bindings import CvatTaskDataExtractor, TaskData from cvat.apps.dataset_manager.task import TaskAnnotation from cvat.apps.engine.models import Task tasks_path = osp.join(osp.dirname(__file__), 'assets', 'tasks.json') -with open(tasks_path) as f: - tasks = json.load(f) +with open(tasks_path) as file: + tasks = json.load(file) + +annotation_path = osp.join(osp.dirname(__file__), 'assets', 'annotations.json') +with open(annotation_path) as file: + annotations = json.load(file) -annotations_path = osp.join(osp.dirname(__file__), 'assets', 'annotations.json') -with open(annotations_path) as f: - annotations = json.load(f) def generate_image_file(filename, size=(100, 50)): f = BytesIO() @@ -37,6 +43,43 @@ def generate_image_file(filename, size=(100, 50)): f.seek(0) return f + +def generate_video_file(filename, width=1280, height=720, duration=1, fps=25, codec_name='mpeg4'): + f = BytesIO() + total_frames = duration * fps + file_ext = os.path.splitext(filename)[1][1:] + container = av.open(f, mode='w', format=file_ext) + + stream = container.add_stream(codec_name=codec_name, rate=fps) + stream.width = width + stream.height = height + stream.pix_fmt = 'yuv420p' + + for frame_i in range(total_frames): + img = np.empty((stream.width, stream.height, 3)) + img[:, :, 0] = 0.5 + 0.5 * np.sin(2 * np.pi * (0 / 3 + frame_i / total_frames)) + img[:, :, 1] = 0.5 + 0.5 * np.sin(2 * np.pi * (1 / 3 + frame_i / total_frames)) + img[:, :, 2] = 0.5 + 0.5 * np.sin(2 * np.pi * (2 / 3 + frame_i / total_frames)) + + img = np.round(255 * img).astype(np.uint8) + img = np.clip(img, 0, 255) + + frame = av.VideoFrame.from_ndarray(img, format='rgb24') + for packet in stream.encode(frame): + container.mux(packet) + + # Flush stream + for packet in stream.encode(): + container.mux(packet) + + # Close the file + container.close() + f.name = filename + f.seek(0) + + return [(width, height)] * total_frames, f + + class ForceLogin: def __init__(self, user, client): self.user = user @@ -82,12 +125,25 @@ def _put_api_v1_task_id_annotations(self, tid, data): return response + def _put_api_v1_job_id_annotations(self, jid, data): + with ForceLogin(self.admin, self.client): + response = self.client.put("/api/v1/jobs/%s/annotations" % jid, + data=data, format="json") + + return response + @staticmethod def _generate_task_images(count): # pylint: disable=no-self-use images = {"client_files[%d]" % i: generate_image_file("image_%d.jpg" % i) for i in range(count)} images["image_quality"] = 75 return images + @staticmethod + def _generate_task_videos(count): # pylint: disable=no-self-use + videos = {"client_files[%d]" % i: generate_video_file("video_%d.mp4" % i) for i in range(count)} + videos["image_quality"] = 75 + return videos + def _create_task(self, data, image_data): with ForceLogin(self.user, self.client): response = self.client.post('/api/v1/tasks', data=data, format="json") @@ -103,6 +159,16 @@ def _create_task(self, data, image_data): return task + def _get_jobs(self, task_id): + with ForceLogin(self.admin, self.client): + response = self.client.get("/api/v1/tasks/{}/jobs".format(task_id)) + return response.data + + def _get_request(self, path, user): + with ForceLogin(user, self.client): + response = self.client.get(path) + return response + def _get_data_from_task(self, task_id, include_images): task_ann = TaskAnnotation(task_id) task_ann.init_from_db() @@ -159,10 +225,46 @@ def _create_annotations(self, task, name_ann, key_get_values): "spec_id": spec_id, "value": value, }) - response = self._put_api_v1_task_id_annotations(task["id"], tmp_annotations) self.assertEqual(response.status_code, status.HTTP_200_OK) + def _create_annotations_in_job(self, task, job_id, name_ann, key_get_values): + tmp_annotations = copy.deepcopy(annotations[name_ann]) + + # change attributes in all annotations + for item in tmp_annotations: + if item in ["tags", "shapes", "tracks"]: + for index_elem, _ in enumerate(tmp_annotations[item]): + tmp_annotations[item][index_elem]["label_id"] = task["labels"][0]["id"] + + for index_attribute, attribute in enumerate(task["labels"][0]["attributes"]): + spec_id = task["labels"][0]["attributes"][index_attribute]["id"] + + if key_get_values == "random": + if attribute["input_type"] == "number": + start = int(attribute["values"][0]) + stop = int(attribute["values"][1]) + 1 + step = int(attribute["values"][2]) + value = str(random.randrange(start, stop, step)) + else: + value = random.choice(task["labels"][0]["attributes"][index_attribute]["values"]) + elif key_get_values == "default": + value = attribute["default_value"] + + if item == "tracks" and attribute["mutable"]: + for index_shape, _ in enumerate(tmp_annotations[item][index_elem]["shapes"]): + tmp_annotations[item][index_elem]["shapes"][index_shape]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + else: + tmp_annotations[item][index_elem]["attributes"].append({ + "spec_id": spec_id, + "value": value, + }) + response = self._put_api_v1_job_id_annotations(job_id, tmp_annotations) + self.assertEqual(response.status_code, status.HTTP_200_OK) + def _download_file(self, url, data, user, file_name): response = self._get_request_with_data(url, data, user) self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) @@ -189,12 +291,746 @@ def _generate_url_dump_tasks_annotations(self, task_id): def _generate_url_upload_tasks_annotations(self, task_id, upload_format_name): return f"/api/v1/tasks/{task_id}/annotations?format={upload_format_name}" + def _generate_url_dump_job_annotations(self, job_id): + return f"/api/v1/jobs/{job_id}/annotations" + + def _generate_url_upload_job_annotations(self, job_id, upload_format_name): + return f"/api/v1/jobs/{job_id}/annotations?format={upload_format_name}" + + def _generate_url_dump_dataset(self, task_id): + return f"/api/v1/tasks/{task_id}/dataset" + def _remove_annotations(self, url, user): response = self._delete_request(url, user) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) return response + class TaskDumpUploadTest(_DbTestBase): + def test_api_v1_dump_and_upload_annotations_with_objects_type_is_shape(self): + test_name = self._testMethodName + dump_formats = dm.views.get_export_formats() + upload_formats = dm.views.get_import_formats() + expected = { + self.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED,'file_exists': True, 'annotation_loaded': True}, + self.user: {'name': 'user', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True, 'annotation_loaded': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'create code': status.HTTP_401_UNAUTHORIZED, + 'accept code': status.HTTP_401_UNAUTHORIZED, 'file_exists': False, 'annotation_loaded': False}, + } + + with TestDir() as test_dir: + # Dump annotations with objects type is shape + for dump_format in dump_formats: + if not dump_format.ENABLED: + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + images = self._generate_task_images(3) + # create task with annotations + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif dump_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + task_id = task["id"] + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "default") + else: + self._create_annotations(task, dump_format_name, "random") + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + + for user, edata in list(expected.items()): + user_name = edata['name'] + file_zip_name = osp.join(test_dir, f'{test_name}_{user_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['create code']) + data = { + "format": dump_format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['code']) + if response.status_code == status.HTTP_200_OK: + content = BytesIO(b"".join(response.streaming_content)) + with open(file_zip_name, "wb") as f: + f.write(content.getvalue()) + self.assertEqual(osp.exists(file_zip_name), edata['file_exists']) + + # Upload annotations with objects type is shape + for upload_format in upload_formats: + upload_format_name = upload_format.DISPLAY_NAME + if upload_format_name == "CVAT 1.1": + file_zip_name = osp.join(test_dir, f'{test_name}_admin_CVAT for images 1.1.zip') + else: + + file_zip_name = osp.join(test_dir, f'{test_name}_admin_{upload_format_name}.zip') + if not upload_format.ENABLED or not osp.exists(file_zip_name): + continue + with self.subTest(format=upload_format_name): + if upload_format_name in [ + "MOTS PNG 1.0", # issue #2925 and changed points values + ]: + self.skipTest("Format is fail") + if osp.exists(file_zip_name): + for user, edata in list(expected.items()): + # remove all annotations from task (create new task without annotation) + images = self._generate_task_images(3) + if upload_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif upload_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif upload_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + task_id = task["id"] + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + + with open(file_zip_name, 'rb') as binary_file: + response = self._put_request_with_data(url, {"annotation_file": binary_file}, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._put_request_with_data(url, {}, user) + self.assertEqual(response.status_code, edata['create code']) + + def test_api_v1_dump_annotations_with_objects_type_is_track(self): + test_name = self._testMethodName + + dump_formats = dm.views.get_export_formats() + upload_formats = dm.views.get_import_formats() + expected = { + self.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True, 'annotation_loaded': True}, + self.user: {'name': 'user', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True, 'annotation_loaded': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'create code': status.HTTP_401_UNAUTHORIZED, + 'accept code': status.HTTP_401_UNAUTHORIZED, 'file_exists': False, 'annotation_loaded': False}, + } + + with TestDir() as test_dir: + # Dump annotations with objects type is track + for dump_format in dump_formats: + if not dump_format.ENABLED: + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + # create task with annotations + video = self._generate_task_videos(1) + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], video) + elif dump_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], video) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], video) + else: + task = self._create_task(tasks["main"], video) + task_id = task["id"] + + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "default") + else: + self._create_annotations(task, dump_format_name, "random") + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + + for user, edata in list(expected.items()): + user_name = edata['name'] + file_zip_name = osp.join(test_dir, f'{test_name}_{user_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['create code']) + data = { + "format": dump_format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['code']) + if response.status_code == status.HTTP_200_OK: + content = BytesIO(b"".join(response.streaming_content)) + with open(file_zip_name, "wb") as f: + f.write(content.getvalue()) + self.assertEqual(osp.exists(file_zip_name), edata['file_exists']) + # Upload annotations with objects type is track + for upload_format in upload_formats: + upload_format_name = upload_format.DISPLAY_NAME + if upload_format_name == "CVAT 1.1": + file_zip_name = osp.join(test_dir, f'{test_name}_admin_CVAT for video 1.1.zip') + else: + file_zip_name = osp.join(test_dir, f'{test_name}_admin_{upload_format_name}.zip') + if not upload_format.ENABLED or not osp.exists(file_zip_name): + continue + with self.subTest(format=upload_format_name): + if upload_format_name in [ + "MOTS PNG 1.0", # issue #2925 and changed points values + ]: + self.skipTest("Format is fail") + if osp.exists(file_zip_name): + for user, edata in list(expected.items()): + # remove all annotations from task (create new task without annotation) + video = self._generate_task_videos(1) + if upload_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], video) + elif upload_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], video) + elif upload_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], video) + else: + task = self._create_task(tasks["main"], video) + task_id = task["id"] + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + + with open(file_zip_name, 'rb') as binary_file: + response = self._put_request_with_data(url, {"annotation_file": binary_file}, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._put_request_with_data(url, {}, user) + self.assertEqual(response.status_code, edata['create code']) + + def test_api_v1_dump_tag_annotations(self): + dump_format_name = "CVAT for images 1.1" + data = { + "format": dump_format_name, + "action": "download", + } + test_cases = ['all' 'first'] + expected = { + self.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True}, + self.user: {'name': 'user', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'create code': status.HTTP_401_UNAUTHORIZED, + 'accept code': status.HTTP_401_UNAUTHORIZED, 'file_exists': False}, + } + for test_case in test_cases: + images = self._generate_task_images(10) + task = self._create_task(tasks["change overlap and segment size"], images) + task_id = task["id"] + jobs = self._get_jobs(task_id) + + if test_case == "all": + for job in jobs: + self._create_annotations_in_job(task, job["id"], "CVAT for images 1.1 tag", "default") + else: + self._create_annotations_in_job(task, jobs[0]["id"], "CVAT for images 1.1 tag", "default") + + for user, edata in list(expected.items()): + with self.subTest(format=f"{edata['name']}"): + with TestDir() as test_dir: + user_name = edata['name'] + url = self._generate_url_dump_tasks_annotations(task_id) + + file_zip_name = osp.join(test_dir, f'{user_name}.zip') + data = { + "format": dump_format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['accept code']) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['create code']) + data = { + "format": dump_format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata['code']) + if response.status_code == status.HTTP_200_OK: + content = BytesIO(b"".join(response.streaming_content)) + with open(file_zip_name, "wb") as f: + f.write(content.getvalue()) + self.assertEqual(osp.exists(file_zip_name), edata['file_exists']) + + def test_api_v1_dump_and_upload_annotations_with_objects_are_different_images(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + upload_types = ["task", "job"] + + images = self._generate_task_images(2) + task = self._create_task(tasks["main"], images) + task_id = task["id"] + + for upload_type in upload_types: + with self.subTest(format=type): + with TestDir() as test_dir: + if upload_type == "task": + self._create_annotations(task, "CVAT for images 1.1 different types", "random") + else: + jobs = self._get_jobs(task_id) + job_id = jobs[0]["id"] + self._create_annotations_in_job(task, job_id, "CVAT for images 1.1 different types", "random") + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}_{upload_type}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + self._remove_annotations(url, self.admin) + if upload_type == "task": + url_upload = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + else: + jobs = self._get_jobs(task_id) + url_upload = self._generate_url_upload_job_annotations(jobs[0]["id"], "CVAT 1.1") + + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url_upload, binary_file, self.admin) + + response = self._get_request(f"/api/v1/tasks/{task_id}/annotations", self.admin) + self.assertEqual(len(response.data["shapes"]), 2) + self.assertEqual(len(response.data["tracks"]), 0) + + def test_api_v1_dump_and_upload_annotations_with_objects_are_different_video(self): + test_name = self._testMethodName + dump_format_name = "CVAT for video 1.1" + upload_types = ["task", "job"] + + video = self._generate_task_videos(1) + task = self._create_task(tasks["main"], video) + task_id = task["id"] + + for upload_type in upload_types: + with self.subTest(format=type): + with TestDir() as test_dir: + if upload_type == "task": + self._create_annotations(task, "CVAT for images 1.1 different types", "random") + else: + jobs = self._get_jobs(task_id) + job_id = jobs[0]["id"] + self._create_annotations_in_job(task, job_id, "CVAT for images 1.1 different types", "random") + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}_{upload_type}.zip') + + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + self._remove_annotations(url, self.admin) + if upload_type == "task": + url_upload = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + else: + jobs = self._get_jobs(task_id) + url_upload = self._generate_url_upload_job_annotations(jobs[0]["id"], "CVAT 1.1") + + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url_upload, binary_file, self.admin) + self.assertEqual(osp.exists(file_zip_name), True) + + response = self._get_request(f"/api/v1/tasks/{task_id}/annotations", self.admin) + self.assertEqual(len(response.data["shapes"]), 0) + self.assertEqual(len(response.data["tracks"]), 2) + + def test_api_v1_dump_and_upload_with_objects_type_is_track_and_outside_property(self): + test_name = self._testMethodName + dump_format_name = "CVAT for video 1.1" + video = self._generate_task_videos(1) + task = self._create_task(tasks["main"], video) + self._create_annotations(task, "CVAT for video 1.1 slice track", "random") + task_id = task["id"] + + with TestDir() as test_dir: + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + with open(file_zip_name, 'rb') as binary_file: + url = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + self._upload_file(url, binary_file, self.admin) + + def test_api_v1_dump_and_upload_with_objects_type_is_track_and_keyframe_property(self): + test_name = self._testMethodName + dump_format_name = "CVAT for video 1.1" + + video = self._generate_task_videos(1) + task = self._create_task(tasks["main"], video) + self._create_annotations(task, "CVAT for video 1.1 slice track keyframe", "random") + task_id = task["id"] + + with TestDir() as test_dir: + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}.zip') + + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + with open(file_zip_name, 'rb') as binary_file: + url = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + self._upload_file(url, binary_file, self.admin) + + def test_api_v1_dump_upload_annotations_from_several_jobs(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + + images = self._generate_task_images(10) + task = self._create_task(tasks["change overlap and segment size"], images) + task_id = task["id"] + jobs = self._get_jobs(task_id) + for job in jobs: + self._create_annotations_in_job(task, job["id"], "CVAT for images 1.1 merge", "random") + + with TestDir() as test_dir: + url = self._generate_url_dump_tasks_annotations(task_id) + file_zip_name = osp.join(test_dir, f'{test_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + # remove annotations + self._remove_annotations(url, self.admin) + url = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + def test_api_v1_dump_annotations_with_objects_type_is_shape_from_several_jobs(self): + test_name = self._testMethodName + dump_format_name = "CVAT for images 1.1" + test_cases = ['all', 'first'] + + images = self._generate_task_images(10) + task = self._create_task(tasks["change overlap and segment size"], images) + task_id = task["id"] + + for test_case in test_cases: + with TestDir() as test_dir: + jobs = self._get_jobs(task_id) + if test_case == "all": + for job in jobs: + self._create_annotations_in_job(task, job["id"], dump_format_name, "default") + else: + self._create_annotations_in_job(task, jobs[0]["id"], dump_format_name, "default") + + url = self._generate_url_dump_tasks_annotations(task_id) + + file_zip_name = osp.join(test_dir, f'{test_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + # remove annotations + self._remove_annotations(url, self.admin) + url = self._generate_url_upload_tasks_annotations(task_id, "CVAT 1.1") + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + def test_api_v1_export_dataset(self): + test_name = self._testMethodName + dump_formats = dm.views.get_export_formats() + + expected = { + self.admin: {'name': 'admin', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True}, + self.user: {'name': 'user', 'code': status.HTTP_200_OK, 'create code': status.HTTP_201_CREATED, + 'accept code': status.HTTP_202_ACCEPTED, 'file_exists': True}, + None: {'name': 'none', 'code': status.HTTP_401_UNAUTHORIZED, 'create code': status.HTTP_401_UNAUTHORIZED, + 'accept code': status.HTTP_401_UNAUTHORIZED, 'file_exists': False}, + } + + with TestDir() as test_dir: + # Dump annotations with objects type is shape + for dump_format in dump_formats: + if not dump_format.ENABLED or dump_format.DISPLAY_NAME != "CVAT for images 1.1": + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + images = self._generate_task_images(3) + # create task with annotations + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif dump_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + task_id = task["id"] + # dump annotations + url = self._generate_url_dump_dataset(task_id) + for user, edata in list(expected.items()): + user_name = edata['name'] + file_zip_name = osp.join(test_dir, f'{test_name}_{user_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata["accept code"]) + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata["create code"]) + data = { + "format": dump_format_name, + "action": "download", + } + response = self._get_request_with_data(url, data, user) + self.assertEqual(response.status_code, edata["code"]) + if response.status_code == status.HTTP_200_OK: + content = BytesIO(b"".join(response.streaming_content)) + with open(file_zip_name, "wb") as f: + f.write(content.getvalue()) + self.assertEqual(response.status_code, edata['code']) + self.assertEqual(osp.exists(file_zip_name), edata['file_exists']) + + def test_api_v1_dump_empty_frames(self): + dump_formats = dm.views.get_export_formats() + upload_formats = dm.views.get_import_formats() + + with TestDir() as test_dir: + for dump_format in dump_formats: + if not dump_format.ENABLED: + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + images = self._generate_task_images(3) + task = self._create_task(tasks["no attributes"], images) + task_id = task["id"] + self._create_annotations(task, "empty annotation", "default") + url = self._generate_url_dump_tasks_annotations(task_id) + + file_zip_name = osp.join(test_dir, f'empty_{dump_format_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + for upload_format in upload_formats: + upload_format_name = upload_format.DISPLAY_NAME + if upload_format_name == "CVAT 1.1": + file_zip_name = osp.join(test_dir, 'empty_CVAT for images 1.1.zip') + else: + file_zip_name = osp.join(test_dir, f'empty_{upload_format_name}.zip') + if not osp.exists(file_zip_name) or not upload_format.ENABLED: + continue + with self.subTest(format=upload_format_name): + if upload_format_name in [ + "MOTS PNG 1.0", # issue #2925 and changed points values + ]: + self.skipTest("Format is fail") + images = self._generate_task_images(3) + task = self._create_task(tasks["no attributes"], images) + task_id = task["id"] + + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + + with open(file_zip_name, 'rb') as binary_file: + response = self._put_request_with_data(url, {"annotation_file": binary_file}, self.admin) + self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + response = self._put_request_with_data(url, {}, self.admin) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertIsNone(response.data) + + def test_api_v1_rewriting_annotations(self): + test_name = self._testMethodName + dump_formats = dm.views.get_export_formats() + with TestDir() as test_dir: + for dump_format in dump_formats: + if not dump_format.ENABLED: + continue + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(format=dump_format_name): + if dump_format_name in [ + "MOTS PNG 1.0", # issue #2925 and changed points values + "Datumaro 1.0" # Datumaro 1.0 is not in the list of import format + ]: + self.skipTest("Format is fail") + images = self._generate_task_images(3) + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif dump_format_name in ["ICDAR Localization 1.0", "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + task_id = task["id"] + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "default") + else: + self._create_annotations(task, dump_format_name, "random") + + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_ann_prev_data = task_ann.data + url = self._generate_url_dump_tasks_annotations(task_id) + + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self.assertEqual(osp.exists(file_zip_name), True) + + self._remove_annotations(url, self.admin) + + self._create_annotations(task, "CVAT for images 1.1 many jobs", "default") + + if dump_format_name == "CVAT for images 1.1" or dump_format_name == "CVAT for video 1.1": + dump_format_name = "CVAT 1.1" + url = self._generate_url_upload_tasks_annotations(task_id, dump_format_name) + + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + task_ann = TaskAnnotation(task_id) + task_ann.init_from_db() + task_ann_data = task_ann.data + self.assertEqual(len(task_ann_data["shapes"]), len(task_ann_prev_data["shapes"])) + + def test_api_v1_tasks_annotations_dump_and_upload_many_jobs_with_datumaro(self): + test_name = self._testMethodName + upload_format_name = "CVAT 1.1" + include_images_params = (False, True) + dump_format_names = ("CVAT for images 1.1", "CVAT for video 1.1") + + for dump_format_name, include_images in itertools.product(dump_format_names, include_images_params): + with self.subTest(f"{dump_format_name}_include_images_{include_images}"): + # create task with annotations + images = self._generate_task_images(13) + task = self._create_task(tasks["many jobs"], images) + self._create_annotations(task, f'{dump_format_name} many jobs', "default") + + task_id = task["id"] + data_from_task_before_upload = self._get_data_from_task(task_id, include_images) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + # equals annotations + data_from_task_after_upload = self._get_data_from_task(task_id, include_images) + compare_datasets(self, data_from_task_before_upload, data_from_task_after_upload) + + def test_api_v1_tasks_annotations_dump_and_upload_with_datumaro(self): + test_name = self._testMethodName + # get formats + dump_formats = dm.views.get_export_formats() + include_images_params = (False, True) + for dump_format, include_images in itertools.product(dump_formats, include_images_params): + if dump_format.ENABLED: + dump_format_name = dump_format.DISPLAY_NAME + with self.subTest(dump_format_name): + if dump_format_name in [ + "MOT 1.1", + "Datumaro 1.0", # not uploaded + "CamVid 1.0", # issue #2840 and changed points values + "MOTS PNG 1.0", # changed points values + "Segmentation mask 1.1", # changed points values + "ICDAR Segmentation 1.0", # changed points values + ]: + self.skipTest("Format is fail") + + # create task + images = self._generate_task_images(3) + if dump_format_name == "Market-1501 1.0": + task = self._create_task(tasks["market1501"], images) + elif dump_format_name in ["ICDAR Localization 1.0", + "ICDAR Recognition 1.0"]: + task = self._create_task(tasks["icdar_localization_and_recognition"], images) + elif dump_format_name == "ICDAR Segmentation 1.0": + task = self._create_task(tasks["icdar_segmentation"], images) + else: + task = self._create_task(tasks["main"], images) + + # create annotations + if dump_format_name in [ + "MOT 1.1", "MOTS PNG 1.0", \ + "PASCAL VOC 1.1", "Segmentation mask 1.1", \ + "TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ + "WiderFace 1.0", "VGGFace2 1.0", \ + ]: + self._create_annotations(task, dump_format_name, "default") + else: + self._create_annotations(task, dump_format_name, "random") + + task_id = task["id"] + data_from_task_before_upload = self._get_data_from_task(task_id, include_images) + + # dump annotations + url = self._generate_url_dump_tasks_annotations(task_id) + with TestDir() as test_dir: + file_zip_name = osp.join(test_dir, f'{test_name}_{dump_format_name}.zip') + data = { + "format": dump_format_name, + "action": "download", + } + self._download_file(url, data, self.admin, file_zip_name) + self._check_downloaded_file(file_zip_name) + + # remove annotations + self._remove_annotations(url, self.admin) + + # upload annotations + if dump_format_name in ["CVAT for images 1.1", "CVAT for video 1.1"]: + upload_format_name = "CVAT 1.1" + else: + upload_format_name = dump_format_name + url = self._generate_url_upload_tasks_annotations(task_id, upload_format_name) + with open(file_zip_name, 'rb') as binary_file: + self._upload_file(url, binary_file, self.admin) + + # equals annotations + data_from_task_after_upload = self._get_data_from_task(task_id, include_images) + compare_datasets(self, data_from_task_before_upload, data_from_task_after_upload) + def test_api_v1_check_duplicated_polygon_points(self): test_name = self._testMethodName images = self._generate_task_images(10)