From 185d181d12bb1ead38eb2ec52c0c08b6f738a038 Mon Sep 17 00:00:00 2001 From: ryoKaz Date: Fri, 19 Nov 2021 21:11:05 +0900 Subject: [PATCH 1/4] fix coco export for pose-estimation --- fastlabel/__init__.py | 4 +- fastlabel/const.py | 3 +- fastlabel/converters.py | 109 ++++++++++++++++++++++++++++++++-------- setup.py | 2 +- 4 files changed, 93 insertions(+), 25 deletions(-) diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index 76df58f..f49f80a 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -1021,14 +1021,14 @@ def __get_yolo_format_annotations(self, dataset_folder_path: str) -> dict: # Task Convert - def export_coco(self, tasks: list, output_dir: str = os.path.join("output", "coco")) -> None: + def export_coco(self, tasks: list, annotations: list = [], output_dir: str = os.path.join("output", "coco")) -> None: """ Convert tasks to COCO format and export as a file. tasks is a list of tasks. (Required) output_dir is output directory(default: output/coco). (Optional) """ - coco = converters.to_coco(tasks) + coco = converters.to_coco(tasks, annotations) os.makedirs(output_dir, exist_ok=True) file_path = os.path.join(output_dir, "annotations.json") with open(file_path, 'w') as f: diff --git a/fastlabel/const.py b/fastlabel/const.py index 297bc28..c9ea6d4 100644 --- a/fastlabel/const.py +++ b/fastlabel/const.py @@ -9,4 +9,5 @@ class AnnotationType(Enum): keypoint = "keypoint" line = "line" segmentation = "segmentation" - classification = "classification" \ No newline at end of file + classification = "classification" + pose_estimation = "pose_estimation" \ No newline at end of file diff --git a/fastlabel/converters.py b/fastlabel/converters.py index 7ee887a..2455ceb 100644 --- a/fastlabel/converters.py +++ b/fastlabel/converters.py @@ -11,9 +11,9 @@ # COCO -def to_coco(tasks: list) -> dict: +def to_coco(tasks: list, annotations: list) -> dict: # Get categories - categories = __get_categories(tasks) + categories = __get_categories(tasks, annotations) # Get images and annotations images = [] @@ -52,22 +52,72 @@ def to_coco(tasks: list) -> dict: } -def __get_categories(tasks: list) -> list: +def __get_coco_skelton(keypoints: list) -> list: + keypoint_id_skelton_index_map = {} + for index, keypoint in enumerate(keypoints, 1): + keypoint_id_skelton_index_map[keypoint["id"]] = index + + skelton = [] + filtered_skelton_indexes = [] + for keypoint in keypoints: + id = keypoint["id"] + skelton_index = keypoint_id_skelton_index_map[id] + edges = keypoint["edges"] + for edge in edges: + edge_skelton_index = keypoint_id_skelton_index_map[edge] + if not edge_skelton_index in filtered_skelton_indexes: + skelton.append([skelton_index, edge_skelton_index]) + filtered_skelton_indexes.append(skelton_index) + return skelton + + +def __get_categories(tasks: list, annotations: list) -> list: + categories = [] values = [] for task in tasks: - for annotation in task["annotations"]: - if annotation["type"] != AnnotationType.bbox.value and annotation["type"] != AnnotationType.polygon.value: + for task_annotation in task["annotations"]: + if task_annotation["type"] != AnnotationType.bbox.value and task_annotation["type"] != AnnotationType.polygon.value and task_annotation["type"] != AnnotationType.pose_estimation.value: continue - values.append(annotation["value"]) + values.append(task_annotation["value"]) values = list(set(values)) - categories = [] - for index, value in enumerate(values): - category = { - "supercategory": value, - "id": index + 1, - "name": value - } + # Create categories from task annotations (not support pose esitimation) + if not annotations: + for index, value in enumerate(values, 1): + category = { + "skelton": [], + "keypoints": [], + "keypoint_colors": [], + "color": task_annotation["color"], + "supercategory": value, + "id": index, + "name": value + } + categories.append(category) + return categories + + # Create categories from passed annotations (support pose esitimation) + index = 1 + for annotation in annotations: + if not annotation["value"] in values: + continue + coco_skelton = [] + coco_keypoints = [] + coco_keypoint_colors = [] + if annotation["type"] == AnnotationType.pose_estimation.value: + keypoints = annotation["keypoints"] + for keypoint in keypoints: + coco_keypoints.append(keypoint["key"]) + coco_keypoint_colors.append(keypoint["color"]) + coco_skelton = __get_coco_skelton(keypoints) + category = {"skelton": coco_skelton, + "keypoints": coco_keypoints, + "keypoint_colors": coco_keypoint_colors, + "color": annotation["color"], + "supercategory": annotation["value"], + "id": index, + "name": annotation["value"]} + index += 1 categories.append(category) return categories @@ -76,13 +126,14 @@ def __to_annotation(data: dict) -> dict: annotation = data["annotation"] categories = data["categories"] image = data["image"] - points = annotation["points"] + points = annotation.get("points") + keypoints = annotation.get("keypoints") annotation_type = annotation["type"] annotation_id = 0 - if annotation_type != AnnotationType.bbox.value and annotation_type != AnnotationType.polygon.value: + if annotation_type != AnnotationType.bbox.value and annotation_type != AnnotationType.polygon.value and annotation_type != AnnotationType.pose_estimation.value: return None - if not points or len(points) == 0: + if annotation_type != AnnotationType.pose_estimation.value and (not points or len(points)) == 0: return None if annotation_type == AnnotationType.bbox.value and (int(points[0]) == int(points[2]) or int(points[1]) == int(points[3])): return None @@ -90,7 +141,8 @@ def __to_annotation(data: dict) -> dict: category = __get_category_by_name(categories, annotation["value"]) return __get_annotation( - annotation_id, points, category["id"], image, annotation_type) + annotation_id, points, keypoints, category["id"], image, annotation_type) + def __get_category_by_name(categories: list, name: str) -> str: @@ -99,13 +151,28 @@ def __get_category_by_name(categories: list, name: str) -> str: return category -def __get_annotation(id_: int, points: list, category_id: int, image: dict, annotation_type: str) -> dict: +def __get_coco_annotation_keypoints(keypoints: list) -> list: + coco_annotation_keypoints = [] + for keypoint in keypoints: + value = keypoint["value"] + if not value: + coco_annotation_keypoints.extend([0, 0, 0]) + # Adjust fastlabel data definition to coco format + visibility = 2 if value[2] == 1 else 1 + coco_annotation_keypoints.extend([value[0], value[1], visibility]) + return coco_annotation_keypoints + + +def __get_annotation(id_: int, points: list, keypoints: list, category_id: int, image: dict, annotation_type: str) -> dict: annotation = {} - annotation["segmentation"] = [points] + annotation["num_keypoints"] = len(keypoints) if keypoints else 0 + annotation["keypoints"] = __get_coco_annotation_keypoints( + keypoints) if keypoints else [] + annotation["segmentation"] = [points] if points else [] annotation["iscrowd"] = 0 - annotation["area"] = __calc_area(annotation_type, points) + annotation["area"] = __calc_area(annotation_type, points) if points else 0 annotation["image_id"] = image["id"] - annotation["bbox"] = __to_bbox(points) + annotation["bbox"] = __to_bbox(points) if points else [] annotation["category_id"] = category_id annotation["id"] = id_ return annotation diff --git a/setup.py b/setup.py index 73fb9ab..6ed4a2a 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name="fastlabel", - version="0.11.7", + version="0.11.8", author="eisuke-ueta", author_email="eisuke.ueta@fastlabel.ai", description="The official Python SDK for FastLabel API, the Data Platform for AI", From 5cad1f629763b67241ce0b24cbc8c83fa6d87e7b Mon Sep 17 00:00:00 2001 From: ryoKaz Date: Sun, 21 Nov 2021 20:11:42 +0900 Subject: [PATCH 2/4] fix document --- README.md | 164 +++++++++++++++++++++++++++++++++++++++++- fastlabel/__init__.py | 2 + 2 files changed, 164 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 56210d3..b62020b 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ Supported following project types: - Image - Keypoint - Image - Line - Image - Segmentation +- Image - Pose Estimation(not support Create Task) - Image - All #### Create Task @@ -198,6 +199,78 @@ Example of a single image task object } ``` +Example when the project type is Image - Pose Estimation + +```python +{ + "id": "YOUR_TASK_ID", + "name": "person.jpg", + "width": 255, # image width + "height": 255, # image height + "url": "YOUR_TASK_URL", + "status": "registered", + "externalStatus": "registered", + "tags": [], + "assignee": "ASSIGNEE_NAME", + "reviewer": "REVIEWER_NAME", + "externalAssignee": "EXTERNAL_ASSIGNEE_NAME", + "externalReviewer": "EXTERNAL_REVIEWER_NAME", + "annotations":[ + { + "type":"pose_estimation", + "title":"jesture", + "value":"jesture", + "color":"#10c414", + "attributes":[ + + ], + "keypoints":[ + { + "name":"頭", + "key":"head", + "value":[ + 102.59, # x + 23.04, # y + 1 # 0:invisible, 1:visible + ], + "edges":[ + "right_shoulder", + "left_shoulder" + ] + }, + { + "name":"右肩", + "key":"right_shoulder", + "value":[ + 186.69, + 114.11, + 1 + ], + "edges":[ + "head" + ] + }, + { + "name":"左肩", + "key":"left_shoulder", + "value":[ + 37.23, + 109.29, + 1 + ], + "edges":[ + "head" + ] + } + ] + } + ], + "createdAt": "2021-02-22T11:25:27.158Z", + "updatedAt": "2021-02-22T11:25:27.158Z" +} +``` + + ### Image Classification Supported following project types: @@ -744,6 +817,63 @@ Example of an annotation object } ``` +Example when the annotation type is Pose Estimation +```python +{ + "id":"b12c81c3-ddec-4f98-b41b-cef7f77d26a4", + "type":"pose_estimation", + "title":"jesture", + "value":"jesture", + "color":"#10c414", + "order":1, + "attributes":[ + + ], + "keypoints":[ + { + "id":"b03ea998-a2f1-4733-b7e9-78cdf73bd38a", + "name":"頭", + "key":"head", + "color":"#0033CC", + "edges":[ + "195f5852-c516-498b-b392-24513ce3ea67", + "06b5c968-1786-4d75-a719-951e915e5557" + ], + "value":[ + + ] + }, + { + "id":"195f5852-c516-498b-b392-24513ce3ea67", + "name":"右肩", + "key":"right_shoulder", + "color":"#0033CC", + "edges":[ + "b03ea998-a2f1-4733-b7e9-78cdf73bd38a" + ], + "value":[ + + ] + }, + { + "id":"06b5c968-1786-4d75-a719-951e915e5557", + "name":"左肩", + "key":"left_shoulder", + "color":"#0033CC", + "edges":[ + "b03ea998-a2f1-4733-b7e9-78cdf73bd38a" + ], + "value":[ + + ] + } + ], + "createdAt":"2021-11-21T09:59:46.714Z", + "updatedAt":"2021-11-21T09:59:46.714Z" +} +``` + + ### Update Annotation Update an annotation. @@ -869,9 +999,12 @@ client.delete_project(project_id="YOUR_PROJECT_ID") ## Converter -Supporting bbox or polygon annotation type. - ### COCO +support the following annotation types. + +- bbox +- polygon +- pose estimation Get tasks and export as a [COCO format](https://cocodataset.org/#format-data) file. @@ -886,8 +1019,22 @@ Export with specifying output directory. client.export_coco(tasks=tasks, output_dir="YOUR_DIRECTROY") ``` +If you would like to export pose estimation type annotations, please pass annotations. + +```python +project_slug = "YOUR_PROJECT_SLUG" +tasks = client.get_image_tasks(project=project_slug) +annotations = client.get_annotations(project=project_slug) +client.export_coco(tasks=tasks, annotations=annotations, output_dir="YOUR_DIRECTROY") +``` + ### YOLO +support the following annotation types. + +- bbox +- polygon + Get tasks and export as YOLO format files. ```python @@ -908,6 +1055,11 @@ client.export_yolo(tasks=tasks, classes=classes, output_dir="YOUR_DIRECTROY") ### Pascal VOC +support the following annotation types. + +- bbox +- polygon + Get tasks and export as Pascal VOC format files. ```python @@ -917,6 +1069,14 @@ client.export_pascalvoc(tasks) ### labelme +support the following annotation types. + +- bbox +- polygon +- points +- line + + Get tasks and export as labelme format files. ```python diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index f49f80a..4732e0f 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -1024,8 +1024,10 @@ def __get_yolo_format_annotations(self, dataset_folder_path: str) -> dict: def export_coco(self, tasks: list, annotations: list = [], output_dir: str = os.path.join("output", "coco")) -> None: """ Convert tasks to COCO format and export as a file. + If you pass annotations, you can export Pose Estimation type annotations. tasks is a list of tasks. (Required) + annotations is a list of annotations. (Optional) output_dir is output directory(default: output/coco). (Optional) """ coco = converters.to_coco(tasks, annotations) From 15e481cadae959174b9c8441d8e08f7297133b25 Mon Sep 17 00:00:00 2001 From: ryoKaz Date: Wed, 24 Nov 2021 23:13:45 +0900 Subject: [PATCH 3/4] fix readme --- README.md | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b62020b..d0f7179 100644 --- a/README.md +++ b/README.md @@ -221,9 +221,7 @@ Example when the project type is Image - Pose Estimation "title":"jesture", "value":"jesture", "color":"#10c414", - "attributes":[ - - ], + "attributes": [], "keypoints":[ { "name":"頭", @@ -826,9 +824,7 @@ Example when the annotation type is Pose Estimation "value":"jesture", "color":"#10c414", "order":1, - "attributes":[ - - ], + "attributes": [], "keypoints":[ { "id":"b03ea998-a2f1-4733-b7e9-78cdf73bd38a", @@ -839,9 +835,7 @@ Example when the annotation type is Pose Estimation "195f5852-c516-498b-b392-24513ce3ea67", "06b5c968-1786-4d75-a719-951e915e5557" ], - "value":[ - - ] + "value": [] }, { "id":"195f5852-c516-498b-b392-24513ce3ea67", @@ -851,9 +845,7 @@ Example when the annotation type is Pose Estimation "edges":[ "b03ea998-a2f1-4733-b7e9-78cdf73bd38a" ], - "value":[ - - ] + "value": [] }, { "id":"06b5c968-1786-4d75-a719-951e915e5557", @@ -863,9 +855,7 @@ Example when the annotation type is Pose Estimation "edges":[ "b03ea998-a2f1-4733-b7e9-78cdf73bd38a" ], - "value":[ - - ] + "value": [] } ], "createdAt":"2021-11-21T09:59:46.714Z", @@ -1000,7 +990,8 @@ client.delete_project(project_id="YOUR_PROJECT_ID") ## Converter ### COCO -support the following annotation types. + +Support the following annotation types. - bbox - polygon @@ -1030,7 +1021,7 @@ client.export_coco(tasks=tasks, annotations=annotations, output_dir="YOUR_DIRECT ### YOLO -support the following annotation types. +Support the following annotation types. - bbox - polygon @@ -1055,7 +1046,7 @@ client.export_yolo(tasks=tasks, classes=classes, output_dir="YOUR_DIRECTROY") ### Pascal VOC -support the following annotation types. +Support the following annotation types. - bbox - polygon @@ -1069,7 +1060,7 @@ client.export_pascalvoc(tasks) ### labelme -support the following annotation types. +Support the following annotation types. - bbox - polygon @@ -1345,7 +1336,7 @@ for image_file_path in glob.iglob(os.path.join(input_dataset_path, "**/**.jpg"), ### labelme -support the following annotation types. +Support the following annotation types. - bbox - polygon From 090876ed77fbe6eb334f9f4c595a1a7884954897 Mon Sep 17 00:00:00 2001 From: ryoKaz Date: Thu, 25 Nov 2021 17:44:47 +0900 Subject: [PATCH 4/4] fix coco keypoints with points short --- fastlabel/converters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/fastlabel/converters.py b/fastlabel/converters.py index 2455ceb..7b0a4d9 100644 --- a/fastlabel/converters.py +++ b/fastlabel/converters.py @@ -157,6 +157,7 @@ def __get_coco_annotation_keypoints(keypoints: list) -> list: value = keypoint["value"] if not value: coco_annotation_keypoints.extend([0, 0, 0]) + continue # Adjust fastlabel data definition to coco format visibility = 2 if value[2] == 1 else 1 coco_annotation_keypoints.extend([value[0], value[1], visibility])