From 5c104af793b6764e66c735ab7c1f6c1a329b2874 Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Wed, 30 Nov 2022 14:15:32 +0200 Subject: [PATCH 01/10] Fix an issue with writing extra information in annotations --- cvat-core/src/annotations-objects.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cvat-core/src/annotations-objects.ts b/cvat-core/src/annotations-objects.ts index bca6a83d1929..550512fa95b6 100644 --- a/cvat-core/src/annotations-objects.ts +++ b/cvat-core/src/annotations-objects.ts @@ -2996,11 +2996,13 @@ export class SkeletonTrack extends Track { if (updatedOccluded.length) { updatedOccluded.forEach((el) => { el.updateFlags.occluded = true; }); updateElements(updatedOccluded, HistoryActions.CHANGED_OCCLUDED); + this.saveOccluded(data.occluded, frame); } if (updatedOutside.length) { updatedOutside.forEach((el) => { el.updateFlags.outside = true; }); updateElements(updatedOutside, HistoryActions.CHANGED_OUTSIDE); + this.saveOutside(frame, data.outside); } if (updatedKeyframe.length) { @@ -3010,16 +3012,19 @@ export class SkeletonTrack extends Track { this.saveKeyframe(frame, data.keyframe); data.updateFlags.keyframe = false; updateElements(updatedKeyframe, HistoryActions.CHANGED_KEYFRAME); + this.saveKeyframe(frame, data.keyframe); } if (updatedHidden.length) { updatedHidden.forEach((el) => { el.updateFlags.hidden = true; }); updateElements(updatedHidden, HistoryActions.CHANGED_HIDDEN, 'hidden'); + this.saveHidden(data.hidden, frame); } if (updatedLock.length) { updatedLock.forEach((el) => { el.updateFlags.lock = true; }); updateElements(updatedLock, HistoryActions.CHANGED_LOCK, 'lock'); + this.saveLock(data.lock, frame); } const result = Track.prototype.save.call(this, frame, data); From aaf49af9c701820b1b3d96f4c8710a290e27e156 Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Wed, 30 Nov 2022 15:08:12 +0200 Subject: [PATCH 02/10] Small fix --- cvat-core/src/annotations-objects.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/cvat-core/src/annotations-objects.ts b/cvat-core/src/annotations-objects.ts index 550512fa95b6..ce5fc9240607 100644 --- a/cvat-core/src/annotations-objects.ts +++ b/cvat-core/src/annotations-objects.ts @@ -3012,7 +3012,6 @@ export class SkeletonTrack extends Track { this.saveKeyframe(frame, data.keyframe); data.updateFlags.keyframe = false; updateElements(updatedKeyframe, HistoryActions.CHANGED_KEYFRAME); - this.saveKeyframe(frame, data.keyframe); } if (updatedHidden.length) { From 43fdd4406d9fb23849b7ceb649ce1a523964d79e Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Wed, 30 Nov 2022 15:08:51 +0200 Subject: [PATCH 03/10] Update Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54de3684ba6d..8936f0907787 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ non-ascii paths while adding files from "Connected file share" (issue #4428) - Visibility and ignored information fail to be loaded (MOT dataset format) () - Added force logout on CVAT app start if token is missing () - Missed token with using social account authentication () +- Redundant writing of skeleton annotations (CVAT for images) () ### Security - TDB From 8497993fea414de5ebbab9d67bc4754c8cb839bb Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Wed, 30 Nov 2022 17:40:29 +0200 Subject: [PATCH 04/10] Add fixes to the server --- cvat-core/src/annotations-objects.ts | 4 ---- cvat/apps/dataset_manager/annotation.py | 25 +++++++++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/cvat-core/src/annotations-objects.ts b/cvat-core/src/annotations-objects.ts index ce5fc9240607..bca6a83d1929 100644 --- a/cvat-core/src/annotations-objects.ts +++ b/cvat-core/src/annotations-objects.ts @@ -2996,13 +2996,11 @@ export class SkeletonTrack extends Track { if (updatedOccluded.length) { updatedOccluded.forEach((el) => { el.updateFlags.occluded = true; }); updateElements(updatedOccluded, HistoryActions.CHANGED_OCCLUDED); - this.saveOccluded(data.occluded, frame); } if (updatedOutside.length) { updatedOutside.forEach((el) => { el.updateFlags.outside = true; }); updateElements(updatedOutside, HistoryActions.CHANGED_OUTSIDE); - this.saveOutside(frame, data.outside); } if (updatedKeyframe.length) { @@ -3017,13 +3015,11 @@ export class SkeletonTrack extends Track { if (updatedHidden.length) { updatedHidden.forEach((el) => { el.updateFlags.hidden = true; }); updateElements(updatedHidden, HistoryActions.CHANGED_HIDDEN, 'hidden'); - this.saveHidden(data.hidden, frame); } if (updatedLock.length) { updatedLock.forEach((el) => { el.updateFlags.lock = true; }); updateElements(updatedLock, HistoryActions.CHANGED_LOCK, 'lock'); - this.saveLock(data.lock, frame); } const result = Track.prototype.save.call(this, frame, data); diff --git a/cvat/apps/dataset_manager/annotation.py b/cvat/apps/dataset_manager/annotation.py index 16e67691a5f0..44a4b8b31a7a 100644 --- a/cvat/apps/dataset_manager/annotation.py +++ b/cvat/apps/dataset_manager/annotation.py @@ -360,29 +360,34 @@ class TrackManager(ObjectManager): def to_shapes(self, end_frame, end_skeleton_frame=None): shapes = [] for idx, track in enumerate(self.objects): - track_shapes = [] + track_shapes = {} for shape in TrackManager.get_interpolated_shapes(track, 0, end_frame): shape["label_id"] = track["label_id"] shape["group"] = track["group"] shape["track_id"] = idx shape["attributes"] += track["attributes"] shape["elements"] = [] - track_shapes.append(shape) + track_shapes[shape["frame"]] = shape + last_frame = shape["frame"] - while end_skeleton_frame and track_shapes[-1]["frame"] < end_skeleton_frame: - shape = deepcopy(track_shapes[-1]) + while end_skeleton_frame and shape["frame"] < end_skeleton_frame: + shape = deepcopy(shape) shape["frame"] += 1 - track_shapes.append(shape) + track_shapes[shape["frame"]] = shape if len(track.get("elements", [])): element_tracks = TrackManager(track["elements"]) - element_shapes = element_tracks.to_shapes(end_frame, end_skeleton_frame=track_shapes[-1]["frame"]) + element_shapes = element_tracks.to_shapes(end_frame, + end_skeleton_frame=last_frame) - for i in range(len(element_shapes) // len(track_shapes)): - for track_shape, element_shape in zip(track_shapes, element_shapes[len(track_shapes) * i : len(track_shapes) * (i + 1)]): - track_shape["elements"].append(element_shape) + for shape in element_shapes: + track_shapes[shape["frame"]]["elements"].append(shape) - shapes.extend(track_shapes) + for frame, shape in list(track_shapes.items()): + if all([el["outside"] for el in shape["elements"]]): + track_shapes.pop(frame) + + shapes.extend(list(track_shapes.values())) return shapes @staticmethod From f703108802e5f38038cd0a67f78dc68d64b73621 Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Mon, 5 Dec 2022 21:45:30 +0200 Subject: [PATCH 05/10] Fix CVAT for video annotations --- cvat/apps/dataset_manager/formats/cvat.py | 355 ++++++++++++---------- 1 file changed, 194 insertions(+), 161 deletions(-) diff --git a/cvat/apps/dataset_manager/formats/cvat.py b/cvat/apps/dataset_manager/formats/cvat.py index dc3e8f00c337..9943f879cebe 100644 --- a/cvat/apps/dataset_manager/formats/cvat.py +++ b/cvat/apps/dataset_manager/formats/cvat.py @@ -137,7 +137,8 @@ def _parse(cls, path): 'label': el.attrib.get('label'), } else: - frame_size = tasks_info[int(el.attrib.get('task_id'))]['frame_size'] if el.attrib.get('task_id') else tuple(tasks_info.values())[0]['frame_size'] + frame_size = tasks_info[int(el.attrib.get('task_id'))]['frame_size'] \ + if el.attrib.get('task_id') else tuple(tasks_info.values())[0]['frame_size'] track = { 'id': el.attrib['id'], 'label': el.attrib.get('label'), @@ -877,6 +878,133 @@ def dump_labeled_shapes(shapes, is_skeleton=False): def dump_as_cvat_interpolation(dumper, annotations): dumper.open_root() dumper.add_meta(annotations.meta) + + def dump_shape(shape, element_shapes={}, label=None): + dump_data = OrderedDict() + if label is None: + dump_data.update(OrderedDict([ + ("frame", str(shape.frame)), + ])) + else: + dump_data.update(OrderedDict([ + ("label", label), + ])) + dump_data.update(OrderedDict([ + ("keyframe", str(int(shape.keyframe))), + ])) + + if shape.type != "skeleton": + dump_data.update(OrderedDict([ + ("outside", str(int(shape.outside))), + ("occluded", str(int(shape.occluded))), + ])) + + if shape.type == "rectangle": + dump_data.update(OrderedDict([ + ("xtl", "{:.2f}".format(shape.points[0])), + ("ytl", "{:.2f}".format(shape.points[1])), + ("xbr", "{:.2f}".format(shape.points[2])), + ("ybr", "{:.2f}".format(shape.points[3])), + ])) + + if shape.rotation: + dump_data.update(OrderedDict([ + ("rotation", "{:.2f}".format(shape.rotation)) + ])) + elif shape.type == "ellipse": + dump_data.update(OrderedDict([ + ("cx", "{:.2f}".format(shape.points[0])), + ("cy", "{:.2f}".format(shape.points[1])), + ("rx", "{:.2f}".format(shape.points[2] - shape.points[0])), + ("ry", "{:.2f}".format(shape.points[1] - shape.points[3])) + ])) + + if shape.rotation: + dump_data.update(OrderedDict([ + ("rotation", "{:.2f}".format(shape.rotation)) + ])) + elif shape.type == "mask": + dump_data.update(OrderedDict([ + ("rle", f"{list(int (v) for v in shape.points[:-4])}"[1:-1]), + ("left", f"{int(shape.points[-4])}"), + ("top", f"{int(shape.points[-3])}"), + ("width", f"{int(shape.points[-2] - shape.points[-4])}"), + ("height", f"{int(shape.points[-1] - shape.points[-3])}"), + ])) + elif shape.type == "cuboid": + dump_data.update(OrderedDict([ + ("xtl1", "{:.2f}".format(shape.points[0])), + ("ytl1", "{:.2f}".format(shape.points[1])), + ("xbl1", "{:.2f}".format(shape.points[2])), + ("ybl1", "{:.2f}".format(shape.points[3])), + ("xtr1", "{:.2f}".format(shape.points[4])), + ("ytr1", "{:.2f}".format(shape.points[5])), + ("xbr1", "{:.2f}".format(shape.points[6])), + ("ybr1", "{:.2f}".format(shape.points[7])), + ("xtl2", "{:.2f}".format(shape.points[8])), + ("ytl2", "{:.2f}".format(shape.points[9])), + ("xbl2", "{:.2f}".format(shape.points[10])), + ("ybl2", "{:.2f}".format(shape.points[11])), + ("xtr2", "{:.2f}".format(shape.points[12])), + ("ytr2", "{:.2f}".format(shape.points[13])), + ("xbr2", "{:.2f}".format(shape.points[14])), + ("ybr2", "{:.2f}".format(shape.points[15])) + ])) + elif shape.type != "skeleton": + dump_data.update(OrderedDict([ + ("points", ';'.join(['{:.2f},{:.2f}'.format(x, y) + for x,y in pairwise(shape.points)])) + ])) + + if label is None: + dump_data["z_order"] = str(shape.z_order) + + if shape.type == "rectangle": + dumper.open_box(dump_data) + elif shape.type == "ellipse": + dumper.open_ellipse(dump_data) + elif shape.type == "polygon": + dumper.open_polygon(dump_data) + elif shape.type == "polyline": + dumper.open_polyline(dump_data) + elif shape.type == "points": + dumper.open_points(dump_data) + elif shape.type == 'mask': + dumper.open_mask(dump_data) + elif shape.type == "cuboid": + dumper.open_cuboid(dump_data) + elif shape.type == 'skeleton': + dumper.open_skeleton(dump_data) + for element_shape, label in element_shapes.get(shape.frame, []): + dump_shape(element_shape, label=label) + else: + raise NotImplementedError("unknown shape type") + + for attr in shape.attributes: + dumper.add_attribute(OrderedDict([ + ("name", attr.name), + ("value", attr.value) + ])) + + if shape.type == "rectangle": + dumper.close_box() + elif shape.type == "ellipse": + dumper.close_ellipse() + elif shape.type == "polygon": + dumper.close_polygon() + elif shape.type == "polyline": + dumper.close_polyline() + elif shape.type == "points": + dumper.close_points() + elif shape.type == 'mask': + dumper.close_mask() + elif shape.type == "cuboid": + dumper.close_cuboid() + elif shape.type == "skeleton": + dumper.close_skeleton() + else: + raise NotImplementedError("unknown shape type") + def dump_track(idx, track): track_id = idx dump_data = OrderedDict([ @@ -896,119 +1024,16 @@ def dump_track(idx, track): dump_data['group_id'] = str(track.group) dumper.open_track(dump_data) - for shape in track.shapes: - dump_data = OrderedDict([ - ("frame", str(shape.frame)), - ("outside", str(int(shape.outside))), - ("occluded", str(int(shape.occluded))), - ("keyframe", str(int(shape.keyframe))), - ]) - - if shape.type == "rectangle": - dump_data.update(OrderedDict([ - ("xtl", "{:.2f}".format(shape.points[0])), - ("ytl", "{:.2f}".format(shape.points[1])), - ("xbr", "{:.2f}".format(shape.points[2])), - ("ybr", "{:.2f}".format(shape.points[3])), - ])) - - if shape.rotation: - dump_data.update(OrderedDict([ - ("rotation", "{:.2f}".format(shape.rotation)) - ])) - elif shape.type == "ellipse": - dump_data.update(OrderedDict([ - ("cx", "{:.2f}".format(shape.points[0])), - ("cy", "{:.2f}".format(shape.points[1])), - ("rx", "{:.2f}".format(shape.points[2] - shape.points[0])), - ("ry", "{:.2f}".format(shape.points[1] - shape.points[3])) - ])) - - if shape.rotation: - dump_data.update(OrderedDict([ - ("rotation", "{:.2f}".format(shape.rotation)) - ])) - elif shape.type == "mask": - dump_data.update(OrderedDict([ - ("rle", f"{list(int (v) for v in shape.points[:-4])}"[1:-1]), - ("left", f"{int(shape.points[-4])}"), - ("top", f"{int(shape.points[-3])}"), - ("width", f"{int(shape.points[-2] - shape.points[-4])}"), - ("height", f"{int(shape.points[-1] - shape.points[-3])}"), - ])) - elif shape.type == "cuboid": - dump_data.update(OrderedDict([ - ("xtl1", "{:.2f}".format(shape.points[0])), - ("ytl1", "{:.2f}".format(shape.points[1])), - ("xbl1", "{:.2f}".format(shape.points[2])), - ("ybl1", "{:.2f}".format(shape.points[3])), - ("xtr1", "{:.2f}".format(shape.points[4])), - ("ytr1", "{:.2f}".format(shape.points[5])), - ("xbr1", "{:.2f}".format(shape.points[6])), - ("ybr1", "{:.2f}".format(shape.points[7])), - ("xtl2", "{:.2f}".format(shape.points[8])), - ("ytl2", "{:.2f}".format(shape.points[9])), - ("xbl2", "{:.2f}".format(shape.points[10])), - ("ybl2", "{:.2f}".format(shape.points[11])), - ("xtr2", "{:.2f}".format(shape.points[12])), - ("ytr2", "{:.2f}".format(shape.points[13])), - ("xbr2", "{:.2f}".format(shape.points[14])), - ("ybr2", "{:.2f}".format(shape.points[15])) - ])) - else: - dump_data.update(OrderedDict([ - ("points", ';'.join(['{:.2f},{:.2f}'.format(x, y) - for x,y in pairwise(shape.points)])) - ])) - - dump_data["z_order"] = str(shape.z_order) - - if shape.type == "rectangle": - dumper.open_box(dump_data) - elif shape.type == "ellipse": - dumper.open_ellipse(dump_data) - elif shape.type == "polygon": - dumper.open_polygon(dump_data) - elif shape.type == "polyline": - dumper.open_polyline(dump_data) - elif shape.type == "points": - dumper.open_points(dump_data) - elif shape.type == 'mask': - dumper.open_mask(dump_data) - elif shape.type == "cuboid": - dumper.open_cuboid(dump_data) - elif shape.type == 'skeleton': - dumper.open_skeleton(dump_data) - else: - raise NotImplementedError("unknown shape type") + element_shapes = {} + for element_track in track.elements: + for element_shape in element_track.shapes: + if element_shape.frame not in element_shapes: + element_shapes[element_shape.frame] = [] + element_shapes[element_shape.frame].append((element_shape, element_track.label)) - for attr in shape.attributes: - dumper.add_attribute(OrderedDict([ - ("name", attr.name), - ("value", attr.value) - ])) - - if shape.type == "rectangle": - dumper.close_box() - elif shape.type == "ellipse": - dumper.close_ellipse() - elif shape.type == "polygon": - dumper.close_polygon() - elif shape.type == "polyline": - dumper.close_polyline() - elif shape.type == "points": - dumper.close_points() - elif shape.type == 'mask': - dumper.close_mask() - elif shape.type == "cuboid": - dumper.close_cuboid() - elif shape.type == "skeleton": - dumper.close_skeleton() - else: - raise NotImplementedError("unknown shape type") + for shape in track.shapes: + dump_shape(shape, element_shapes) - for i, element in enumerate(track.elements): - dump_track(i, element) dumper.close_track() counter = 0 @@ -1017,11 +1042,13 @@ def dump_track(idx, track): counter += 1 for shape in annotations.shapes: - frame_step = annotations.frame_step if not isinstance(annotations, ProjectData) else annotations.frame_step[shape.task_id] + frame_step = annotations.frame_step if not isinstance(annotations, ProjectData) \ + else annotations.frame_step[shape.task_id] if not isinstance(annotations, ProjectData): stop_frame = int(annotations.meta[annotations.META_FIELD]['stop_frame']) else: - task_meta = list(filter(lambda task: int(task[1]['id']) == shape.task_id, annotations.meta[annotations.META_FIELD]['tasks']))[0][1] + task_meta = list(filter(lambda task: int(task[1]['id']) == shape.task_id, + annotations.meta[annotations.META_FIELD]['tasks']))[0][1] stop_frame = int(task_meta['stop_frame']) track = { 'label': shape.label, @@ -1102,32 +1129,23 @@ def load_anno(file_object, annotations): next(context) track = None - track_element=None shape = None shape_element=None tag = None image_is_opened = False attributes = None elem_attributes = None + element_tracks = None for ev, el in context: if ev == 'start': if el.tag == 'track': - if track: - track_element = annotations.Track( - label=el.attrib['label'], - group=int(el.attrib.get('group_id', 0)), - source=el.attrib.get('source', 'manual'), - shapes=[], - elements=[], - ) - else: - track = annotations.Track( - label=el.attrib['label'], - group=int(el.attrib.get('group_id', 0)), - source=el.attrib.get('source', 'manual'), - shapes=[], - elements=[], - ) + track = annotations.Track( + label=el.attrib['label'], + group=int(el.attrib.get('group_id', 0)), + source=el.attrib.get('source', 'manual'), + shapes=[], + elements=[], + ) elif el.tag == 'image': image_is_opened = True frame_id = annotations.abs_frame_id(match_dm_item( @@ -1145,6 +1163,14 @@ def load_anno(file_object, annotations): 'points': [], 'type': 'rectangle' if el.tag == 'box' else el.tag } + if track is not None and el.attrib['label'] not in element_tracks: + element_tracks[el.attrib['label']] = annotations.Track( + label=el.attrib['label'], + group=0, + source=el.attrib.get('source', 'manual'), + shapes=[], + elements=[], + ) else: attributes = [] shape = { @@ -1154,6 +1180,10 @@ def load_anno(file_object, annotations): } if track is None: shape['elements'] = [] + elif shape['type'] == 'skeleton': + shape['frame'] = el.attrib['frame'] + if element_tracks is None: + element_tracks = {} elif el.tag == 'tag' and image_is_opened: attributes = [] tag = { @@ -1213,15 +1243,21 @@ def load_anno(file_object, annotations): for pair in el.attrib['points'].split(';'): shape_element['points'].extend(map(float, pair.split(','))) - shape_element['frame'] = frame_id - shape_element['source'] = str(el.attrib.get('source', 'manual')) - shape['elements'].append(annotations.LabeledShape(**shape_element)) + if track is None: + shape_element['frame'] = frame_id + shape_element['source'] = str(el.attrib.get('source', 'manual')) + shape['elements'].append(annotations.LabeledShape(**shape_element)) + else: + shape_element["frame"] = shape['frame'] + shape_element['keyframe'] = el.attrib['keyframe'] == "1" + if shape_element['keyframe']: + element_tracks[el.attrib['label']].shapes.append(annotations.TrackedShape(**shape_element)) shape_element = None elif el.tag in supported_shapes: if track is not None: shape['frame'] = el.attrib['frame'] - shape['outside'] = el.attrib['outside'] == "1" + shape['outside'] = el.attrib.get('outside', "0") == "1" shape['keyframe'] = el.attrib['keyframe'] == "1" else: shape['frame'] = frame_id @@ -1230,7 +1266,7 @@ def load_anno(file_object, annotations): shape['source'] = str(el.attrib.get('source', 'manual')) shape['outside'] = False - shape['occluded'] = el.attrib['occluded'] == '1' + shape['occluded'] = el.attrib.get('occluded', "0") == '1' shape['z_order'] = int(el.attrib.get('z_order', 0)) shape['rotation'] = float(el.attrib.get('rotation', 0)) @@ -1273,10 +1309,8 @@ def load_anno(file_object, annotations): else: for pair in el.attrib['points'].split(';'): shape['points'].extend(map(float, pair.split(','))) - if track_element is not None: - if shape['keyframe']: - track_element.shapes.append(annotations.TrackedShape(**shape)) - elif track is not None: + + if track is not None: if shape['keyframe']: track.shapes.append(annotations.TrackedShape(**shape)) else: @@ -1284,28 +1318,27 @@ def load_anno(file_object, annotations): shape = None elif el.tag == 'track': - if track_element: - track.elements.append(track_element) - track_element = None + if track.shapes[0].type == 'mask': + # convert mask tracks to shapes + # because mask track are not supported + annotations.add_shape(annotations.LabeledShape(**{ + 'attributes': track.shapes[0].attributes, + 'points': track.shapes[0].points, + 'type': track.shapes[0].type, + 'occluded': track.shapes[0].occluded, + 'frame': track.shapes[0].frame, + 'source': track.shapes[0].source, + 'rotation': track.shapes[0].rotation, + 'z_order': track.shapes[0].z_order, + 'group': track.shapes[0].group, + 'label': track.label, + })) else: - if track.shapes[0].type == 'mask': - # convert mask tracks to shapes - # because mask track are not supported - annotations.add_shape(annotations.LabeledShape(**{ - 'attributes': track.shapes[0].attributes, - 'points': track.shapes[0].points, - 'type': track.shapes[0].type, - 'occluded': track.shapes[0].occluded, - 'frame': track.shapes[0].frame, - 'source': track.shapes[0].source, - 'rotation': track.shapes[0].rotation, - 'z_order': track.shapes[0].z_order, - 'group': track.shapes[0].group, - 'label': track.label, - })) - else: - annotations.add_track(track) - track = None + if element_tracks is not None: + for element in element_tracks.values(): + track.elements.append(element) + annotations.add_track(track) + track = None elif el.tag == 'image': image_is_opened = False elif el.tag == 'tag': From b1ed77df34cdc068a5f974d8b97f069938eccb87 Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Thu, 8 Dec 2022 16:14:05 +0200 Subject: [PATCH 06/10] Add fixed for projects --- cvat/apps/dataset_manager/annotation.py | 4 +- cvat/apps/dataset_manager/bindings.py | 4 +- cvat/apps/dataset_manager/formats/cvat.py | 128 +++++++++++----------- 3 files changed, 70 insertions(+), 66 deletions(-) diff --git a/cvat/apps/dataset_manager/annotation.py b/cvat/apps/dataset_manager/annotation.py index 44a4b8b31a7a..2c1acabf5053 100644 --- a/cvat/apps/dataset_manager/annotation.py +++ b/cvat/apps/dataset_manager/annotation.py @@ -376,8 +376,8 @@ def to_shapes(self, end_frame, end_skeleton_frame=None): track_shapes[shape["frame"]] = shape if len(track.get("elements", [])): - element_tracks = TrackManager(track["elements"]) - element_shapes = element_tracks.to_shapes(end_frame, + track_elements = TrackManager(track["elements"]) + element_shapes = track_elements.to_shapes(end_frame, end_skeleton_frame=last_frame) for shape in element_shapes: diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index c417a7c09100..c73f38e34d81 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -1827,7 +1827,8 @@ def reduce_fn(acc, v): if ann.type == dm.AnnotationType.skeleton: for element in ann.elements: element_keyframe = dm.util.cast(element.attributes.get('keyframe', None), bool) is True - element_outside = dm.util.cast(element.attributes.pop('outside', None), bool) is True + element_occluded = element.visibility[0] == dm.Points.Visibility.hidden + element_outside = element.visibility[0] == dm.Points.Visibility.absent if not element_keyframe and not element_outside: continue @@ -1842,7 +1843,6 @@ def reduce_fn(acc, v): instance_data.Attribute(name=n, value=str(v)) for n, v in element.attributes.items() ] - element_occluded = dm.util.cast(element.attributes.pop('occluded', None), bool) is True element_source = element.attributes.pop('source').lower() \ if element.attributes.get('source', '').lower() in {'auto', 'manual'} else 'manual' tracks[track_id]['elements'][element.label].shapes.append(instance_data.TrackedShape( diff --git a/cvat/apps/dataset_manager/formats/cvat.py b/cvat/apps/dataset_manager/formats/cvat.py index 9943f879cebe..a11ea14cd50d 100644 --- a/cvat/apps/dataset_manager/formats/cvat.py +++ b/cvat/apps/dataset_manager/formats/cvat.py @@ -119,8 +119,8 @@ def _parse(cls, path): items = OrderedDict() track = None - track_element = None track_shapes = None + track_elements = None shape = None shape_element = None tag = None @@ -131,23 +131,17 @@ def _parse(cls, path): for ev, el in context: if ev == 'start': if el.tag == 'track': - if track: - track_element = { - 'id': el.attrib['id'], - 'label': el.attrib.get('label'), - } - else: - frame_size = tasks_info[int(el.attrib.get('task_id'))]['frame_size'] \ - if el.attrib.get('task_id') else tuple(tasks_info.values())[0]['frame_size'] - track = { - 'id': el.attrib['id'], - 'label': el.attrib.get('label'), - 'group': int(el.attrib.get('group_id', 0)), - 'height': frame_size[0], - 'width': frame_size[1], - } - subset = el.attrib.get('subset') - track_shapes = {} + frame_size = tasks_info[int(el.attrib.get('task_id'))]['frame_size'] \ + if el.attrib.get('task_id') else tuple(tasks_info.values())[0]['frame_size'] + track = { + 'id': el.attrib['id'], + 'label': el.attrib.get('label'), + 'group': int(el.attrib.get('group_id', 0)), + 'height': frame_size[0], + 'width': frame_size[1], + } + subset = el.attrib.get('subset') + track_shapes = {} elif el.tag == 'image': image = { 'name': el.attrib.get('name'), @@ -163,7 +157,10 @@ def _parse(cls, path): 'type': 'rectangle' if el.tag == 'box' else el.tag, 'attributes': element_attributes, } - shape_element.update(image) + if track: + shape_element.update(track) + else: + shape_element.update(image) else: attributes = {} shape = { @@ -171,12 +168,11 @@ def _parse(cls, path): 'attributes': attributes, } shape['elements'] = [] - if track_element: - shape.update(track_element) - shape['track_id'] = int(track_element['id']) - elif track: + if track: shape.update(track) shape['track_id'] = int(track['id']) + shape['frame'] = el.attrib['frame'] + track_elements = [] if image: shape.update(image) elif el.tag == 'tag' and image: @@ -240,7 +236,12 @@ def _parse(cls, path): else: shape_element['outside'] = (el.attrib.get('outside') == '1') - shape['elements'].append(shape_element) + if track: + shape_element['keyframe'] = (el.attrib.get('keyframe') == '1') + if shape_element['keyframe']: + track_elements.append(shape_element) + else: + shape['elements'].append(shape_element) shape_element = None elif el.tag in cls._SUPPORTED_SHAPES: @@ -267,10 +268,15 @@ def _parse(cls, path): shape['points'] = [] for pair in el.attrib['points'].split(';'): shape['points'].extend(map(float, pair.split(','))) - if track_element: - track_shapes[shape['frame']]['elements'].append(shape) - elif track: - track_shapes[shape['frame']] = shape + + if track: + if shape["type"] == "skeleton" and track_elements: + shape["keyframe"] = True + track_shapes[shape['frame']] = shape + track_shapes[shape['frame']]['elements'] = track_elements + track_elements = None + elif shape["type"] != "skeleton": + track_shapes[shape['frame']] = shape else: frame_desc = items.get((subset, shape['frame']), {'annotations': []}) frame_desc['annotations'].append( @@ -286,15 +292,12 @@ def _parse(cls, path): items[(subset, tag['frame'])] = frame_desc tag = None elif el.tag == 'track': - if track_element: - track_element = None - else: - for track_shape in track_shapes.values(): - frame_desc = items.get((subset, track_shape['frame']), {'annotations': []}) - frame_desc['annotations'].append( - cls._parse_shape_ann(track_shape, categories)) - items[(subset, track_shape['frame'])] = frame_desc - track = None + for track_shape in track_shapes.values(): + frame_desc = items.get((subset, track_shape['frame']), {'annotations': []}) + frame_desc['annotations'].append( + cls._parse_shape_ann(track_shape, categories)) + items[(subset, track_shape['frame'])] = frame_desc + track = None elif el.tag == 'image': frame_desc = items.get((subset, image['frame']), {'annotations': []}) frame_desc.update({ @@ -781,11 +784,6 @@ def dump_labeled_shapes(shapes, is_skeleton=False): ("xbr2", "{:.2f}".format(shape.points[14])), ("ybr2", "{:.2f}".format(shape.points[15])) ])) - elif shape.type == 'skeleton': - dump_data.update(OrderedDict([ - ("points", ''), - ("rotation", "{:.2f}".format(shape.rotation)) - ])) elif shape.type == "mask": dump_data.update(OrderedDict([ ("rle", f"{list(int (v) for v in shape.points[:-4])}"[1:-1]), @@ -794,7 +792,7 @@ def dump_labeled_shapes(shapes, is_skeleton=False): ("width", f"{int(shape.points[-2] - shape.points[-4])}"), ("height", f"{int(shape.points[-1] - shape.points[-3])}"), ])) - else: + elif shape.type != 'skeleton': dump_data.update(OrderedDict([ ("points", ';'.join(( ','.join(( @@ -804,7 +802,8 @@ def dump_labeled_shapes(shapes, is_skeleton=False): )), ])) - dump_data['z_order'] = str(shape.z_order) + if not is_skeleton: + dump_data['z_order'] = str(shape.z_order) if shape.group: dump_data['group_id'] = str(shape.group) @@ -879,7 +878,7 @@ def dump_as_cvat_interpolation(dumper, annotations): dumper.open_root() dumper.add_meta(annotations.meta) - def dump_shape(shape, element_shapes={}, label=None): + def dump_shape(shape, element_shapes=None, label=None): dump_data = OrderedDict() if label is None: dump_data.update(OrderedDict([ @@ -974,17 +973,20 @@ def dump_shape(shape, element_shapes={}, label=None): elif shape.type == "cuboid": dumper.open_cuboid(dump_data) elif shape.type == 'skeleton': - dumper.open_skeleton(dump_data) - for element_shape, label in element_shapes.get(shape.frame, []): - dump_shape(element_shape, label=label) + if element_shapes and element_shapes.get(shape.frame): + dumper.open_skeleton(dump_data) + for element_shape, label in element_shapes.get(shape.frame, []): + dump_shape(element_shape, label=label) else: raise NotImplementedError("unknown shape type") - for attr in shape.attributes: - dumper.add_attribute(OrderedDict([ - ("name", attr.name), - ("value", attr.value) - ])) + if shape.type == "skeleton" and element_shapes \ + and element_shapes.get(shape.frame) or shape.type != "skeleton": + for attr in shape.attributes: + dumper.add_attribute(OrderedDict([ + ("name", attr.name), + ("value", attr.value) + ])) if shape.type == "rectangle": dumper.close_box() @@ -1001,7 +1003,8 @@ def dump_shape(shape, element_shapes={}, label=None): elif shape.type == "cuboid": dumper.close_cuboid() elif shape.type == "skeleton": - dumper.close_skeleton() + if element_shapes and element_shapes.get(shape.frame): + dumper.close_skeleton() else: raise NotImplementedError("unknown shape type") @@ -1135,7 +1138,7 @@ def load_anno(file_object, annotations): image_is_opened = False attributes = None elem_attributes = None - element_tracks = None + track_elements = None for ev, el in context: if ev == 'start': if el.tag == 'track': @@ -1163,8 +1166,8 @@ def load_anno(file_object, annotations): 'points': [], 'type': 'rectangle' if el.tag == 'box' else el.tag } - if track is not None and el.attrib['label'] not in element_tracks: - element_tracks[el.attrib['label']] = annotations.Track( + if track is not None and el.attrib['label'] not in track_elements: + track_elements[el.attrib['label']] = annotations.Track( label=el.attrib['label'], group=0, source=el.attrib.get('source', 'manual'), @@ -1182,8 +1185,8 @@ def load_anno(file_object, annotations): shape['elements'] = [] elif shape['type'] == 'skeleton': shape['frame'] = el.attrib['frame'] - if element_tracks is None: - element_tracks = {} + if track_elements is None: + track_elements = {} elif el.tag == 'tag' and image_is_opened: attributes = [] tag = { @@ -1251,7 +1254,7 @@ def load_anno(file_object, annotations): shape_element["frame"] = shape['frame'] shape_element['keyframe'] = el.attrib['keyframe'] == "1" if shape_element['keyframe']: - element_tracks[el.attrib['label']].shapes.append(annotations.TrackedShape(**shape_element)) + track_elements[el.attrib['label']].shapes.append(annotations.TrackedShape(**shape_element)) shape_element = None elif el.tag in supported_shapes: @@ -1334,9 +1337,10 @@ def load_anno(file_object, annotations): 'label': track.label, })) else: - if element_tracks is not None: - for element in element_tracks.values(): + if track_elements is not None: + for element in track_elements.values(): track.elements.append(element) + track_elements = None annotations.add_track(track) track = None elif el.tag == 'image': From e6e29ff2f03873716c045e8823f2ee562522f858 Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Tue, 13 Dec 2022 13:31:35 +0200 Subject: [PATCH 07/10] Update documentation --- .../en/docs/manual/advanced/xml_format.md | 85 +++++++++---------- 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/site/content/en/docs/manual/advanced/xml_format.md b/site/content/en/docs/manual/advanced/xml_format.md index 97a7ddd04660..9c4cf87bd0cc 100644 --- a/site/content/en/docs/manual/advanced/xml_format.md +++ b/site/content/en/docs/manual/advanced/xml_format.md @@ -121,7 +121,7 @@ In later releases `polygon`, `polyline`, `points`, `skeletons` and `tags` were a ... - + String: the attribute value ... @@ -229,12 +229,12 @@ Example: - - + + - + - + @@ -270,14 +270,11 @@ cloned for each location (a known redundancy). ... - - - ... - - + + ... - + ... ... @@ -375,60 +372,54 @@ Example: - - - - - - - - - - - - - - + + - + - + - + + + - + - + - - - + + + - + - + - + + + - + - + - - - + + + - + - + - + + + - + - + - + ``` From 1e986c0ef4a1ccced5493a0b0a84820d9fe09594 Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Wed, 8 Feb 2023 14:18:29 +0200 Subject: [PATCH 08/10] Remove occluded field from skeleton annotation --- cvat/apps/dataset_manager/formats/cvat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat/apps/dataset_manager/formats/cvat.py b/cvat/apps/dataset_manager/formats/cvat.py index 2814780a5a7d..12cfcaceff6f 100644 --- a/cvat/apps/dataset_manager/formats/cvat.py +++ b/cvat/apps/dataset_manager/formats/cvat.py @@ -736,7 +736,6 @@ def dump_labeled_shapes(shapes, is_skeleton=False): for shape in shapes: dump_data = OrderedDict([ ("label", shape.label), - ("occluded", str(int(shape.occluded))), ("source", shape.source), ]) if is_skeleton: @@ -797,6 +796,7 @@ def dump_labeled_shapes(shapes, is_skeleton=False): ])) elif shape.type != 'skeleton': dump_data.update(OrderedDict([ + ("occluded", str(int(shape.occluded))), ("points", ';'.join(( ','.join(( "{:.2f}".format(x), From 45fa19b904d11af57927fb90ae3dcf8d3ff7c940 Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Wed, 8 Feb 2023 14:26:25 +0200 Subject: [PATCH 09/10] Fix documentation --- site/content/en/docs/manual/advanced/xml_format.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/content/en/docs/manual/advanced/xml_format.md b/site/content/en/docs/manual/advanced/xml_format.md index 9c4cf87bd0cc..8efab34c1828 100644 --- a/site/content/en/docs/manual/advanced/xml_format.md +++ b/site/content/en/docs/manual/advanced/xml_format.md @@ -120,7 +120,7 @@ In later releases `polygon`, `polyline`, `points`, `skeletons` and `tags` were a String: the attribute value ... - + String: the attribute value @@ -229,7 +229,7 @@ Example: - + From 7b3ff12d511b19a594c7444ba5d461c554e5f41a Mon Sep 17 00:00:00 2001 From: yasakova-anastasia Date: Tue, 14 Feb 2023 09:23:36 +0200 Subject: [PATCH 10/10] Fix an issue with occluded field --- cvat/apps/dataset_manager/formats/cvat.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cvat/apps/dataset_manager/formats/cvat.py b/cvat/apps/dataset_manager/formats/cvat.py index d414a0349243..854659774bb8 100644 --- a/cvat/apps/dataset_manager/formats/cvat.py +++ b/cvat/apps/dataset_manager/formats/cvat.py @@ -742,6 +742,11 @@ def dump_labeled_shapes(shapes, is_skeleton=False): ("outside", str(int(shape.outside))) ])) + if shape.type != 'skeleton': + dump_data.update(OrderedDict([ + ("occluded", str(int(shape.occluded))) + ])) + if shape.type == "rectangle": dump_data.update(OrderedDict([ ("xtl", "{:.2f}".format(shape.points[0])), @@ -795,7 +800,6 @@ def dump_labeled_shapes(shapes, is_skeleton=False): ])) elif shape.type != 'skeleton': dump_data.update(OrderedDict([ - ("occluded", str(int(shape.occluded))), ("points", ';'.join(( ','.join(( "{:.2f}".format(x),