Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Open Images format #3679

Merged
merged 16 commits into from
Nov 9, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add a tutorial on attaching cloud storage AWS-S3 (<https://github.com/openvinotoolkit/cvat/pull/3745>)
and Azure Blob Container (<https://github.com/openvinotoolkit/cvat/pull/3778>)
- The feature to remove annotations in a specified range of frames (<https://github.com/openvinotoolkit/cvat/pull/3617>)
- Add Open Images V6 format (<https://github.com/openvinotoolkit/cvat/pull/3679>)

### Changed

Expand Down
83 changes: 83 additions & 0 deletions cvat/apps/dataset_manager/formats/openimages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Copyright (C) 2021 Intel Corporation
#
# SPDX-License-Identifier: MIT

import glob
import os.path as osp
from tempfile import TemporaryDirectory

from datumaro.components.dataset import Dataset, DatasetItem
from datumaro.plugins.open_images_format import OpenImagesPath
from datumaro.util.image import DEFAULT_IMAGE_META_FILE_NAME
from pyunpack import Archive

from cvat.apps.dataset_manager.bindings import (GetCVATDataExtractor,
find_dataset_root, import_dm_annotations, match_dm_item)
from cvat.apps.dataset_manager.util import make_zip_archive

from .registry import dm_env, exporter, importer


def find_item_ids(path):
image_desc_patterns = (
OpenImagesPath.FULL_IMAGE_DESCRIPTION_FILE_NAME,
*OpenImagesPath.SUBSET_IMAGE_DESCRIPTION_FILE_PATTERNS
)

image_desc_patterns = (
osp.join(path, OpenImagesPath.ANNOTATIONS_DIR, pattern)
for pattern in image_desc_patterns
)

for pattern in image_desc_patterns:
for path in glob.glob(pattern):
with open(path, 'r') as desc:
next(desc)
for row in desc:
yield row.split(',')[0]

@exporter(name='Open Images V6', ext='ZIP', version='1.0')
def _export(dst_file, task_data, save_images=False):
dataset = Dataset.from_extractors(GetCVATDataExtractor(
task_data, include_images=save_images), env=dm_env)
dataset.transform('polygons_to_masks')
dataset.transform('merge_instance_segments')

with TemporaryDirectory() as temp_dir:
dataset.export(temp_dir, 'open_images', save_images=save_images)

make_zip_archive(temp_dir, dst_file)

@importer(name='Open Images V6', ext='ZIP', version='1.0')
def _import(src_file, task_data):
with TemporaryDirectory() as tmp_dir:
Archive(src_file.name).extractall(tmp_dir)

image_meta_path = osp.join(tmp_dir, OpenImagesPath.ANNOTATIONS_DIR,
DEFAULT_IMAGE_META_FILE_NAME)
image_meta = None

if not osp.isfile(image_meta_path):
image_meta = {}
item_ids = list(find_item_ids(tmp_dir))

root_hint = find_dataset_root(
[DatasetItem(id=item_id) for item_id in item_ids], task_data)

for item_id in item_ids:
frame_info = None
try:
frame_id = match_dm_item(DatasetItem(id=item_id),
task_data, root_hint)
frame_info = task_data.frame_info[frame_id]
except Exception: # nosec
pass
if frame_info is not None:
image_meta[item_id] = (frame_info['height'], frame_info['width'])

dataset = Dataset.import_from(tmp_dir, 'open_images',
image_meta=image_meta, env=dm_env)
dataset.transform('masks_to_polygons')
import_dm_annotations(dataset, task_data)


1 change: 1 addition & 0 deletions cvat/apps/dataset_manager/formats/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,5 @@ def make_exporter(name):
import cvat.apps.dataset_manager.formats.icdar
import cvat.apps.dataset_manager.formats.velodynepoint
import cvat.apps.dataset_manager.formats.pointcloud
import cvat.apps.dataset_manager.formats.openimages

29 changes: 29 additions & 0 deletions cvat/apps/dataset_manager/tests/assets/annotations.json
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,35 @@
}
]
},
"Open Images V6 1.0": {
"version": 0,
"tags": [],
"shapes": [
{
"type": "rectangle",
"occluded": false,
"z_order": 0,
"points": [1.0, 2.0, 3.0, 4.0],
"frame": 0,
"label_id": null,
"group": 0,
"source": "manual",
"attributes": []
},
{
"type": "polygon",
"occluded": false,
"z_order": 0,
"points": [1.0, 1.0, 1.0, 20.0, 20.0, 1.0, 20.0, 1.0, 1.0, 1.0],
"frame": 0,
"label_id": null,
"group": 0,
"source": "manual",
"attributes": []
}
],
"tracks": []
},
"PASCAL VOC 1.1": {
"version": 0,
"tags": [
Expand Down
5 changes: 3 additions & 2 deletions cvat/apps/dataset_manager/tests/test_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,8 @@ def test_export_formats_query(self):
'ICDAR Localization 1.0',
'ICDAR Segmentation 1.0',
'Kitti Raw Format 1.0',
'Sly Point Cloud Format 1.0'

'Sly Point Cloud Format 1.0',
'Open Images V6 1.0'
})

def test_import_formats_query(self):
Expand All @@ -323,6 +323,7 @@ def test_import_formats_query(self):
'ICDAR Segmentation 1.0',
'Kitti Raw Format 1.0',
'Sly Point Cloud Format 1.0',
'Open Images V6 1.0',
'Datumaro 1.0',
'Datumaro 3D 1.0'
})
Expand Down
7 changes: 5 additions & 2 deletions cvat/apps/dataset_manager/tests/test_rest_api_formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,8 @@ def test_api_v1_rewriting_annotations(self):
"MOT 1.1", "MOTS PNG 1.0", \
"PASCAL VOC 1.1", "Segmentation mask 1.1", \
"TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \
"WiderFace 1.0", "VGGFace2 1.0", "Datumaro 1.0" \
"WiderFace 1.0", "VGGFace2 1.0", "Datumaro 1.0",\
"Open Images V6 1.0" \
]:
self._create_annotations(task, dump_format_name, "default")
else:
Expand Down Expand Up @@ -1005,6 +1006,7 @@ def test_api_v1_tasks_annotations_dump_and_upload_with_datumaro(self):
"MOTS PNG 1.0", # changed points values
"Segmentation mask 1.1", # changed points values
"ICDAR Segmentation 1.0", # changed points values
"Open Images V6 1.0", # changed points values
'Kitti Raw Format 1.0',
'Sly Point Cloud Format 1.0',
'Datumaro 3D 1.0'
Expand All @@ -1028,7 +1030,8 @@ def test_api_v1_tasks_annotations_dump_and_upload_with_datumaro(self):
"MOT 1.1", "MOTS PNG 1.0", \
"PASCAL VOC 1.1", "Segmentation mask 1.1", \
"TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \
"WiderFace 1.0", "VGGFace2 1.0", "Datumaro 1.0", \
"WiderFace 1.0", "VGGFace2 1.0", "Open Images V6 1.0", \
"Datumaro 1.0", \
]:
self._create_annotations(task, dump_format_name, "default")
else:
Expand Down
5 changes: 5 additions & 0 deletions cvat/apps/engine/tests/test_rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4811,6 +4811,11 @@ def _get_initial_annotation(annotation_format):
annotations["shapes"] = points_wo_attrs \
+ rectangle_shapes_wo_attrs

