1111from enum import Enum
1212from glob import glob
1313from shutil import copy2
14- from typing import Optional
14+ from typing import Optional , List , Tuple
1515
1616import ijson
1717import ujson as json
1818from PIL import Image
1919from label_studio_sdk .converter import brush
2020from 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
2122from label_studio_sdk .converter .exports import csv2
2223from label_studio_sdk .converter .utils import (
2324 parse_config ,
3435 convert_annotation_to_yolo_obb ,
3536)
3637from 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
3739
3840logger = logging .getLogger (__name__ )
3941
@@ -109,13 +111,13 @@ class Converter(object):
109111 "description" : "Popular machine learning format used by the COCO dataset for object detection and image "
110112 "segmentation tasks with polygons and rectangles." ,
111113 "link" : "https://labelstud.io/guide/export.html#COCO" ,
112- "tags" : ["image segmentation" , "object detection" ],
114+ "tags" : ["image segmentation" , "object detection" , "keypoints" ],
113115 },
114116 Format .COCO_WITH_IMAGES : {
115117 "title" : "COCO with Images" ,
116118 "description" : "COCO format with images downloaded." ,
117119 "link" : "https://labelstud.io/guide/export.html#COCO" ,
118- "tags" : ["image segmentation" , "object detection" ],
120+ "tags" : ["image segmentation" , "object detection" , "keypoints" ],
119121 },
120122 Format .VOC : {
121123 "title" : "Pascal VOC XML" ,
@@ -128,13 +130,13 @@ class Converter(object):
128130 "description" : "Popular TXT format is created for each image file. Each txt file contains annotations for "
129131 "the corresponding image file, that is object class, object coordinates, height & width." ,
130132 "link" : "https://labelstud.io/guide/export.html#YOLO" ,
131- "tags" : ["image segmentation" , "object detection" ],
133+ "tags" : ["image segmentation" , "object detection" , "keypoints" ],
132134 },
133135 Format .YOLO_WITH_IMAGES : {
134136 "title" : "YOLO with Images" ,
135137 "description" : "YOLO format with images downloaded." ,
136138 "link" : "https://labelstud.io/guide/export.html#YOLO" ,
137- "tags" : ["image segmentation" , "object detection" ],
139+ "tags" : ["image segmentation" , "object detection" , "keypoints" ],
138140 },
139141 Format .YOLO_OBB : {
140142 "title" : "YOLOv8 OBB" ,
@@ -205,6 +207,7 @@ def __init__(
205207 self ._schema = None
206208 self .access_token = access_token
207209 self .hostname = hostname
210+ self .is_keypoints = None
208211
209212 if isinstance (config , dict ):
210213 self ._schema = config
@@ -376,11 +379,14 @@ def _get_supported_formats(self):
376379 and (
377380 "RectangleLabels" in output_tag_types
378381 or "PolygonLabels" in output_tag_types
382+ or "KeyPointLabels" in output_tag_types
379383 )
380384 or "Rectangle" in output_tag_types
381385 and "Labels" in output_tag_types
382386 or "PolygonLabels" in output_tag_types
383387 and "Labels" in output_tag_types
388+ or "KeyPointLabels" in output_tag_types
389+ and "Labels" in output_tag_types
384390 ):
385391 all_formats .remove (Format .COCO .name )
386392 all_formats .remove (Format .COCO_WITH_IMAGES .name )
@@ -522,6 +528,9 @@ def annotation_result_from_task(self, task):
522528 if "original_height" in r :
523529 v ["original_height" ] = r ["original_height" ]
524530 outputs [r ["from_name" ]].append (v )
531+ if self .is_keypoints :
532+ v ['id' ] = r .get ('id' )
533+ v ['parentID' ] = r .get ('parentID' )
525534
526535 data = Converter .get_data (task , outputs , annotation )
527536 if "agreement" in task :
@@ -638,6 +647,7 @@ def add_image(images, width, height, image_id, image_path):
638647 os .makedirs (output_image_dir , exist_ok = True )
639648 images , categories , annotations = [], [], []
640649 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 )
641651 data_key = self ._data_keys [0 ]
642652 item_iterator = (
643653 self .iter_from_dir (input_data )
@@ -703,9 +713,10 @@ def add_image(images, width, height, image_id, image_path):
703713 logger .debug (f'Empty bboxes for { item ["output" ]} ' )
704714 continue
705715
716+ keypoint_labels = []
706717 for label in labels :
707718 category_name = None
708- for key in ["rectanglelabels" , "polygonlabels" , "labels" ]:
719+ for key in ["rectanglelabels" , "polygonlabels" , "keypointlabels" , " labels" ]:
709720 if key in label and len (label [key ]) > 0 :
710721 category_name = label [key ][0 ]
711722 break
@@ -775,11 +786,22 @@ def add_image(images, width, height, image_id, image_path):
775786 "area" : get_polygon_area (x , y ),
776787 }
777788 )
789+ elif "keypointlabels" in label :
790+ keypoint_labels .append (label )
778791 else :
779792 raise ValueError ("Unknown label type" )
780793
781794 if os .getenv ("LABEL_STUDIO_FORCE_ANNOTATOR_EXPORT" ):
782795 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+ ))
783805
784806 with io .open (output_file , mode = "w" , encoding = "utf8" ) as fout :
785807 json .dump (
@@ -846,7 +868,14 @@ def convert_to_yolo(
846868 else :
847869 output_label_dir = os .path .join (output_dir , "labels" )
848870 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 ()
850879 data_key = self ._data_keys [0 ]
851880 item_iterator = (
852881 self .iter_from_dir (input_data )
@@ -923,82 +952,7 @@ def convert_to_yolo(
923952 pass
924953 continue
925954
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 )
1002956 with open (class_file , "w" , encoding = "utf8" ) as f :
1003957 for c in categories :
1004958 f .write (c ["name" ] + "\n " )
0 commit comments