11
11
from enum import Enum
12
12
from glob import glob
13
13
from shutil import copy2
14
- from typing import Optional
14
+ from typing import Optional , List , Tuple
15
15
16
16
import ijson
17
17
import ujson as json
18
18
from PIL import Image
19
19
from label_studio_sdk .converter import brush
20
20
from label_studio_sdk .converter .audio import convert_to_asr_json_manifest
21
+ from label_studio_sdk .converter .keypoints import process_keypoints_for_coco , build_kp_order , update_categories_for_keypoints , keypoints_in_label_config , get_yolo_categories_for_keypoints
21
22
from label_studio_sdk .converter .exports import csv2
22
23
from label_studio_sdk .converter .utils import (
23
24
parse_config ,
34
35
convert_annotation_to_yolo_obb ,
35
36
)
36
37
from label_studio_sdk ._extensions .label_studio_tools .core .utils .io import get_local_path
38
+ from label_studio_sdk .converter .exports .yolo import process_and_save_yolo_annotations
37
39
38
40
logger = logging .getLogger (__name__ )
39
41
@@ -109,13 +111,13 @@ class Converter(object):
109
111
"description" : "Popular machine learning format used by the COCO dataset for object detection and image "
110
112
"segmentation tasks with polygons and rectangles." ,
111
113
"link" : "https://labelstud.io/guide/export.html#COCO" ,
112
- "tags" : ["image segmentation" , "object detection" ],
114
+ "tags" : ["image segmentation" , "object detection" , "keypoints" ],
113
115
},
114
116
Format .COCO_WITH_IMAGES : {
115
117
"title" : "COCO with Images" ,
116
118
"description" : "COCO format with images downloaded." ,
117
119
"link" : "https://labelstud.io/guide/export.html#COCO" ,
118
- "tags" : ["image segmentation" , "object detection" ],
120
+ "tags" : ["image segmentation" , "object detection" , "keypoints" ],
119
121
},
120
122
Format .VOC : {
121
123
"title" : "Pascal VOC XML" ,
@@ -128,13 +130,13 @@ class Converter(object):
128
130
"description" : "Popular TXT format is created for each image file. Each txt file contains annotations for "
129
131
"the corresponding image file, that is object class, object coordinates, height & width." ,
130
132
"link" : "https://labelstud.io/guide/export.html#YOLO" ,
131
- "tags" : ["image segmentation" , "object detection" ],
133
+ "tags" : ["image segmentation" , "object detection" , "keypoints" ],
132
134
},
133
135
Format .YOLO_WITH_IMAGES : {
134
136
"title" : "YOLO with Images" ,
135
137
"description" : "YOLO format with images downloaded." ,
136
138
"link" : "https://labelstud.io/guide/export.html#YOLO" ,
137
- "tags" : ["image segmentation" , "object detection" ],
139
+ "tags" : ["image segmentation" , "object detection" , "keypoints" ],
138
140
},
139
141
Format .YOLO_OBB : {
140
142
"title" : "YOLOv8 OBB" ,
@@ -205,6 +207,7 @@ def __init__(
205
207
self ._schema = None
206
208
self .access_token = access_token
207
209
self .hostname = hostname
210
+ self .is_keypoints = None
208
211
209
212
if isinstance (config , dict ):
210
213
self ._schema = config
@@ -376,11 +379,14 @@ def _get_supported_formats(self):
376
379
and (
377
380
"RectangleLabels" in output_tag_types
378
381
or "PolygonLabels" in output_tag_types
382
+ or "KeyPointLabels" in output_tag_types
379
383
)
380
384
or "Rectangle" in output_tag_types
381
385
and "Labels" in output_tag_types
382
386
or "PolygonLabels" in output_tag_types
383
387
and "Labels" in output_tag_types
388
+ or "KeyPointLabels" in output_tag_types
389
+ and "Labels" in output_tag_types
384
390
):
385
391
all_formats .remove (Format .COCO .name )
386
392
all_formats .remove (Format .COCO_WITH_IMAGES .name )
@@ -522,6 +528,9 @@ def annotation_result_from_task(self, task):
522
528
if "original_height" in r :
523
529
v ["original_height" ] = r ["original_height" ]
524
530
outputs [r ["from_name" ]].append (v )
531
+ if self .is_keypoints :
532
+ v ['id' ] = r .get ('id' )
533
+ v ['parentID' ] = r .get ('parentID' )
525
534
526
535
data = Converter .get_data (task , outputs , annotation )
527
536
if "agreement" in task :
@@ -638,6 +647,7 @@ def add_image(images, width, height, image_id, image_path):
638
647
os .makedirs (output_image_dir , exist_ok = True )
639
648
images , categories , annotations = [], [], []
640
649
categories , category_name_to_id = self ._get_labels ()
650
+ categories , category_name_to_id = update_categories_for_keypoints (categories , category_name_to_id , self ._schema )
641
651
data_key = self ._data_keys [0 ]
642
652
item_iterator = (
643
653
self .iter_from_dir (input_data )
@@ -703,9 +713,10 @@ def add_image(images, width, height, image_id, image_path):
703
713
logger .debug (f'Empty bboxes for { item ["output" ]} ' )
704
714
continue
705
715
716
+ keypoint_labels = []
706
717
for label in labels :
707
718
category_name = None
708
- for key in ["rectanglelabels" , "polygonlabels" , "labels" ]:
719
+ for key in ["rectanglelabels" , "polygonlabels" , "keypointlabels" , " labels" ]:
709
720
if key in label and len (label [key ]) > 0 :
710
721
category_name = label [key ][0 ]
711
722
break
@@ -775,11 +786,22 @@ def add_image(images, width, height, image_id, image_path):
775
786
"area" : get_polygon_area (x , y ),
776
787
}
777
788
)
789
+ elif "keypointlabels" in label :
790
+ keypoint_labels .append (label )
778
791
else :
779
792
raise ValueError ("Unknown label type" )
780
793
781
794
if os .getenv ("LABEL_STUDIO_FORCE_ANNOTATOR_EXPORT" ):
782
795
annotations [- 1 ].update ({"annotator" : get_annotator (item )})
796
+ if keypoint_labels :
797
+ kp_order = build_kp_order (self ._schema )
798
+ annotations .append (process_keypoints_for_coco (
799
+ keypoint_labels ,
800
+ kp_order ,
801
+ annotation_id = len (annotations ),
802
+ image_id = image_id ,
803
+ category_name_to_id = category_name_to_id ,
804
+ ))
783
805
784
806
with io .open (output_file , mode = "w" , encoding = "utf8" ) as fout :
785
807
json .dump (
@@ -846,7 +868,14 @@ def convert_to_yolo(
846
868
else :
847
869
output_label_dir = os .path .join (output_dir , "labels" )
848
870
os .makedirs (output_label_dir , exist_ok = True )
849
- categories , category_name_to_id = self ._get_labels ()
871
+ is_keypoints = keypoints_in_label_config (self ._schema )
872
+
873
+ if is_keypoints :
874
+ # we use this attribute to add id and parentID to annotation data
875
+ self .is_keypoints = True
876
+ categories , category_name_to_id = get_yolo_categories_for_keypoints (self ._schema )
877
+ else :
878
+ categories , category_name_to_id = self ._get_labels ()
850
879
data_key = self ._data_keys [0 ]
851
880
item_iterator = (
852
881
self .iter_from_dir (input_data )
@@ -923,82 +952,7 @@ def convert_to_yolo(
923
952
pass
924
953
continue
925
954
926
- annotations = []
927
- for label in labels :
928
- category_name = None
929
- category_names = [] # considering multi-label
930
- for key in ["rectanglelabels" , "polygonlabels" , "labels" ]:
931
- if key in label and len (label [key ]) > 0 :
932
- # change to save multi-label
933
- for category_name in label [key ]:
934
- category_names .append (category_name )
935
-
936
- if len (category_names ) == 0 :
937
- logger .debug (
938
- "Unknown label type or labels are empty: " + str (label )
939
- )
940
- continue
941
-
942
- for category_name in category_names :
943
- if category_name not in category_name_to_id :
944
- category_id = len (categories )
945
- category_name_to_id [category_name ] = category_id
946
- categories .append ({"id" : category_id , "name" : category_name })
947
- category_id = category_name_to_id [category_name ]
948
-
949
- if (
950
- "rectanglelabels" in label
951
- or "rectangle" in label
952
- or "labels" in label
953
- ):
954
- # yolo obb
955
- if is_obb :
956
- obb_annotation = convert_annotation_to_yolo_obb (label )
957
- if obb_annotation is None :
958
- continue
959
-
960
- top_left , top_right , bottom_right , bottom_left = (
961
- obb_annotation
962
- )
963
- x1 , y1 = top_left
964
- x2 , y2 = top_right
965
- x3 , y3 = bottom_right
966
- x4 , y4 = bottom_left
967
- annotations .append (
968
- [category_id , x1 , y1 , x2 , y2 , x3 , y3 , x4 , y4 ]
969
- )
970
-
971
- # simple yolo
972
- else :
973
- annotation = convert_annotation_to_yolo (label )
974
- if annotation is None :
975
- continue
976
-
977
- (
978
- x ,
979
- y ,
980
- w ,
981
- h ,
982
- ) = annotation
983
- annotations .append ([category_id , x , y , w , h ])
984
-
985
- elif "polygonlabels" in label or "polygon" in label :
986
- if not ('points' in label ):
987
- continue
988
- points_abs = [(x / 100 , y / 100 ) for x , y in label ["points" ]]
989
- annotations .append (
990
- [category_id ]
991
- + [coord for point in points_abs for coord in point ]
992
- )
993
- else :
994
- raise ValueError (f"Unknown label type { label } " )
995
- with open (label_path , "w" ) as f :
996
- for annotation in annotations :
997
- for idx , l in enumerate (annotation ):
998
- if idx == len (annotation ) - 1 :
999
- f .write (f"{ l } \n " )
1000
- else :
1001
- f .write (f"{ l } " )
955
+ categories , category_name_to_id = process_and_save_yolo_annotations (labels , label_path , category_name_to_id , categories , is_obb , is_keypoints , self ._schema )
1002
956
with open (class_file , "w" , encoding = "utf8" ) as f :
1003
957
for c in categories :
1004
958
f .write (c ["name" ] + "\n " )
0 commit comments