elif annotation_format == "Open Images V6 1.0":
annotations["tags"] = tags_wo_attrs
annotations["shapes"] = rectangle_shapes_wo_attrs \
+ polygon_shapes_wo_attrs

elif annotation_format == "Market-1501 1.0":
tags_with_attrs = [{
"frame": 1,
Expand Down
108 changes: 108 additions & 0 deletions site/content/en/docs/manual/advanced/formats/format-openimages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
linkTitle: 'Open Images V6'
weight: 15
---

# [Open Images](https://storage.googleapis.com/openimages/web/index.html)

- [Format specification](https://storage.googleapis.com/openimages/web/download.html)

- Supported annotations:

- Rectangles (detection task)
- Tags (classification task)
- Polygons (segmentation task)

- Supported attributes:

- Labels

- `score` (should be defined for labels as `text` or `number`).
The confidence level from 0 to 1.

- Bounding boxes

- `score` (should be defined for labels as `text` or `number`).
The confidence level from 0 to 1.
- `occluded` (both UI option and a separate attribute).
Whether the object is occluded by another object.
- `truncated` (should be defined for labels as `checkbox` -es).
Whether the object extends beyond the boundary of the image.
- `is_group_of` (should be defined for labels as `checkbox` -es).
Whether the object represents a group of objects of the same class.
- `is_depiction` (should be defined for labels as `checkbox` -es).
Whether the object is a depiction (such as a drawing)
rather than a real object.
- `is_inside` (should be defined for labels as `checkbox` -es).
Whether the object is seen from the inside.

- Masks
- `box_id` (should be defined for labels as `text`).
An identifier for the bounding box associated with the mask.
- `predicted_iou` (should be defined for labels as `text` or `number`).
Predicted IoU value with respect to the ground truth.

## Open Images export

Downloaded file: a zip archive of the following structure:

```
└─ taskname.zip/
├── annotations/
│ ├── bbox_labels_600_hierarchy.json
│ ├── class-descriptions.csv
| ├── images.meta # additional file with information about image sizes
│ ├── <subset_name>-image_ids_and_rotation.csv
│ ├── <subset_name>-annotations-bbox.csv
│ ├── <subset_name>-annotations-human-imagelabels.csv
│ └── <subset_name>-annotations-object-segmentation.csv
├── images/
│ ├── subset1/
│ │ ├── <image_name101.jpg>
│ │ ├── <image_name102.jpg>
│ │ └── ...
│ ├── subset2/
│ │ ├── <image_name201.jpg>
│ │ ├── <image_name202.jpg>
│ │ └── ...
| ├── ...
└── masks/
├── subset1/
│ ├── <mask_name101.png>
│ ├── <mask_name102.png>
│ └── ...
├── subset2/
│ ├── <mask_name201.png>
│ ├── <mask_name202.png>
│ └── ...
├── ...
```

## Open Images import

Uploaded file: a zip archive of the following structure:

```
└─ upload.zip/
├── annotations/
│ ├── bbox_labels_600_hierarchy.json
│ ├── class-descriptions.csv
| ├── images.meta # optional, file with information about image sizes
│ ├── <subset_name>-image_ids_and_rotation.csv
│ ├── <subset_name>-annotations-bbox.csv
│ ├── <subset_name>-annotations-human-imagelabels.csv
│ └── <subset_name>-annotations-object-segmentation.csv
└── masks/
├── subset1/
│ ├── <mask_name101.png>
│ ├── <mask_name102.png>
│ └── ...
├── subset2/
│ ├── <mask_name201.png>
│ ├── <mask_name202.png>
│ └── ...
├── ...
```

Image ids in the `<subset_name>-image_ids_and_rotation.csv` should match with
image names in the task.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ context('Import annotations for frames with dots in name.', { browser: '!firefox
const createRectangleShape2Points = {
points: 'By 2 Points',
type: 'Shape',
labelName: labelName,
labelName,
firstX: 250,
firstY: 350,
secondX: 350,
Expand Down Expand Up @@ -70,9 +70,14 @@ context('Import annotations for frames with dots in name.', { browser: '!firefox
cy.get('.cvat-modal-export-task').find('.cvat-modal-export-select').click();
cy.get('.ant-select-dropdown')
.not('.ant-select-dropdown-hidden')
.trigger('wheel', {deltaY: 700})
.contains('.cvat-modal-export-option-item', dumpType)
.click();
.within(() => {
cy.get('.rc-virtual-list-holder')
.trigger('wheel', { deltaY: 1000 })
.trigger('wheel', { deltaY: 1000 })
.contains('.cvat-modal-export-option-item', dumpType)
.should('be.visible')
.click();
});
cy.get('.cvat-modal-export-select').should('contain.text', dumpType);
cy.get('.cvat-modal-export-task').contains('button', 'OK').click();
cy.wait('@dumpAnnotations', { timeout: 5000 }).its('response.statusCode').should('equal', 202);
Expand All @@ -92,6 +97,7 @@ context('Import annotations for frames with dots in name.', { browser: '!firefox
it('Upload annotation with YOLO format to job.', () => {
cy.interactMenu('Upload annotations');
cy.contains('.cvat-menu-load-submenu-item', dumpType.split(' ')[0])
.scrollIntoView()
.should('be.visible')
.within(() => {
cy.get('.cvat-menu-load-submenu-item-button')
Expand Down