Skip to content

Commit

Permalink
Fix upload anno for COCO (#788)
Browse files Browse the repository at this point in the history
* COCO: load bbox as rectangle if segmentation field is empty
* added unit test for coco format (case: object segment field is empty)
  • Loading branch information
azhavoro authored and nmanovic committed Nov 1, 2019
1 parent 1487cea commit befe5ef
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 7 deletions.
3 changes: 2 additions & 1 deletion cvat/apps/annotation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,8 @@ It may take some time.

#### COCO loader description
- uploaded file: single unpacked `*.json`.
- supported shapes: Polygons (the `segmentation` must not be empty)
- supported shapes: object is interpreted as Polygon if the `segmentation` field of annotation is not empty
else as Rectangle with coordinates from `bbox` field.
- additional comments: the CVAT task should be created with the full label set that may be in the annotation files

#### How to create a task from MS COCO dataset
Expand Down
20 changes: 18 additions & 2 deletions cvat/apps/annotation/coco.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,11 +344,12 @@ def load(file_object, annotations):
for ann in anns:
group = 0
label_name = labels[ann['category_id']]
polygons = []
if 'segmentation' in ann:
polygons = []
# polygon
if ann['iscrowd'] == 0:
polygons = ann['segmentation']
# filter non-empty polygons
polygons = [polygon for polygon in ann['segmentation'] if polygon]
# mask
else:
if isinstance(ann['segmentation']['counts'], list):
Expand All @@ -375,3 +376,18 @@ def load(file_object, annotations):
attributes=[],
group=group,
))

if not polygons and 'bbox' in ann and isinstance(ann['bbox'], list):
xtl = ann['bbox'][0]
ytl = ann['bbox'][1]
xbr = xtl + ann['bbox'][2]
ybr = ytl + ann['bbox'][3]
annotations.add_shape(annotations.LabeledShape(
type='rectangle',
frame=frame_number,
label=label_name,
points=[xtl, ytl, xbr, ybr],
occluded=False,
attributes=[],
group=group,
))
6 changes: 3 additions & 3 deletions cvat/apps/engine/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,9 +280,9 @@ class FloatArrayField(models.TextField):
separator = ","

def from_db_value(self, value, expression, connection):
if value is None:
return value
return [float(v) for v in value.split(self.separator)]
if not value:
return value
return [float(v) for v in value.split(self.separator)]

def to_python(self, value):
if isinstance(value, list):
Expand Down
3 changes: 2 additions & 1 deletion cvat/apps/engine/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ class ShapeSerializer(serializers.Serializer):
occluded = serializers.BooleanField()
z_order = serializers.IntegerField(default=0)
points = serializers.ListField(
child=serializers.FloatField(min_value=0)
child=serializers.FloatField(min_value=0),
allow_empty=False,
)

class LabeledShapeSerializer(ShapeSerializer, AnnotationSerializer):
Expand Down
81 changes: 81 additions & 0 deletions cvat/apps/engine/tests/test_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2779,6 +2779,84 @@ def etree_to_dict(t):
elif annotation_format_name == "MASK":
self.assertTrue(zipfile.is_zipfile(content))


def _run_coco_annotation_upload_test(self, user):
def generate_coco_anno():
return b"""{
"categories": [
{
"id": 1,
"name": "car",
"supercategory": ""
},
{
"id": 2,
"name": "person",
"supercategory": ""
}
],
"images": [
{
"coco_url": "",
"date_captured": "",
"flickr_url": "",
"license": 0,
"id": 0,
"file_name": "test_1.jpg",
"height": 720,
"width": 1280
}
],
"annotations": [
{
"category_id": 1,
"id": 1,
"image_id": 0,
"iscrowd": 0,
"segmentation": [
[]
],
"area": 17702.0,
"bbox": [
574.0,
407.0,
167.0,
106.0
]
}
]
}"""

response = self._get_annotation_formats(user)
self.assertEqual(response.status_code, status.HTTP_200_OK)
supported_formats = response.data
self.assertTrue(isinstance(supported_formats, list) and supported_formats)

coco_format = None
for f in response.data:
if f["name"] == "COCO":
coco_format = f
break
self.assertTrue(coco_format)
loader = coco_format["loaders"][0]

task, _ = self._create_task(user, user)

content = io.BytesIO(generate_coco_anno())
content.seek(0)

uploaded_data = {
"annotation_file": content,
}
response = self._upload_api_v1_tasks_id_annotations(task["id"], user, uploaded_data, "format={}".format(loader["display_name"]))
self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED)

response = self._upload_api_v1_tasks_id_annotations(task["id"], user, {}, "format={}".format(loader["display_name"]))
self.assertEqual(response.status_code, status.HTTP_201_CREATED)

response = self._get_api_v1_tasks_id_annotations(task["id"], user)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_api_v1_tasks_id_annotations_admin(self):
self._run_api_v1_tasks_id_annotations(self.admin, self.assignee,
self.assignee)
Expand All @@ -2801,6 +2879,9 @@ def test_api_v1_tasks_id_annotations_dump_load_user(self):
def test_api_v1_tasks_id_annotations_dump_load_no_auth(self):
self._run_api_v1_tasks_id_annotations_dump_load(self.user, self.assignee, None)

def test_api_v1_tasks_id_annotations_upload_coco_user(self):
self._run_coco_annotation_upload_test(self.user)

class ServerShareAPITestCase(APITestCase):
def setUp(self):
self.client = APIClient()
Expand Down

0 comments on commit befe5ef

Please sign in to comment.