Fix project import with skeletons (#4867)

main
Anastasia Yasakova 3 years ago committed by GitHub
parent 9e67bcb53c
commit fd666d00ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
non-ascii paths while adding files from "Connected file share" (issue #4428)
- Removed unnecessary volumes defined in docker-compose.serverless.yml
(<https://github.com/openvinotoolkit/cvat/pull/4659>)
- Project import with skeletons (<https://github.com/opencv/cvat/pull/4867>)
### Security
- TDB

@ -524,9 +524,7 @@ class TaskData(InstanceLabelData):
]
shape['attributes'] = [self._import_attribute(label_id, attrib, mutable=True)
for attrib in shape['attributes']
if self._get_mutable_attribute_id(label_id, attrib.name) or (
self.soft_attribute_import and attrib.name not in CVAT_INTERNAL_ATTRIBUTES
)
if self._get_mutable_attribute_id(label_id, attrib.name)
]
shape['points'] = list(map(float, shape['points']))
@ -1101,9 +1099,9 @@ class CVATDataExtractorMixin:
def _read_cvat_anno(self, cvat_frame_anno: Union[ProjectData.Frame, TaskData.Frame], labels: list):
categories = self.categories()
label_cat = categories[dm.AnnotationType.label]
def map_label(name): return label_cat.find(name)[0]
def map_label(name, parent=''): return label_cat.find(name, parent)[0]
label_attrs = {
label['name']: label['attributes']
label.get('parent', '') + label['name']: label['attributes']
for _, label in labels
}
@ -1198,9 +1196,9 @@ class CvatTaskDataExtractor(dm.SourceExtractor, CVATDataExtractorMixin):
def _read_cvat_anno(self, cvat_frame_anno: TaskData.Frame, labels: list):
categories = self.categories()
label_cat = categories[dm.AnnotationType.label]
def map_label(name, parent=""): return label_cat.find(name, parent)[0]
def map_label(name, parent=''): return label_cat.find(name, parent)[0]
label_attrs = {
label.get("parent", "") + label['name']: label['attributes']
label.get('parent', '') + label['name']: label['attributes']
for _, label in labels
}
@ -1562,6 +1560,7 @@ def import_dm_annotations(dm_dataset: dm.Dataset, instance_data: Union[TaskData,
for n, v in ann.attributes.items()
]
points = []
if ann.type in shapes:
points = []
if ann.type == dm.AnnotationType.cuboid_3d:
@ -1647,6 +1646,11 @@ def import_dm_annotations(dm_dataset: dm.Dataset, instance_data: Union[TaskData,
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
if not element_keyframe and not element_outside:
continue
if element.label not in tracks[track_id]['elements']:
tracks[track_id]['elements'][element.label] = instance_data.Track(
label=label_cat.items[element.label].name,
@ -1659,7 +1663,6 @@ def import_dm_annotations(dm_dataset: dm.Dataset, instance_data: Union[TaskData,
for n, v in element.attributes.items()
]
element_occluded = dm.util.cast(element.attributes.pop('occluded', None), bool) is True
element_outside = dm.util.cast(element.attributes.pop('outside', 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(

@ -14,7 +14,7 @@ from typing import Callable
from datumaro.components.annotation import (AnnotationType, Bbox, Label,
LabelCategories, Points, Polygon,
PolyLine)
PolyLine, Skeleton)
from datumaro.components.dataset import Dataset, DatasetItem
from datumaro.components.extractor import (DEFAULT_SUBSET_NAME, Extractor,
Importer)
@ -119,23 +119,34 @@ class CvatExtractor(Extractor):
items = OrderedDict()
track = None
track_element = None
track_shapes = None
shape = None
shape_element = None
tag = None
attributes = None
element_attributes = None
image = None
subset = None
for ev, el in context:
if ev == 'start':
if el.tag == 'track':
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')
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 = {}
elif el.tag == 'image':
image = {
'name': el.attrib.get('name'),
@ -145,16 +156,28 @@ class CvatExtractor(Extractor):
}
subset = el.attrib.get('subset')
elif el.tag in cls._SUPPORTED_SHAPES and (track or image):
attributes = {}
shape = {
'type': None,
'attributes': attributes,
}
if track:
shape.update(track)
shape['track_id'] = int(track['id'])
if image:
shape.update(image)
if shape and shape['type'] == 'skeleton':
element_attributes = {}
shape_element = {
'type': 'rectangle' if el.tag == 'box' else el.tag,
'attributes': element_attributes,
}
shape_element.update(image)
else:
attributes = {}
shape = {
'type': 'rectangle' if el.tag == 'box' else el.tag,
'attributes': attributes,
}
shape['elements'] = []
if track_element:
shape.update(track_element)
shape['track_id'] = int(track_element['id'])
elif track:
shape.update(track)
shape['track_id'] = int(track['id'])
if image:
shape.update(image)
elif el.tag == 'tag' and image:
attributes = {}
tag = {
@ -165,7 +188,19 @@ class CvatExtractor(Extractor):
}
subset = el.attrib.get('subset')
elif ev == 'end':
if el.tag == 'attribute' and attributes is not None:
if el.tag == 'attribute' and element_attributes is not None and shape_element is not None:
attr_value = el.text or ''
attr_type = attribute_types.get(el.attrib['name'])
if el.text in ['true', 'false']:
attr_value = attr_value == 'true'
elif attr_type is not None and attr_type != 'text':
try:
attr_value = float(attr_value)
except ValueError:
pass
element_attributes[el.attrib['name']] = attr_value
if el.tag == 'attribute' and attributes is not None and shape_element is None:
attr_value = el.text or ''
attr_type = attribute_types.get(el.attrib['name'])
if el.text in ['true', 'false']:
@ -176,6 +211,37 @@ class CvatExtractor(Extractor):
except ValueError:
pass
attributes[el.attrib['name']] = attr_value
elif el.tag in cls._SUPPORTED_SHAPES and shape["type"] == "skeleton" and el.tag != "skeleton":
shape_element['label'] = el.attrib.get('label')
shape_element['group'] = int(el.attrib.get('group_id', 0))
shape_element['type'] = el.tag
shape_element['z_order'] = int(el.attrib.get('z_order', 0))
if el.tag == 'box':
shape_element['points'] = list(map(float, [
el.attrib['xtl'], el.attrib['ytl'],
el.attrib['xbr'], el.attrib['ybr'],
]))
else:
shape_element['points'] = []
for pair in el.attrib['points'].split(';'):
shape_element['points'].extend(map(float, pair.split(',')))
if el.tag == 'points' and el.attrib.get('occluded') == '1':
shape_element['visibility'] = [Points.Visibility.hidden] * (len(shape_element['points']) // 2)
else:
shape_element['occluded'] = (el.attrib.get('occluded') == '1')
if el.tag == 'points' and el.attrib.get('outside') == '1':
shape_element['visibility'] = [Points.Visibility.absent] * (len(shape_element['points']) // 2)
else:
shape_element['outside'] = (el.attrib.get('outside') == '1')
shape['elements'].append(shape_element)
shape_element = None
elif el.tag in cls._SUPPORTED_SHAPES:
if track is not None:
shape['frame'] = el.attrib['frame']
@ -194,15 +260,22 @@ class CvatExtractor(Extractor):
el.attrib['xtl'], el.attrib['ytl'],
el.attrib['xbr'], el.attrib['ybr'],
]))
elif el.tag == 'skeleton':
shape['points'] = []
else:
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
else:
frame_desc = items.get((subset, shape['frame']), {'annotations': []})
frame_desc['annotations'].append(
cls._parse_shape_ann(shape, categories))
items[(subset, shape['frame'])] = frame_desc
frame_desc = items.get((subset, shape['frame']), {'annotations': []})
frame_desc['annotations'].append(
cls._parse_shape_ann(shape, categories))
items[(subset, shape['frame'])] = frame_desc
shape = None
elif el.tag == 'tag':
@ -212,7 +285,15 @@ class CvatExtractor(Extractor):
items[(subset, tag['frame'])] = frame_desc
tag = None
elif el.tag == 'track':
track = None
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
elif el.tag == 'image':
frame_desc = items.get((subset, image['frame']), {'annotations': []})
frame_desc.update({
@ -377,7 +458,8 @@ class CvatExtractor(Extractor):
id=ann_id, attributes=attributes, group=group)
elif ann_type == 'points':
return Points(points, label=label_id, z_order=z_order,
visibility = ann.get('visibility', None)
return Points(points, visibility, label=label_id, z_order=z_order,
id=ann_id, attributes=attributes, group=group)
elif ann_type == 'box':
@ -386,6 +468,14 @@ class CvatExtractor(Extractor):
return Bbox(x, y, w, h, label=label_id, z_order=z_order,
id=ann_id, attributes=attributes, group=group)
elif ann_type == 'skeleton':
elements = []
for element in ann.get('elements', []):
elements.append(cls._parse_shape_ann(element, categories))
return Skeleton(elements, label=label_id, z_order=z_order,
id=ann_id, attributes=attributes, group=group)
else:
raise NotImplementedError("Unknown annotation type '%s'" % ann_type)
@ -963,7 +1053,10 @@ def dump_as_cvat_interpolation(dumper, annotations):
elements=[],
) for element in shape.elements]
}
if isinstance(annotations, ProjectData): track['task_id'] = shape.task_id
if isinstance(annotations, ProjectData):
track['task_id'] = shape.task_id
for element in track['elements']:
element.task_id = shape.task_id
dump_track(counter, annotations.Track(**track))
counter += 1

@ -708,43 +708,6 @@
],
"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": [],
@ -1085,7 +1048,6 @@
"points": [66.45, 147.08, 182.16, 204.56],
"frame": 0,
"outside": false,
"outside": true,
"attributes": []
},
{
@ -1215,50 +1177,6 @@
}
]
},
"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": 0,
"outside": false,
"attributes": []
},
{
"type": "polygon",
"occluded": false,
"z_order": 0,
"points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08],
"frame": 1,
"outside": true,
"attributes": []
},
{
"type": "polygon",
"occluded": false,
"z_order": 0,
"points": [24.62, 13.01, 34.88, 20.03, 18.14, 18.08],
"frame": 2,
"outside": false,
"keyframe": true,
"attributes": []
}
],
"attributes": []
}
]
},
"CVAT for video 1.1 attributes in tracks": {
"version": 0,
"tags": [],

@ -173,34 +173,35 @@ class _TaskBackupBase(_BackupBase):
source, dest = attribute.pop('spec_id'), 'name'
attribute[dest] = label_mapping[label]['attributes'][source]
def _update_label(shape):
def _update_label(shape, parent_label=''):
if 'label_id' in shape:
source, dest = shape.pop('label_id'), 'label'
source = shape.pop('label_id')
shape['label'] = label_mapping[source]['value']
elif 'label' in shape:
source, dest = shape.pop('label'), 'label_id'
shape[dest] = label_mapping[source]['value']
source = parent_label + shape.pop('label')
shape['label_id'] = label_mapping[source]['value']
return source
def _prepare_shapes(shapes):
def _prepare_shapes(shapes, parent_label=''):
for shape in shapes:
label = _update_label(shape)
label = _update_label(shape, parent_label)
for attr in shape['attributes']:
_update_attribute(attr, label)
_prepare_shapes(shape.get('elements', []))
_prepare_shapes(shape.get('elements', []), label)
self._prepare_meta(allowed_fields, shape)
def _prepare_tracks(tracks):
def _prepare_tracks(tracks, parent_label=''):
for track in tracks:
label = _update_label(track)
label = _update_label(track, parent_label)
for shape in track['shapes']:
for attr in shape['attributes']:
_update_attribute(attr, label)
self._prepare_meta(allowed_fields, shape)
_prepare_tracks(track.get('elements', []))
_prepare_tracks(track.get('elements', []), label)
for attr in track['attributes']:
_update_attribute(attr, label)
@ -427,7 +428,7 @@ class _ImporterBase():
sublabels = label.pop('sublabels', [])
db_label = models.Label.objects.create(**label_relation, parent=parent_label, **label)
label_mapping[label_name] = {
label_mapping[(parent_label.name if parent_label else '') + label_name] = {
'value': db_label.id,
'attributes': {},
}
@ -444,7 +445,7 @@ class _ImporterBase():
attribute_serializer = AttributeSerializer(data=attribute)
attribute_serializer.is_valid(raise_exception=True)
db_attribute = attribute_serializer.save(label=db_label)
label_mapping[label_name]['attributes'][attribute_name] = db_attribute.id
label_mapping[(parent_label.name if parent_label else '') + label_name]['attributes'][attribute_name] = db_attribute.id
return label_mapping

@ -816,12 +816,13 @@ class ProjectWriteSerializer(serializers.ModelSerializer):
sublabels = label.pop('sublabels', [])
svg = label.pop('svg', '')
db_label = LabelSerializer.update_instance(label, instance, parent_label)
update_labels(sublabels, parent_label=db_label)
if not label.get('deleted'):
update_labels(sublabels, parent_label=db_label)
if label.get('id') is None and db_label.type == str(models.LabelType.SKELETON):
for db_sublabel in list(db_label.sublabels.all()):
svg = svg.replace(f'data-label-name="{db_sublabel.name}"', f'data-label-id="{db_sublabel.id}"')
models.Skeleton.objects.create(root=db_label, svg=svg)
if label.get('id') is None and db_label.type == str(models.LabelType.SKELETON):
for db_sublabel in list(db_label.sublabels.all()):
svg = svg.replace(f'data-label-name="{db_sublabel.name}"', f'data-label-id="{db_sublabel.id}"')
models.Skeleton.objects.create(root=db_label, svg=svg)
update_labels(labels)

@ -9,10 +9,10 @@ This is the native CVAT annotation format. It supports all CVAT annotations
features, so it can be used to make data backups.
- supported annotations CVAT for Images: Rectangles, Polygons, Polylines,
Points, Cuboids, Tags, Tracks
Points, Cuboids, Skeletons, Tags, Tracks
- supported annotations CVAT for Videos: Rectangles, Polygons, Polylines,
Points, Cuboids, Tracks
Points, Cuboids, Skeletons, Tracks
- attributes are supported

@ -38,6 +38,7 @@ In annotation mode each image tag has `width` and `height` attributes for the sa
<labels>
<label>
<name>String: name of the label (e.g. car, person)</name>
<type>String: any, bbox, cuboid, cuboid_3d, ellipse, polygon, polyline, points, skeleton, tag</type>
<attributes>
<attribute>
<name>String: attribute name</name>
@ -49,6 +50,8 @@ ex. value 2
ex. value 3</values>
</attribute>
</attributes>
<svg>String: label representation in svg, only for skeletons</svg>
<parent>String: label parent name, only for skeletons</parent>
</label>
</labels>
<segments>
@ -81,7 +84,7 @@ On each image it is possible to have many different objects. Each object can hav
If an annotation task is created with `z_order` flag then each object will have `z_order` attribute which is used
to draw objects properly when they are intersected (if `z_order` is bigger the object is closer to camera).
In previous versions of the format only `box` shape was available.
In later releases `polygon`, `polyline`, `points` and `tags` were added. Please see below for more details:
In later releases `polygon`, `polyline`, `points`, `skeletons` and `tags` were added. Please see below for more details:
```xml
<?xml version="1.0" encoding="utf-8"?>
@ -117,6 +120,14 @@ In later releases `polygon`, `polyline`, `points` and `tags` were added. Please
<attribute name="String: an attribute name">String: the attribute value</attribute>
...
</tag>
<skeleton label="String: the associated label" occluded="Number: 0 - False, 1 - True" z_order="Number: z-order of the object">
<points label="String: the associated label" occluded="Number: 0 - False, 1 - True" outside="Number: 0 - False, 1 - True" points="x0,y0;x1,y1" z_order="0">
<attribute name="String: an attribute name">String: the attribute value</attribute>
</points>
...
<attribute name="String: an attribute name">String: the attribute value</attribute>
...
</skeleton>
...
</image>
...
@ -161,6 +172,34 @@ Example:
<attributes>
</attributes>
</label>
<label>
<name>s1</name>
<type>skeleton</type>
<attributes>
</attributes>
<svg>&lt;line x1="36.87290954589844" y1="47.732025146484375" x2="86.87290954589844" y2="10.775501251220703" stroke="black" data-type="edge" data-node-from="2" stroke-width="0.5" data-node-to="3"&gt;&lt;/line&gt;&lt;line x1="25.167224884033203" y1="22.64841079711914" x2="36.87290954589844" y2="47.732025146484375" stroke="black" data-type="edge" data-node-from="1" stroke-width="0.5" data-node-to="2"&gt;&lt;/line&gt;&lt;circle r="1.5" stroke="black" fill="#b3b3b3" cx="25.167224884033203" cy="22.64841079711914" stroke-width="0.1" data-type="element node" data-element-id="1" data-node-id="1" data-label-name="1"&gt;&lt;/circle&gt;&lt;circle r="1.5" stroke="black" fill="#b3b3b3" cx="36.87290954589844" cy="47.732025146484375" stroke-width="0.1" data-type="element node" data-element-id="2" data-node-id="2" data-label-name="2"&gt;&lt;/circle&gt;&lt;circle r="1.5" stroke="black" fill="#b3b3b3" cx="86.87290954589844" cy="10.775501251220703" stroke-width="0.1" data-type="element node" data-element-id="3" data-node-id="3" data-label-name="3"&gt;&lt;/circle&gt;</svg>
</label>
<label>
<name>1</name>
<type>points</type>
<attributes>
</attributes>
<parent>s1</parent>
</label>
<label>
<name>2</name>
<type>points</type>
<attributes>
</attributes>
<parent>s1</parent>
</label>
<label>
<name>3</name>
<type>points</type>
<attributes>
</attributes>
<parent>s1</parent>
</label>
</labels>
<segments>
<segment>
@ -190,6 +229,14 @@ Example:
</points>
<tag label="good_frame" source="manual">
</tag>
<skeleton label="s1" occluded="0" source="manual" points="" rotation="0.00" z_order="0">
<points label="1" occluded="0" source="manual" outside="0" points="54.47,94.81" z_order="0">
</points>
<points label="2" occluded="0" source="manual" outside="0" points="68.02,162.34" z_order="0">
</points>
<points label="3" occluded="0" source="manual" outside="0" points="125.87,62.85" z_order="0">
</points>
</skeleton>
</image>
</annotations>
```
@ -206,7 +253,7 @@ cloned for each location (a known redundancy).
<?xml version="1.0" encoding="utf-8"?>
<annotations>
...
<track id="Number: id of the track (doesn't have any special meeting" label="String: the associated label" source="manual or auto">
<track id="Number: id of the track (doesn't have any special meeting)" label="String: the associated label" source="manual or auto">
<box frame="Number: frame" xtl="Number: float" ytl="Number: float" xbr="Number: float" ybr="Number: float" outside="Number: 0 - False, 1 - True" occluded="Number: 0 - False, 1 - True" keyframe="Number: 0 - False, 1 - True">
<attribute name="String: an attribute name">String: the attribute value</attribute>
...
@ -222,6 +269,17 @@ cloned for each location (a known redundancy).
</points>
...
</track>
<track id="Number: id of the track (doesn't have any special meeting)" label="String: the associated label" source="manual or auto">
<skeleton frame="Number: frame" outside="Number: 0 - False, 1 - True" occluded="Number: 0 - False, 1 - True" keyframe="Number: 0 - False, 1 - True">
</skeleton>
...
<track id="Number: id of the track (doesn't have any special meeting)" label="String: the associated label" source="manual or auto">
<points frame="Number: frame" outside="Number: 0 - False, 1 - True" occluded="Number: 0 - False, 1 - True" keyframe="Number: 0 - False, 1 - True" points="x0,y0;x1,y1">
</points>
...
</track>
...
</track>
...
</annotations>
```
@ -254,6 +312,34 @@ Example:
<attributes>
</attributes>
</label>
<label>
<name>s1</name>
<type>skeleton</type>
<attributes>
</attributes>
<svg>&lt;line x1="36.87290954589844" y1="47.732025146484375" x2="86.87290954589844" y2="10.775501251220703" stroke="black" data-type="edge" data-node-from="2" stroke-width="0.5" data-node-to="3"&gt;&lt;/line&gt;&lt;line x1="25.167224884033203" y1="22.64841079711914" x2="36.87290954589844" y2="47.732025146484375" stroke="black" data-type="edge" data-node-from="1" stroke-width="0.5" data-node-to="2"&gt;&lt;/line&gt;&lt;circle r="1.5" stroke="black" fill="#b3b3b3" cx="25.167224884033203" cy="22.64841079711914" stroke-width="0.1" data-type="element node" data-element-id="1" data-node-id="1" data-label-name="1"&gt;&lt;/circle&gt;&lt;circle r="1.5" stroke="black" fill="#b3b3b3" cx="36.87290954589844" cy="47.732025146484375" stroke-width="0.1" data-type="element node" data-element-id="2" data-node-id="2" data-label-name="2"&gt;&lt;/circle&gt;&lt;circle r="1.5" stroke="black" fill="#b3b3b3" cx="86.87290954589844" cy="10.775501251220703" stroke-width="0.1" data-type="element node" data-element-id="3" data-node-id="3" data-label-name="3"&gt;&lt;/circle&gt;</svg>
</label>
<label>
<name>1</name>
<type>points</type>
<attributes>
</attributes>
<parent>s1</parent>
</label>
<label>
<name>2</name>
<type>points</type>
<attributes>
</attributes>
<parent>s1</parent>
</label>
<label>
<name>3</name>
<type>points</type>
<attributes>
</attributes>
<parent>s1</parent>
</label>
</labels>
<segments>
<segment>
@ -288,5 +374,61 @@ Example:
<polygon frame="14" points="313.74,233.16;331.11,220.00;359.53,243.16;333.21,283.16;287.95,274.74" outside="1" occluded="0" keyframe="1">
</polygon>
</track>
<track id="1" label="s1" source="manual">
<skeleton frame="0" outside="0" occluded="0" keyframe="1" points="" z_order="0">
</skeleton>
<skeleton frame="1" outside="1" occluded="0" keyframe="1" points="" z_order="0">
</skeleton>
<skeleton frame="6" outside="0" occluded="0" keyframe="1" points="" z_order="0">
</skeleton>
<skeleton frame="7" outside="1" occluded="0" keyframe="1" points="" z_order="0">
</skeleton>
<skeleton frame="13" outside="0" occluded="0" keyframe="0" points="" z_order="0">
</skeleton>
<skeleton frame="14" outside="1" occluded="0" keyframe="1" points="" z_order="0">
</skeleton>
<track id="0" label="1" source="manual" task_id="20" subset="default_1">
<points frame="0" outside="0" occluded="0" keyframe="1" points="112.07,258.59" z_order="0">
</points>
<points frame="1" outside="1" occluded="0" keyframe="1" points="112.07,258.59" z_order="0">
</points>
<points frame="6" outside="0" occluded="0" keyframe="0" points="120.07,270.59" z_order="0">
</points>
<points frame="7" outside="1" occluded="0" keyframe="1" points="120.07,270.59" z_order="0">
</points>
<points frame="13" outside="0" occluded="0" keyframe="0" points="112.07,258.59" z_order="0">
</points>
<points frame="14" outside="1" occluded="0" keyframe="1" points="112.07,258.59" z_order="0">
</points>
</track>
<track id="1" label="2" source="manual" task_id="20" subset="default_1">
<points frame="0" outside="0" occluded="0" keyframe="1" points="127.87,333.23" z_order="0">
</points>
<points frame="1" outside="1" occluded="0" keyframe="1" points="127.87,333.23" z_order="0">
</points>
<points frame="6" outside="0" occluded="0" keyframe="0" points="140.87,350.23" z_order="0">
</points>
<points frame="7" outside="1" occluded="0" keyframe="1" points="140.87,350.23" z_order="0">
</points>
<points frame="13" outside="0" occluded="0" keyframe="0" points="127.87,333.23" z_order="0">
</points>
<points frame="14" outside="1" occluded="0" keyframe="1" points="127.87,333.23" z_order="0">
</points>
</track>
<track id="2" label="3" source="manual" task_id="20" subset="default_1">
<points frame="0" outside="0" occluded="0" keyframe="1" points="195.37,223.27" z_order="0">
</points>
<points frame="1" outside="1" occluded="0" keyframe="1" points="195.37,223.27" z_order="0">
</points>
<points frame="6" outside="0" occluded="0" keyframe="0" points="210.37,260.27" z_order="0">
</points>
<points frame="7" outside="1" occluded="0" keyframe="1" points="210.37,260.27" z_order="0">
</points>
<points frame="13" outside="0" occluded="0" keyframe="0" points="195.37,223.27" z_order="0">
</points>
<points frame="14" outside="1" occluded="0" keyframe="1" points="195.37,223.27" z_order="0">
</points>
</track>
</track>
</annotations>
```

Loading…
Cancel
Save