From 61f85a97fa745570ff7aad0b8ceba385decb11cb Mon Sep 17 00:00:00 2001 From: shuichi Date: Mon, 30 Sep 2024 22:34:03 +0900 Subject: [PATCH 1/9] add yolo export sdk --- fastlabel/converters.py | 120 ++++++++++++++++++++++++++++++---------- 1 file changed, 91 insertions(+), 29 deletions(-) diff --git a/fastlabel/converters.py b/fastlabel/converters.py index ac66ede..fd3b4b6 100644 --- a/fastlabel/converters.py +++ b/fastlabel/converters.py @@ -478,7 +478,7 @@ def __serialize(value: any) -> any: def to_yolo(project_type: str, tasks: list, classes: list, output_dir: str) -> tuple: if len(classes) == 0: coco = to_coco(project_type=project_type, tasks=tasks, output_dir=output_dir) - return __coco2yolo(coco) + return __coco2yolo(project_type, coco) else: return __to_yolo( project_type=project_type, @@ -488,7 +488,7 @@ def to_yolo(project_type: str, tasks: list, classes: list, output_dir: str) -> t ) -def __coco2yolo(coco: dict) -> tuple: +def __coco2yolo(project_type: str, coco: dict) -> tuple: categories = coco["categories"] annos = [] @@ -498,39 +498,75 @@ def __coco2yolo(coco: dict) -> tuple: # Get objects objs = [] - for annotation in coco["annotations"]: - if image["id"] != annotation["image_id"]: - continue + if (project_type == "image_segmentation"): + objs = __coco2yolo_segmentation(coco, categories, image, dw, dh) + else: + objs = __coco2yolo_rect(coco, categories, image, dw, dh) - category_index = "0" - for index, category in enumerate(categories): - if category["id"] == annotation["category_id"]: - category_index = str(index) - break + # get annotation + anno = {"filename": image["file_name"], "object": objs} + annos.append(anno) - xmin = annotation["bbox"][0] - ymin = annotation["bbox"][1] - xmax = annotation["bbox"][0] + annotation["bbox"][2] - ymax = annotation["bbox"][1] + annotation["bbox"][3] + return annos, categories - x = (xmin + xmax) / 2 - y = (ymin + ymax) / 2 - w = xmax - xmin - h = ymax - ymin - x = str(_truncate(x * dw, 7)) - y = str(_truncate(y * dh, 7)) - w = str(_truncate(w * dw, 7)) - h = str(_truncate(h * dh, 7)) +def __coco2yolo_rect(coco: dict, categories: list, image: dict, dw: float, dh: float) -> list[str]: + objs = [] + for annotation in coco["annotations"]: + if image["id"] != annotation["image_id"]: + continue - obj = [category_index, x, y, w, h] - objs.append(" ".join(obj)) + category_index = "0" + for index, category in enumerate(categories): + if category["id"] == annotation["category_id"]: + category_index = str(index) + break + xmin = annotation["bbox"][0] + ymin = annotation["bbox"][1] + xmax = annotation["bbox"][0] + annotation["bbox"][2] + ymax = annotation["bbox"][1] + annotation["bbox"][3] + + x = (xmin + xmax) / 2 + y = (ymin + ymax) / 2 + w = xmax - xmin + h = ymax - ymin + + x = str(_truncate(x * dw, 7)) + y = str(_truncate(y * dh, 7)) + w = str(_truncate(w * dw, 7)) + h = str(_truncate(h * dh, 7)) + + obj = [category_index, x, y, w, h] + objs.append(" ".join(obj)) + return obj + + +def __coco2yolo_segmentation(coco: dict, categories: list, image: dict, dw: float, dh: float) -> list[str]: + objs = [] + for annotation in coco["annotations"]: + if image["id"] != annotation["image_id"]: + continue - # get annotation - anno = {"filename": image["file_name"], "object": objs} - annos.append(anno) + category_index = "0" + for index, category in enumerate(categories): + if category["id"] == annotation["category_id"]: + category_index = str(index) + break + # 座標部分を取得 + for coordinates in annotation["segmentation"]: + # 座標を(x, y)のペアに分割し、yoloの小数で表す形式に変換する。 + yolo_vertices = [ + {"x": str(_truncate(coordinates[i] * dw, 7)), "y": str(_truncate(coordinates[i + 1] * dh, 7))} + for i in range(0, len(coordinates), 2) + ] - return annos, categories + # category_index の後に x, yを順番に足していく。 + obj = [category_index] + for v in yolo_vertices: + obj.append(v["x"]) + obj.append(v["y"]) + objs.append(" ".join(obj)) + return objs def __to_yolo(project_type: str, tasks: list, classes: list, output_dir: str) -> tuple: @@ -594,6 +630,7 @@ def __get_yolo_annotation(data: dict) -> dict: if ( annotation_type != AnnotationType.bbox.value and annotation_type != AnnotationType.polygon.value + and annotation_type != AnnotationType.segmentation.value ): return None if not points or len(points) == 0: @@ -607,8 +644,33 @@ def __get_yolo_annotation(data: dict) -> dict: dw = 1.0 / data["width"] dh = 1.0 / data["height"] + if annotation_type == AnnotationType.segmentation.value: + return __segmentation2yolo(value, classes, dw, dh, points) + else: + bbox = __to_bbox(annotation_type, points) + return __bbox2yolo(value, classes, dw, dh, bbox) + + +def __segmentation2yolo(value: str, classes: list, dw: float, dh: float, points: list): + objs = [] + category_index = str(classes.index(value)) + for shapes in points: + for coordinates in shapes: + # 座標を(x, y)のペアに分割し、yoloの小数で表す形式に変換する。 + yolo_vertices = [ + {"x": str(_truncate(coordinates[i] * dw, 7)), "y": str(_truncate(coordinates[i + 1] * dh, 7))} + for i in range(0, len(coordinates), 2) + ] + # category_index の後に x, yを順番に足していく。 + obj = [category_index] + for v in yolo_vertices: + obj.append(v["x"]) + obj.append(v["y"]) + objs.append(" ".join(obj)) + return objs + - bbox = __to_bbox(annotation_type, points) +def __bbox2yolo(value: str, classes: list, dw: float, dh: float, bbox: list): xmin = bbox[0] ymin = bbox[1] xmax = bbox[0] + bbox[2] From 352f08ad3bbdc4d424e70c0655fd0aa251f78acc Mon Sep 17 00:00:00 2001 From: shuichi Date: Mon, 30 Sep 2024 22:35:49 +0900 Subject: [PATCH 2/9] =?UTF-8?q?example=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/export_yolo.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 examples/export_yolo.py diff --git a/examples/export_yolo.py b/examples/export_yolo.py new file mode 100644 index 0000000..50ab2ab --- /dev/null +++ b/examples/export_yolo.py @@ -0,0 +1,7 @@ +import fastlabel +client = fastlabel.Client() + +project_slug = "YOUR_PROJECT_SLUG" +tasks = client.get_image_tasks(project=project_slug) + +client.export_yolo(project=project_slug, tasks=tasks, output_dir="./export_yolo/") \ No newline at end of file From 234dc43974912e7672f70435d83064b529544436 Mon Sep 17 00:00:00 2001 From: shuichi Date: Mon, 30 Sep 2024 22:38:17 +0900 Subject: [PATCH 3/9] add last line --- examples/export_yolo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/export_yolo.py b/examples/export_yolo.py index 50ab2ab..56e933f 100644 --- a/examples/export_yolo.py +++ b/examples/export_yolo.py @@ -4,4 +4,4 @@ project_slug = "YOUR_PROJECT_SLUG" tasks = client.get_image_tasks(project=project_slug) -client.export_yolo(project=project_slug, tasks=tasks, output_dir="./export_yolo/") \ No newline at end of file +client.export_yolo(project=project_slug, tasks=tasks, output_dir="./export_yolo/") From d3f65edd0c7fb93f1bd813ca76708a701b86d7e3 Mon Sep 17 00:00:00 2001 From: shuichi Date: Mon, 30 Sep 2024 22:44:57 +0900 Subject: [PATCH 4/9] formatter --- examples/export_yolo.py | 1 + fastlabel/converters.py | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/examples/export_yolo.py b/examples/export_yolo.py index 56e933f..d4cc8d9 100644 --- a/examples/export_yolo.py +++ b/examples/export_yolo.py @@ -1,4 +1,5 @@ import fastlabel + client = fastlabel.Client() project_slug = "YOUR_PROJECT_SLUG" diff --git a/fastlabel/converters.py b/fastlabel/converters.py index fd3b4b6..8a4d069 100644 --- a/fastlabel/converters.py +++ b/fastlabel/converters.py @@ -498,7 +498,7 @@ def __coco2yolo(project_type: str, coco: dict) -> tuple: # Get objects objs = [] - if (project_type == "image_segmentation"): + if project_type == "image_segmentation": objs = __coco2yolo_segmentation(coco, categories, image, dw, dh) else: objs = __coco2yolo_rect(coco, categories, image, dw, dh) @@ -510,7 +510,9 @@ def __coco2yolo(project_type: str, coco: dict) -> tuple: return annos, categories -def __coco2yolo_rect(coco: dict, categories: list, image: dict, dw: float, dh: float) -> list[str]: +def __coco2yolo_rect( + coco: dict, categories: list, image: dict, dw: float, dh: float +) -> list[str]: objs = [] for annotation in coco["annotations"]: if image["id"] != annotation["image_id"]: @@ -541,7 +543,9 @@ def __coco2yolo_rect(coco: dict, categories: list, image: dict, dw: float, dh: f return obj -def __coco2yolo_segmentation(coco: dict, categories: list, image: dict, dw: float, dh: float) -> list[str]: +def __coco2yolo_segmentation( + coco: dict, categories: list, image: dict, dw: float, dh: float +) -> list[str]: objs = [] for annotation in coco["annotations"]: if image["id"] != annotation["image_id"]: @@ -556,7 +560,10 @@ def __coco2yolo_segmentation(coco: dict, categories: list, image: dict, dw: floa for coordinates in annotation["segmentation"]: # 座標を(x, y)のペアに分割し、yoloの小数で表す形式に変換する。 yolo_vertices = [ - {"x": str(_truncate(coordinates[i] * dw, 7)), "y": str(_truncate(coordinates[i + 1] * dh, 7))} + { + "x": str(_truncate(coordinates[i] * dw, 7)), + "y": str(_truncate(coordinates[i + 1] * dh, 7)), + } for i in range(0, len(coordinates), 2) ] @@ -658,7 +665,10 @@ def __segmentation2yolo(value: str, classes: list, dw: float, dh: float, points: for coordinates in shapes: # 座標を(x, y)のペアに分割し、yoloの小数で表す形式に変換する。 yolo_vertices = [ - {"x": str(_truncate(coordinates[i] * dw, 7)), "y": str(_truncate(coordinates[i + 1] * dh, 7))} + { + "x": str(_truncate(coordinates[i] * dw, 7)), + "y": str(_truncate(coordinates[i + 1] * dh, 7)), + } for i in range(0, len(coordinates), 2) ] # category_index の後に x, yを順番に足していく。 From 0a4cacc0b9567de525a71a86333c3da99fbd433f Mon Sep 17 00:00:00 2001 From: shuichi Date: Wed, 9 Oct 2024 21:06:18 +0900 Subject: [PATCH 5/9] make a example --- examples/import_yolo.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 examples/import_yolo.py diff --git a/examples/import_yolo.py b/examples/import_yolo.py new file mode 100644 index 0000000..f1194d3 --- /dev/null +++ b/examples/import_yolo.py @@ -0,0 +1,23 @@ +import fastlabel + +client = fastlabel.Client() + +project = "YOUR_PROJECT_SLUG" + +input_file_path = "./classes.txt" +input_dataset_path = "./dataset/" +annotations_map = client.convert_yolo_to_fastlabel( + classes_file_path=input_file_path, + dataset_folder_path=input_dataset_path +) +for image_file_path in glob.iglob(os.path.join(input_dataset_path, "**/**.jpg"), recursive=True): + time.sleep(1) + name = image_file_path.replace(os.path.join(*[input_dataset_path, ""]), "") + file_path = image_file_path + annotations = annotations_map.get(name) if annotations_map.get(name) is not None else [] + task_id = client.create_image_task( + project=project, + name=name, + file_path=file_path, + annotations=annotations + ) \ No newline at end of file From 45421977ce65c81f1af0057f97ebdeafcba4f3ec Mon Sep 17 00:00:00 2001 From: shuichi Date: Wed, 9 Oct 2024 23:14:57 +0900 Subject: [PATCH 6/9] add segmentation yolo import function --- examples/import_yolo.py | 6 +++++- fastlabel/__init__.py | 22 +++++++++++++------ fastlabel/converters.py | 48 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/examples/import_yolo.py b/examples/import_yolo.py index f1194d3..c63e709 100644 --- a/examples/import_yolo.py +++ b/examples/import_yolo.py @@ -1,4 +1,7 @@ import fastlabel +import os +import glob +import time client = fastlabel.Client() @@ -8,7 +11,8 @@ input_dataset_path = "./dataset/" annotations_map = client.convert_yolo_to_fastlabel( classes_file_path=input_file_path, - dataset_folder_path=input_dataset_path + dataset_folder_path=input_dataset_path, + project_type="segmentation" ) for image_file_path in glob.iglob(os.path.join(input_dataset_path, "**/**.jpg"), recursive=True): time.sleep(1) diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index 7ab7291..1dfefdc 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -2890,7 +2890,7 @@ def convert_pascalvoc_to_fastlabel(self, folder_path: str) -> dict: return results def convert_yolo_to_fastlabel( - self, classes_file_path: str, dataset_folder_path: str + self, classes_file_path: str, dataset_folder_path: str, project_type: str, ) -> dict: """ Convert YOLO format to FastLabel format as annotation files. @@ -2948,12 +2948,20 @@ def convert_yolo_to_fastlabel( image_sizes = self.__get_yolo_image_sizes(dataset_folder_path) yolo_annotations = self.__get_yolo_format_annotations(dataset_folder_path) - return converters.execute_yolo_to_fastlabel( - classes, - image_sizes, - yolo_annotations, - os.path.join(*[dataset_folder_path, ""]), - ) + if (project_type == "segmentation"): + return converters.execute_segmentation_yolo_to_fastlabel( + classes, + image_sizes, + yolo_annotations, + os.path.join(*[dataset_folder_path, ""]), + ) + else: + return converters.execute_bbox_yolo_to_fastlabel( + classes, + image_sizes, + yolo_annotations, + os.path.join(*[dataset_folder_path, ""]), + ) def __get_yolo_format_classes(self, classes_file_path: str) -> dict: """ diff --git a/fastlabel/converters.py b/fastlabel/converters.py index 8a4d069..38bed4e 100644 --- a/fastlabel/converters.py +++ b/fastlabel/converters.py @@ -1158,7 +1158,7 @@ def execute_pascalvoc_to_fastlabel(pascalvoc: dict, file_path: str = None) -> tu return (file_name, annotations) -def execute_yolo_to_fastlabel( +def execute_bbox_yolo_to_fastlabel( classes: dict, image_sizes: dict, yolo_annotations: dict, @@ -1209,6 +1209,52 @@ def execute_yolo_to_fastlabel( return results +def execute_segmentation_yolo_to_fastlabel( + classes: dict, + image_sizes: dict, + yolo_annotations: dict, + dataset_folder_path: str = None, +) -> dict: + results = {} + for yolo_anno_key in yolo_annotations: + annotations = [] + for each_image_annotation in yolo_annotations[yolo_anno_key]: + yolo_class_id = each_image_annotation[0] + coordinates = each_image_annotation[1:] + image_width, image_height = image_sizes[yolo_anno_key]["size"] + + classs_name = classes[str(yolo_class_id)] + + points = [[[]]] + # 座標を(x, y)のペアに分割 + vertices = [ + {"x": coordinates[i], "y": coordinates[i + 1]} + for i in range(0, len(coordinates), 2) + ] + for vertice in vertices: + points[0][0].append(round(float(image_width) * float(vertice["x"]))) + points[0][0].append(round(float(image_height) * float(vertice["y"]))) + + annotations.append( + { + "value": classs_name, + "points": points, + "type": AnnotationType.segmentation.value, + } + ) + + file_path = ( + image_sizes[yolo_anno_key]["image_file_path"].replace( + os.path.join(*[dataset_folder_path, ""]), "" + ) + if dataset_folder_path + else image_sizes[yolo_anno_key]["image_file_path"] + ) + results[file_path] = annotations + + return results + + def __get_annotation_type_by_labelme(shape_type: str) -> str: if shape_type == "rectangle": return "bbox" From aa9ad578489d38f296ea73cafe6b9d40b3b29d61 Mon Sep 17 00:00:00 2001 From: shuichi Date: Wed, 9 Oct 2024 23:17:57 +0900 Subject: [PATCH 7/9] add line on end of file --- examples/import_yolo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/import_yolo.py b/examples/import_yolo.py index c63e709..2dd42b6 100644 --- a/examples/import_yolo.py +++ b/examples/import_yolo.py @@ -24,4 +24,4 @@ name=name, file_path=file_path, annotations=annotations - ) \ No newline at end of file + ) From a797fb29e8e86efebe57ff524b17854897b8edc8 Mon Sep 17 00:00:00 2001 From: shuichi Date: Tue, 22 Oct 2024 22:42:22 +0900 Subject: [PATCH 8/9] update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a6bc532..a19d3be 100644 --- a/README.md +++ b/README.md @@ -2914,6 +2914,7 @@ Support the following annotation types. - bbox - polygon +- segmentation Get tasks and export as YOLO format files. @@ -3040,7 +3041,7 @@ for image_file_path in glob.iglob(os.path.join(input_dataset_path, "**/**.jpg"), ### YOLO To FastLabel -Supported bbox annotation type. +Supported bbox and segmentation annotation type. Convert annotation file of YOLO format as a Fastlabel format and create task. From 9f4a4f07ed3a1bdd1b06deb3fdb8553dc21973b1 Mon Sep 17 00:00:00 2001 From: "takahiro.tamenishi" Date: Tue, 17 Dec 2024 14:33:48 +0900 Subject: [PATCH 9/9] reformat black --- examples/import_yolo.py | 20 +++++++++++--------- fastlabel/__init__.py | 7 +++++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/examples/import_yolo.py b/examples/import_yolo.py index 2dd42b6..cea9426 100644 --- a/examples/import_yolo.py +++ b/examples/import_yolo.py @@ -1,8 +1,9 @@ -import fastlabel -import os import glob +import os import time +import fastlabel + client = fastlabel.Client() project = "YOUR_PROJECT_SLUG" @@ -12,16 +13,17 @@ annotations_map = client.convert_yolo_to_fastlabel( classes_file_path=input_file_path, dataset_folder_path=input_dataset_path, - project_type="segmentation" + project_type="segmentation", ) -for image_file_path in glob.iglob(os.path.join(input_dataset_path, "**/**.jpg"), recursive=True): +for image_file_path in glob.iglob( + os.path.join(input_dataset_path, "**/**.jpg"), recursive=True +): time.sleep(1) name = image_file_path.replace(os.path.join(*[input_dataset_path, ""]), "") file_path = image_file_path - annotations = annotations_map.get(name) if annotations_map.get(name) is not None else [] + annotations = ( + annotations_map.get(name) if annotations_map.get(name) is not None else [] + ) task_id = client.create_image_task( - project=project, - name=name, - file_path=file_path, - annotations=annotations + project=project, name=name, file_path=file_path, annotations=annotations ) diff --git a/fastlabel/__init__.py b/fastlabel/__init__.py index 1dfefdc..7c2c17e 100644 --- a/fastlabel/__init__.py +++ b/fastlabel/__init__.py @@ -2890,7 +2890,10 @@ def convert_pascalvoc_to_fastlabel(self, folder_path: str) -> dict: return results def convert_yolo_to_fastlabel( - self, classes_file_path: str, dataset_folder_path: str, project_type: str, + self, + classes_file_path: str, + dataset_folder_path: str, + project_type: str, ) -> dict: """ Convert YOLO format to FastLabel format as annotation files. @@ -2948,7 +2951,7 @@ def convert_yolo_to_fastlabel( image_sizes = self.__get_yolo_image_sizes(dataset_folder_path) yolo_annotations = self.__get_yolo_format_annotations(dataset_folder_path) - if (project_type == "segmentation"): + if project_type == "segmentation": return converters.execute_segmentation_yolo_to_fastlabel( classes, image_sizes,