diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ec4d8605ea2..66844e5854d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -152,6 +152,7 @@ non-ascii paths while adding files from "Connected file share" (issue #4428) - Added force logout on CVAT app start if token is missing () - Drawing issues on 3D canvas () - Missed token with using social account authentication () +- Redundant writing of skeleton annotations (CVAT for images) () - The same object on 3D scene or `null` selected each click (PERFORMANCE) () - An exception when run export for an empty task () - Fixed FBRS serverless function runtime error on images with alpha channel () diff --git a/cvat/apps/dataset_manager/annotation.py b/cvat/apps/dataset_manager/annotation.py index 6d99a2711467..fe56c2bd9e92 100644 --- a/cvat/apps/dataset_manager/annotation.py +++ b/cvat/apps/dataset_manager/annotation.py @@ -411,29 +411,34 @@ def __init__(self, objects, dimension): 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, self._dimension): 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"], self._dimension) - element_shapes = element_tracks.to_shapes(end_frame, end_skeleton_frame=track_shapes[-1]["frame"]) + track_elements = TrackManager(track["elements"], self._dimension) + element_shapes = track_elements.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 diff --git a/cvat/apps/dataset_manager/bindings.py b/cvat/apps/dataset_manager/bindings.py index 090e18bee53a..50a885a8a159 100644 --- a/cvat/apps/dataset_manager/bindings.py +++ b/cvat/apps/dataset_manager/bindings.py @@ -1823,7 +1823,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 @@ -1838,7 +1839,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 681b2d7149d7..854659774bb8 100644 --- a/cvat/apps/dataset_manager/formats/cvat.py +++ b/cvat/apps/dataset_manager/formats/cvat.py @@ -118,8 +118,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 @@ -130,22 +130,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'), @@ -161,7 +156,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 = { @@ -169,12 +167,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: @@ -238,7 +235,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: @@ -266,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( @@ -285,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({ @@ -731,7 +735,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: @@ -739,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])), @@ -782,11 +790,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]), @@ -795,7 +798,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(( @@ -805,7 +808,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,138 +883,166 @@ 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_track(idx, track): - track_id = idx - dump_data = OrderedDict([ - ("id", str(track_id)), - ("label", track.label), - ("source", track.source), - ]) - if hasattr(track, 'task_id'): - task, = filter(lambda task: task.id == track.task_id, annotations.tasks) + def dump_shape(shape, element_shapes=None, label=None): + dump_data = OrderedDict() + if label is None: dump_data.update(OrderedDict([ - ('task_id', str(track.task_id)), - ('subset', get_defaulted_subset(task.subset, annotations.subsets)), + ("frame", str(shape.frame)), ])) + else: + dump_data.update(OrderedDict([ + ("label", label), + ])) + dump_data.update(OrderedDict([ + ("keyframe", str(int(shape.keyframe))), + ])) - if track.group: - dump_data['group_id'] = str(track.group) - dumper.open_track(dump_data) - - for shape in track.shapes: - dump_data = OrderedDict([ - ("frame", str(shape.frame)), + if shape.type != "skeleton": + dump_data.update(OrderedDict([ ("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.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": + if shape.rotation: 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])) + ("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": + if shape.rotation: 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)])) + ("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': + 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': + if element_shapes and element_shapes.get(shape.frame): dumper.open_skeleton(dump_data) - else: - raise NotImplementedError("unknown shape type") + for element_shape, label in element_shapes.get(shape.frame, []): + dump_shape(element_shape, label=label) + else: + raise NotImplementedError("unknown shape type") + 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() - 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": + 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": + if element_shapes and element_shapes.get(shape.frame): dumper.close_skeleton() - else: - raise NotImplementedError("unknown shape type") + else: + raise NotImplementedError("unknown shape type") + + def dump_track(idx, track): + track_id = idx + dump_data = OrderedDict([ + ("id", str(track_id)), + ("label", track.label), + ("source", track.source), + ]) + + if hasattr(track, 'task_id'): + task, = filter(lambda task: task.id == track.task_id, annotations.tasks) + dump_data.update(OrderedDict([ + ('task_id', str(track.task_id)), + ('subset', get_defaulted_subset(task.subset, annotations.subsets)), + ])) + + if track.group: + dump_data['group_id'] = str(track.group) + dumper.open_track(dump_data) + + 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 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 @@ -1019,11 +1051,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, @@ -1104,32 +1138,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 + track_elements = 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( @@ -1147,6 +1172,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 track_elements: + track_elements[el.attrib['label']] = annotations.Track( + label=el.attrib['label'], + group=0, + source=el.attrib.get('source', 'manual'), + shapes=[], + elements=[], + ) else: attributes = [] shape = { @@ -1156,6 +1189,10 @@ def load_anno(file_object, annotations): } if track is None: shape['elements'] = [] + elif shape['type'] == 'skeleton': + shape['frame'] = el.attrib['frame'] + if track_elements is None: + track_elements = {} elif el.tag == 'tag' and image_is_opened: attributes = [] tag = { @@ -1215,15 +1252,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']: + track_elements[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 @@ -1232,7 +1275,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)) @@ -1275,10 +1318,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: @@ -1286,28 +1327,28 @@ 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 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': image_is_opened = False elif el.tag == 'tag': diff --git a/site/content/en/docs/manual/advanced/xml_format.md b/site/content/en/docs/manual/advanced/xml_format.md index 97a7ddd04660..8efab34c1828 100644 --- a/site/content/en/docs/manual/advanced/xml_format.md +++ b/site/content/en/docs/manual/advanced/xml_format.md @@ -120,8 +120,8 @@ In later releases `polygon`, `polyline`, `points`, `skeletons` and `tags` were a String: the attribute value ... - - + + 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: - - - - - - - - - - - - - - + + - + - + - + + + - + - + - - - + + + - + - + - + + + - + - + - - - + + + - + - + - + + + - + - + - + ```