Skip to content

Commit ede99a6

Browse files
Merge pull request #227 from fastlabel/feature/export-yolo
segementationのプロジェクトのyolo形式でのexport
2 parents 32166b8 + 9f4a4f0 commit ede99a6

File tree

5 files changed

+205
-38
lines changed

5 files changed

+205
-38
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2971,6 +2971,7 @@ Support the following annotation types.
29712971

29722972
- bbox
29732973
- polygon
2974+
- segmentation
29742975

29752976
Get tasks and export as YOLO format files.
29762977

@@ -3097,7 +3098,7 @@ for image_file_path in glob.iglob(os.path.join(input_dataset_path, "**/**.jpg"),
30973098

30983099
### YOLO To FastLabel
30993100

3100-
Supported bbox annotation type.
3101+
Supported bbox and segmentation annotation type.
31013102

31023103
Convert annotation file of YOLO format as a Fastlabel format and create task.
31033104

examples/export_yolo.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import fastlabel
2+
3+
client = fastlabel.Client()
4+
5+
project_slug = "YOUR_PROJECT_SLUG"
6+
tasks = client.get_image_tasks(project=project_slug)
7+
8+
client.export_yolo(project=project_slug, tasks=tasks, output_dir="./export_yolo/")

examples/import_yolo.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import glob
2+
import os
3+
import time
4+
5+
import fastlabel
6+
7+
client = fastlabel.Client()
8+
9+
project = "YOUR_PROJECT_SLUG"
10+
11+
input_file_path = "./classes.txt"
12+
input_dataset_path = "./dataset/"
13+
annotations_map = client.convert_yolo_to_fastlabel(
14+
classes_file_path=input_file_path,
15+
dataset_folder_path=input_dataset_path,
16+
project_type="segmentation",
17+
)
18+
for image_file_path in glob.iglob(
19+
os.path.join(input_dataset_path, "**/**.jpg"), recursive=True
20+
):
21+
time.sleep(1)
22+
name = image_file_path.replace(os.path.join(*[input_dataset_path, ""]), "")
23+
file_path = image_file_path
24+
annotations = (
25+
annotations_map.get(name) if annotations_map.get(name) is not None else []
26+
)
27+
task_id = client.create_image_task(
28+
project=project, name=name, file_path=file_path, annotations=annotations
29+
)

fastlabel/__init__.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2968,7 +2968,10 @@ def convert_pascalvoc_to_fastlabel(self, folder_path: str) -> dict:
29682968
return results
29692969

29702970
def convert_yolo_to_fastlabel(
2971-
self, classes_file_path: str, dataset_folder_path: str
2971+
self,
2972+
classes_file_path: str,
2973+
dataset_folder_path: str,
2974+
project_type: str,
29722975
) -> dict:
29732976
"""
29742977
Convert YOLO format to FastLabel format as annotation files.
@@ -3026,12 +3029,20 @@ def convert_yolo_to_fastlabel(
30263029
image_sizes = self.__get_yolo_image_sizes(dataset_folder_path)
30273030
yolo_annotations = self.__get_yolo_format_annotations(dataset_folder_path)
30283031

3029-
return converters.execute_yolo_to_fastlabel(
3030-
classes,
3031-
image_sizes,
3032-
yolo_annotations,
3033-
os.path.join(*[dataset_folder_path, ""]),
3034-
)
3032+
if project_type == "segmentation":
3033+
return converters.execute_segmentation_yolo_to_fastlabel(
3034+
classes,
3035+
image_sizes,
3036+
yolo_annotations,
3037+
os.path.join(*[dataset_folder_path, ""]),
3038+
)
3039+
else:
3040+
return converters.execute_bbox_yolo_to_fastlabel(
3041+
classes,
3042+
image_sizes,
3043+
yolo_annotations,
3044+
os.path.join(*[dataset_folder_path, ""]),
3045+
)
30353046

30363047
def __get_yolo_format_classes(self, classes_file_path: str) -> dict:
30373048
"""

fastlabel/converters.py

Lines changed: 148 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ def __serialize(value: any) -> any:
478478
def to_yolo(project_type: str, tasks: list, classes: list, output_dir: str) -> tuple:
479479
if len(classes) == 0:
480480
coco = to_coco(project_type=project_type, tasks=tasks, output_dir=output_dir)
481-
return __coco2yolo(coco)
481+
return __coco2yolo(project_type, coco)
482482
else:
483483
return __to_yolo(
484484
project_type=project_type,
@@ -488,7 +488,7 @@ def to_yolo(project_type: str, tasks: list, classes: list, output_dir: str) -> t
488488
)
489489

490490

491-
def __coco2yolo(coco: dict) -> tuple:
491+
def __coco2yolo(project_type: str, coco: dict) -> tuple:
492492
categories = coco["categories"]
493493

494494
annos = []
@@ -498,39 +498,82 @@ def __coco2yolo(coco: dict) -> tuple:
498498

499499
# Get objects
500500
objs = []
501-
for annotation in coco["annotations"]:
502-
if image["id"] != annotation["image_id"]:
503-
continue
501+
if project_type == "image_segmentation":
502+
objs = __coco2yolo_segmentation(coco, categories, image, dw, dh)
503+
else:
504+
objs = __coco2yolo_rect(coco, categories, image, dw, dh)
504505

505-
category_index = "0"
506-
for index, category in enumerate(categories):
507-
if category["id"] == annotation["category_id"]:
508-
category_index = str(index)
509-
break
506+
# get annotation
507+
anno = {"filename": image["file_name"], "object": objs}
508+
annos.append(anno)
510509

511-
xmin = annotation["bbox"][0]
512-
ymin = annotation["bbox"][1]
513-
xmax = annotation["bbox"][0] + annotation["bbox"][2]
514-
ymax = annotation["bbox"][1] + annotation["bbox"][3]
510+
return annos, categories
515511

516-
x = (xmin + xmax) / 2
517-
y = (ymin + ymax) / 2
518-
w = xmax - xmin
519-
h = ymax - ymin
520512

521-
x = str(_truncate(x * dw, 7))
522-
y = str(_truncate(y * dh, 7))
523-
w = str(_truncate(w * dw, 7))
524-
h = str(_truncate(h * dh, 7))
513+
def __coco2yolo_rect(
514+
coco: dict, categories: list, image: dict, dw: float, dh: float
515+
) -> list[str]:
516+
objs = []
517+
for annotation in coco["annotations"]:
518+
if image["id"] != annotation["image_id"]:
519+
continue
525520

526-
obj = [category_index, x, y, w, h]
527-
objs.append(" ".join(obj))
521+
category_index = "0"
522+
for index, category in enumerate(categories):
523+
if category["id"] == annotation["category_id"]:
524+
category_index = str(index)
525+
break
526+
xmin = annotation["bbox"][0]
527+
ymin = annotation["bbox"][1]
528+
xmax = annotation["bbox"][0] + annotation["bbox"][2]
529+
ymax = annotation["bbox"][1] + annotation["bbox"][3]
530+
531+
x = (xmin + xmax) / 2
532+
y = (ymin + ymax) / 2
533+
w = xmax - xmin
534+
h = ymax - ymin
535+
536+
x = str(_truncate(x * dw, 7))
537+
y = str(_truncate(y * dh, 7))
538+
w = str(_truncate(w * dw, 7))
539+
h = str(_truncate(h * dh, 7))
540+
541+
obj = [category_index, x, y, w, h]
542+
objs.append(" ".join(obj))
543+
return obj
544+
545+
546+
def __coco2yolo_segmentation(
547+
coco: dict, categories: list, image: dict, dw: float, dh: float
548+
) -> list[str]:
549+
objs = []
550+
for annotation in coco["annotations"]:
551+
if image["id"] != annotation["image_id"]:
552+
continue
528553

529-
# get annotation
530-
anno = {"filename": image["file_name"], "object": objs}
531-
annos.append(anno)
554+
category_index = "0"
555+
for index, category in enumerate(categories):
556+
if category["id"] == annotation["category_id"]:
557+
category_index = str(index)
558+
break
559+
# 座標部分を取得
560+
for coordinates in annotation["segmentation"]:
561+
# 座標を(x, y)のペアに分割し、yoloの小数で表す形式に変換する。
562+
yolo_vertices = [
563+
{
564+
"x": str(_truncate(coordinates[i] * dw, 7)),
565+
"y": str(_truncate(coordinates[i + 1] * dh, 7)),
566+
}
567+
for i in range(0, len(coordinates), 2)
568+
]
532569

533-
return annos, categories
570+
# category_index の後に x, yを順番に足していく。
571+
obj = [category_index]
572+
for v in yolo_vertices:
573+
obj.append(v["x"])
574+
obj.append(v["y"])
575+
objs.append(" ".join(obj))
576+
return objs
534577

535578

536579
def __to_yolo(project_type: str, tasks: list, classes: list, output_dir: str) -> tuple:
@@ -594,6 +637,7 @@ def __get_yolo_annotation(data: dict) -> dict:
594637
if (
595638
annotation_type != AnnotationType.bbox.value
596639
and annotation_type != AnnotationType.polygon.value
640+
and annotation_type != AnnotationType.segmentation.value
597641
):
598642
return None
599643
if not points or len(points) == 0:
@@ -607,8 +651,36 @@ def __get_yolo_annotation(data: dict) -> dict:
607651

608652
dw = 1.0 / data["width"]
609653
dh = 1.0 / data["height"]
654+
if annotation_type == AnnotationType.segmentation.value:
655+
return __segmentation2yolo(value, classes, dw, dh, points)
656+
else:
657+
bbox = __to_bbox(annotation_type, points)
658+
return __bbox2yolo(value, classes, dw, dh, bbox)
659+
660+
661+
def __segmentation2yolo(value: str, classes: list, dw: float, dh: float, points: list):
662+
objs = []
663+
category_index = str(classes.index(value))
664+
for shapes in points:
665+
for coordinates in shapes:
666+
# 座標を(x, y)のペアに分割し、yoloの小数で表す形式に変換する。
667+
yolo_vertices = [
668+
{
669+
"x": str(_truncate(coordinates[i] * dw, 7)),
670+
"y": str(_truncate(coordinates[i + 1] * dh, 7)),
671+
}
672+
for i in range(0, len(coordinates), 2)
673+
]
674+
# category_index の後に x, yを順番に足していく。
675+
obj = [category_index]
676+
for v in yolo_vertices:
677+
obj.append(v["x"])
678+
obj.append(v["y"])
679+
objs.append(" ".join(obj))
680+
return objs
610681

611-
bbox = __to_bbox(annotation_type, points)
682+
683+
def __bbox2yolo(value: str, classes: list, dw: float, dh: float, bbox: list):
612684
xmin = bbox[0]
613685
ymin = bbox[1]
614686
xmax = bbox[0] + bbox[2]
@@ -1089,7 +1161,7 @@ def execute_pascalvoc_to_fastlabel(pascalvoc: dict, file_path: str = None) -> tu
10891161
return (file_name, annotations)
10901162

10911163

1092-
def execute_yolo_to_fastlabel(
1164+
def execute_bbox_yolo_to_fastlabel(
10931165
classes: dict,
10941166
image_sizes: dict,
10951167
yolo_annotations: dict,
@@ -1140,6 +1212,52 @@ def execute_yolo_to_fastlabel(
11401212
return results
11411213

11421214

1215+
def execute_segmentation_yolo_to_fastlabel(
1216+
classes: dict,
1217+
image_sizes: dict,
1218+
yolo_annotations: dict,
1219+
dataset_folder_path: str = None,
1220+
) -> dict:
1221+
results = {}
1222+
for yolo_anno_key in yolo_annotations:
1223+
annotations = []
1224+
for each_image_annotation in yolo_annotations[yolo_anno_key]:
1225+
yolo_class_id = each_image_annotation[0]
1226+
coordinates = each_image_annotation[1:]
1227+
image_width, image_height = image_sizes[yolo_anno_key]["size"]
1228+
1229+
classs_name = classes[str(yolo_class_id)]
1230+
1231+
points = [[[]]]
1232+
# 座標を(x, y)のペアに分割
1233+
vertices = [
1234+
{"x": coordinates[i], "y": coordinates[i + 1]}
1235+
for i in range(0, len(coordinates), 2)
1236+
]
1237+
for vertice in vertices:
1238+
points[0][0].append(round(float(image_width) * float(vertice["x"])))
1239+
points[0][0].append(round(float(image_height) * float(vertice["y"])))
1240+
1241+
annotations.append(
1242+
{
1243+
"value": classs_name,
1244+
"points": points,
1245+
"type": AnnotationType.segmentation.value,
1246+
}
1247+
)
1248+
1249+
file_path = (
1250+
image_sizes[yolo_anno_key]["image_file_path"].replace(
1251+
os.path.join(*[dataset_folder_path, ""]), ""
1252+
)
1253+
if dataset_folder_path
1254+
else image_sizes[yolo_anno_key]["image_file_path"]
1255+
)
1256+
results[file_path] = annotations
1257+
1258+
return results
1259+
1260+
11431261
def __get_annotation_type_by_labelme(shape_type: str) -> str:
11441262
if shape_type == "rectangle":
11451263
return "bbox"

0 commit comments

Comments
 (0)