Add Open Images format (#3679)

main
Kirill Sizov 4 years ago committed by GitHub
parent 1ea23e312a
commit cc1b8190f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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>) - 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>) 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>) - 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 ### Changed

@ -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)

@ -121,4 +121,5 @@ import cvat.apps.dataset_manager.formats.market1501
import cvat.apps.dataset_manager.formats.icdar import cvat.apps.dataset_manager.formats.icdar
import cvat.apps.dataset_manager.formats.velodynepoint import cvat.apps.dataset_manager.formats.velodynepoint
import cvat.apps.dataset_manager.formats.pointcloud import cvat.apps.dataset_manager.formats.pointcloud
import cvat.apps.dataset_manager.formats.openimages

@ -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": { "PASCAL VOC 1.1": {
"version": 0, "version": 0,
"tags": [ "tags": [

@ -295,8 +295,8 @@ class TaskExportTest(_DbTestBase):
'ICDAR Localization 1.0', 'ICDAR Localization 1.0',
'ICDAR Segmentation 1.0', 'ICDAR Segmentation 1.0',
'Kitti Raw Format 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): def test_import_formats_query(self):
@ -323,6 +323,7 @@ class TaskExportTest(_DbTestBase):
'ICDAR Segmentation 1.0', 'ICDAR Segmentation 1.0',
'Kitti Raw Format 1.0', 'Kitti Raw Format 1.0',
'Sly Point Cloud Format 1.0', 'Sly Point Cloud Format 1.0',
'Open Images V6 1.0',
'Datumaro 1.0', 'Datumaro 1.0',
'Datumaro 3D 1.0' 'Datumaro 3D 1.0'
}) })

@ -916,7 +916,8 @@ class TaskDumpUploadTest(_DbTestBase):
"MOT 1.1", "MOTS PNG 1.0", \ "MOT 1.1", "MOTS PNG 1.0", \
"PASCAL VOC 1.1", "Segmentation mask 1.1", \ "PASCAL VOC 1.1", "Segmentation mask 1.1", \
"TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ "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") self._create_annotations(task, dump_format_name, "default")
else: else:
@ -1005,6 +1006,7 @@ class TaskDumpUploadTest(_DbTestBase):
"MOTS PNG 1.0", # changed points values "MOTS PNG 1.0", # changed points values
"Segmentation mask 1.1", # changed points values "Segmentation mask 1.1", # changed points values
"ICDAR Segmentation 1.0", # changed points values "ICDAR Segmentation 1.0", # changed points values
"Open Images V6 1.0", # changed points values
'Kitti Raw Format 1.0', 'Kitti Raw Format 1.0',
'Sly Point Cloud Format 1.0', 'Sly Point Cloud Format 1.0',
'Datumaro 3D 1.0' 'Datumaro 3D 1.0'
@ -1028,7 +1030,8 @@ class TaskDumpUploadTest(_DbTestBase):
"MOT 1.1", "MOTS PNG 1.0", \ "MOT 1.1", "MOTS PNG 1.0", \
"PASCAL VOC 1.1", "Segmentation mask 1.1", \ "PASCAL VOC 1.1", "Segmentation mask 1.1", \
"TFRecord 1.0", "YOLO 1.1", "ImageNet 1.0", \ "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") self._create_annotations(task, dump_format_name, "default")
else: else:

@ -4811,6 +4811,11 @@ class TaskAnnotationAPITestCase(JobAnnotationAPITestCase):
annotations["shapes"] = points_wo_attrs \ annotations["shapes"] = points_wo_attrs \
+ rectangle_shapes_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": elif annotation_format == "Market-1501 1.0":
tags_with_attrs = [{ tags_with_attrs = [{
"frame": 1, "frame": 1,

@ -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.

@ -25,7 +25,7 @@ context('Import annotations for frames with dots in name.', { browser: '!firefox
const createRectangleShape2Points = { const createRectangleShape2Points = {
points: 'By 2 Points', points: 'By 2 Points',
type: 'Shape', type: 'Shape',
labelName: labelName, labelName,
firstX: 250, firstX: 250,
firstY: 350, firstY: 350,
secondX: 350, secondX: 350,
@ -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('.cvat-modal-export-task').find('.cvat-modal-export-select').click();
cy.get('.ant-select-dropdown') cy.get('.ant-select-dropdown')
.not('.ant-select-dropdown-hidden') .not('.ant-select-dropdown-hidden')
.trigger('wheel', {deltaY: 700}) .within(() => {
.contains('.cvat-modal-export-option-item', dumpType) cy.get('.rc-virtual-list-holder')
.click(); .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-select').should('contain.text', dumpType);
cy.get('.cvat-modal-export-task').contains('button', 'OK').click(); cy.get('.cvat-modal-export-task').contains('button', 'OK').click();
cy.wait('@dumpAnnotations', { timeout: 5000 }).its('response.statusCode').should('equal', 202); cy.wait('@dumpAnnotations', { timeout: 5000 }).its('response.statusCode').should('equal', 202);
@ -92,6 +97,7 @@ context('Import annotations for frames with dots in name.', { browser: '!firefox
it('Upload annotation with YOLO format to job.', () => { it('Upload annotation with YOLO format to job.', () => {
cy.interactMenu('Upload annotations'); cy.interactMenu('Upload annotations');
cy.contains('.cvat-menu-load-submenu-item', dumpType.split(' ')[0]) cy.contains('.cvat-menu-load-submenu-item', dumpType.split(' ')[0])
.scrollIntoView()
.should('be.visible') .should('be.visible')
.within(() => { .within(() => {
cy.get('.cvat-menu-load-submenu-item-button') cy.get('.cvat-menu-load-submenu-item-button')

Loading…
Cancel
Save