label-studio-sdk 1.0.12__py3-none-any.whl → 1.0.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of label-studio-sdk might be problematic. Click here for more details.
- label_studio_sdk/converter/converter.py +37 -83
- label_studio_sdk/converter/exports/yolo.py +149 -0
- label_studio_sdk/converter/keypoints.py +146 -0
- {label_studio_sdk-1.0.12.dist-info → label_studio_sdk-1.0.13.dist-info}/METADATA +2 -1
- {label_studio_sdk-1.0.12.dist-info → label_studio_sdk-1.0.13.dist-info}/RECORD +7 -5
- {label_studio_sdk-1.0.12.dist-info → label_studio_sdk-1.0.13.dist-info}/LICENSE +0 -0
- {label_studio_sdk-1.0.12.dist-info → label_studio_sdk-1.0.13.dist-info}/WHEEL +0 -0
|
@@ -11,13 +11,14 @@ from datetime import datetime
|
|
|
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,6 +35,7 @@ from label_studio_sdk.converter.utils import (
|
|
|
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 @@ class Converter(object):
|
|
|
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 @@ class Converter(object):
|
|
|
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 @@ class Converter(object):
|
|
|
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 @@ class Converter(object):
|
|
|
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 @@ class Converter(object):
|
|
|
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 @@ class Converter(object):
|
|
|
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 @@ class Converter(object):
|
|
|
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
|
-
|
|
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 @@ class Converter(object):
|
|
|
923
952
|
pass
|
|
924
953
|
continue
|
|
925
954
|
|
|
926
|
-
|
|
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,0 +1,149 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from label_studio_sdk.converter.utils import convert_annotation_to_yolo, convert_annotation_to_yolo_obb
|
|
3
|
+
from label_studio_sdk.converter.keypoints import build_kp_order
|
|
4
|
+
|
|
5
|
+
logger = logging.getLogger(__name__)
|
|
6
|
+
|
|
7
|
+
def process_keypoints_for_yolo(labels, label_path,
|
|
8
|
+
category_name_to_id, categories,
|
|
9
|
+
is_obb, kp_order):
|
|
10
|
+
class_map = {c['name']: c['id'] for c in categories}
|
|
11
|
+
|
|
12
|
+
rectangles = {}
|
|
13
|
+
for item in labels:
|
|
14
|
+
if item['type'].lower() == 'rectanglelabels':
|
|
15
|
+
bbox_id = item['id']
|
|
16
|
+
cls_name = item['rectanglelabels'][0]
|
|
17
|
+
cls_idx = class_map.get(cls_name)
|
|
18
|
+
if cls_idx is None:
|
|
19
|
+
continue
|
|
20
|
+
|
|
21
|
+
x = item['x'] / 100.0
|
|
22
|
+
y = item['y'] / 100.0
|
|
23
|
+
width = item['width'] / 100.0
|
|
24
|
+
height = item['height'] / 100.0
|
|
25
|
+
x_c = x + width / 2.0
|
|
26
|
+
y_c = y + height / 2.0
|
|
27
|
+
|
|
28
|
+
rectangles[bbox_id] = {
|
|
29
|
+
'class_idx': cls_idx,
|
|
30
|
+
'x_center': x_c,
|
|
31
|
+
'y_center': y_c,
|
|
32
|
+
'width': width,
|
|
33
|
+
'height': height,
|
|
34
|
+
'kp_dict': {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for item in labels:
|
|
38
|
+
if item['type'].lower() == 'keypointlabels':
|
|
39
|
+
parent_id = item.get('parentID')
|
|
40
|
+
if parent_id not in rectangles:
|
|
41
|
+
continue
|
|
42
|
+
label_name = item['keypointlabels'][0]
|
|
43
|
+
kp_x = item['x'] / 100.0
|
|
44
|
+
kp_y = item['y'] / 100.0
|
|
45
|
+
rectangles[parent_id]['kp_dict'][label_name] = (kp_x, kp_y, 2) # 2 = visible
|
|
46
|
+
|
|
47
|
+
lines = []
|
|
48
|
+
for rect in rectangles.values():
|
|
49
|
+
base = [
|
|
50
|
+
rect['class_idx'],
|
|
51
|
+
rect['x_center'],
|
|
52
|
+
rect['y_center'],
|
|
53
|
+
rect['width'],
|
|
54
|
+
rect['height']
|
|
55
|
+
]
|
|
56
|
+
keypoints = []
|
|
57
|
+
for k in kp_order:
|
|
58
|
+
keypoints.extend(rect['kp_dict'].get(k, (0.0, 0.0, 0)))
|
|
59
|
+
line = ' '.join(map(str, base + keypoints))
|
|
60
|
+
lines.append(line)
|
|
61
|
+
|
|
62
|
+
with open(label_path, 'w', encoding='utf-8') as f:
|
|
63
|
+
f.write('\n'.join(lines))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def process_and_save_yolo_annotations(labels, label_path, category_name_to_id, categories, is_obb, is_keypoints, label_config):
|
|
67
|
+
if is_keypoints:
|
|
68
|
+
kp_order = build_kp_order(label_config)
|
|
69
|
+
process_keypoints_for_yolo(labels, label_path, category_name_to_id, categories, is_obb, kp_order)
|
|
70
|
+
return categories, category_name_to_id
|
|
71
|
+
|
|
72
|
+
annotations = []
|
|
73
|
+
for label in labels:
|
|
74
|
+
category_name = None
|
|
75
|
+
category_names = [] # considering multi-label
|
|
76
|
+
for key in ["rectanglelabels", "polygonlabels", "labels"]:
|
|
77
|
+
if key in label and len(label[key]) > 0:
|
|
78
|
+
# change to save multi-label
|
|
79
|
+
for category_name in label[key]:
|
|
80
|
+
category_names.append(category_name)
|
|
81
|
+
|
|
82
|
+
if len(category_names) == 0:
|
|
83
|
+
logger.debug(
|
|
84
|
+
"Unknown label type or labels are empty: " + str(label)
|
|
85
|
+
)
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
for category_name in category_names:
|
|
89
|
+
if category_name not in category_name_to_id:
|
|
90
|
+
category_id = len(categories)
|
|
91
|
+
category_name_to_id[category_name] = category_id
|
|
92
|
+
categories.append({"id": category_id, "name": category_name})
|
|
93
|
+
category_id = category_name_to_id[category_name]
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
"rectanglelabels" in label
|
|
97
|
+
or "rectangle" in label
|
|
98
|
+
or "labels" in label
|
|
99
|
+
):
|
|
100
|
+
# yolo obb
|
|
101
|
+
if is_obb:
|
|
102
|
+
obb_annotation = convert_annotation_to_yolo_obb(label)
|
|
103
|
+
if obb_annotation is None:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
top_left, top_right, bottom_right, bottom_left = (
|
|
107
|
+
obb_annotation
|
|
108
|
+
)
|
|
109
|
+
x1, y1 = top_left
|
|
110
|
+
x2, y2 = top_right
|
|
111
|
+
x3, y3 = bottom_right
|
|
112
|
+
x4, y4 = bottom_left
|
|
113
|
+
annotations.append(
|
|
114
|
+
[category_id, x1, y1, x2, y2, x3, y3, x4, y4]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# simple yolo
|
|
118
|
+
else:
|
|
119
|
+
annotation = convert_annotation_to_yolo(label)
|
|
120
|
+
if annotation is None:
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
(
|
|
124
|
+
x,
|
|
125
|
+
y,
|
|
126
|
+
w,
|
|
127
|
+
h,
|
|
128
|
+
) = annotation
|
|
129
|
+
annotations.append([category_id, x, y, w, h])
|
|
130
|
+
|
|
131
|
+
elif "polygonlabels" in label or "polygon" in label:
|
|
132
|
+
if not ('points' in label):
|
|
133
|
+
continue
|
|
134
|
+
points_abs = [(x / 100, y / 100) for x, y in label["points"]]
|
|
135
|
+
annotations.append(
|
|
136
|
+
[category_id]
|
|
137
|
+
+ [coord for point in points_abs for coord in point]
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError(f"Unknown label type {label}")
|
|
141
|
+
with open(label_path, "w") as f:
|
|
142
|
+
for annotation in annotations:
|
|
143
|
+
for idx, l in enumerate(annotation):
|
|
144
|
+
if idx == len(annotation) - 1:
|
|
145
|
+
f.write(f"{l}\n")
|
|
146
|
+
else:
|
|
147
|
+
f.write(f"{l} ")
|
|
148
|
+
|
|
149
|
+
return categories, category_name_to_id
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def keypoints_in_label_config(label_config):
|
|
6
|
+
"""
|
|
7
|
+
Check if the label config contains keypoints.
|
|
8
|
+
:param label_config: Label config in JSON format.
|
|
9
|
+
:return: True if keypoints are present, False otherwise.
|
|
10
|
+
"""
|
|
11
|
+
for cfg in label_config.values():
|
|
12
|
+
if cfg.get("type") == "KeyPointLabels":
|
|
13
|
+
return True
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def update_categories_for_keypoints(categories, category_name_to_id, label_config):
|
|
18
|
+
keypoint_labels = []
|
|
19
|
+
for cfg in label_config.values():
|
|
20
|
+
if cfg.get("type") == "KeyPointLabels":
|
|
21
|
+
keypoint_labels.extend(cfg.get("labels", []))
|
|
22
|
+
keypoint_labels = list(dict.fromkeys(keypoint_labels))
|
|
23
|
+
|
|
24
|
+
non_kp = [cat.copy() for cat in categories if cat["name"] not in keypoint_labels]
|
|
25
|
+
|
|
26
|
+
new_categories = []
|
|
27
|
+
new_mapping = {}
|
|
28
|
+
next_id = 0
|
|
29
|
+
for cat in non_kp:
|
|
30
|
+
cat["id"] = next_id
|
|
31
|
+
new_categories.append(cat)
|
|
32
|
+
new_mapping[cat["name"]] = next_id
|
|
33
|
+
next_id += 1
|
|
34
|
+
|
|
35
|
+
if keypoint_labels:
|
|
36
|
+
merged_id = next_id
|
|
37
|
+
merged_category = {
|
|
38
|
+
"id": merged_id,
|
|
39
|
+
"name": "default",
|
|
40
|
+
"supercategory": "default",
|
|
41
|
+
"keypoints": keypoint_labels,
|
|
42
|
+
"skeleton": []
|
|
43
|
+
}
|
|
44
|
+
new_categories.append(merged_category)
|
|
45
|
+
for kp_name in keypoint_labels:
|
|
46
|
+
new_mapping[kp_name] = merged_id
|
|
47
|
+
|
|
48
|
+
return new_categories, new_mapping
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def build_kp_order(label_config):
|
|
52
|
+
kp_block = {}
|
|
53
|
+
|
|
54
|
+
for tag in label_config.values():
|
|
55
|
+
if tag.get("type") == "KeyPointLabels":
|
|
56
|
+
kp_block.update(tag.get("labels_attrs", {}))
|
|
57
|
+
|
|
58
|
+
pairs, used = [], set()
|
|
59
|
+
|
|
60
|
+
for name, attrs in kp_block.items():
|
|
61
|
+
try:
|
|
62
|
+
idx = int(attrs.get("model_index"))
|
|
63
|
+
except (TypeError, ValueError):
|
|
64
|
+
continue
|
|
65
|
+
if idx in used:
|
|
66
|
+
continue
|
|
67
|
+
pairs.append((idx, name))
|
|
68
|
+
used.add(idx)
|
|
69
|
+
|
|
70
|
+
pairs.sort(key=lambda p: p[0])
|
|
71
|
+
result = [name for _, name in pairs]
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_bbox_coco(keypoints, kp_order):
|
|
76
|
+
xs = [keypoints[3*i] for i in range(len(kp_order)) if keypoints[3*i + 2] > 0]
|
|
77
|
+
ys = [keypoints[3*i + 1] for i in range(len(kp_order)) if keypoints[3*i + 2] > 0]
|
|
78
|
+
if xs and ys:
|
|
79
|
+
x_min = min(xs)
|
|
80
|
+
y_min = min(ys)
|
|
81
|
+
x_max = max(xs)
|
|
82
|
+
y_max = max(ys)
|
|
83
|
+
width = x_max - x_min
|
|
84
|
+
height = y_max - y_min
|
|
85
|
+
bbox = [x_min, y_min, width, height]
|
|
86
|
+
else:
|
|
87
|
+
bbox = [0, 0, 0, 0]
|
|
88
|
+
return bbox
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def process_keypoints_for_coco(keypoint_labels, kp_order, annotation_id, image_id, category_name_to_id):
|
|
92
|
+
keypoints = [0] * (len(kp_order) * 3)
|
|
93
|
+
|
|
94
|
+
for kp in keypoint_labels:
|
|
95
|
+
width, height = kp["original_width"], kp["original_height"]
|
|
96
|
+
x, y = kp['x'] / 100 * width, kp['y'] / 100 * height
|
|
97
|
+
labels = kp.get('keypointlabels', [])
|
|
98
|
+
v = 2 if labels else 0
|
|
99
|
+
for label in labels:
|
|
100
|
+
if label in kp_order:
|
|
101
|
+
idx = kp_order.index(label)
|
|
102
|
+
keypoints[3 * idx] = int(round(x))
|
|
103
|
+
keypoints[3 * idx + 1] = int(round(y))
|
|
104
|
+
keypoints[3 * idx + 2] = v
|
|
105
|
+
|
|
106
|
+
num_keypoints = sum(1 for i in range(len(kp_order)) if keypoints[3*i + 2] > 0)
|
|
107
|
+
|
|
108
|
+
bbox = get_bbox_coco(keypoints, kp_order)
|
|
109
|
+
|
|
110
|
+
category_id = category_name_to_id.get(kp_order[0], 0)
|
|
111
|
+
annotation = {
|
|
112
|
+
'id': annotation_id,
|
|
113
|
+
'image_id': image_id,
|
|
114
|
+
'category_id': category_id,
|
|
115
|
+
'keypoints': keypoints,
|
|
116
|
+
'num_keypoints': num_keypoints,
|
|
117
|
+
'bbox': bbox,
|
|
118
|
+
'iscrowd': 0
|
|
119
|
+
}
|
|
120
|
+
return annotation
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_yolo_categories_for_keypoints(label_config):
|
|
124
|
+
# Get all keypoint labels from the label config
|
|
125
|
+
keypoint_labels = []
|
|
126
|
+
for cfg in label_config.values():
|
|
127
|
+
if cfg.get("type") == "KeyPointLabels":
|
|
128
|
+
keypoint_labels.extend(cfg.get("labels", []))
|
|
129
|
+
keypoint_labels = list(dict.fromkeys(keypoint_labels)) # Remove duplicates
|
|
130
|
+
|
|
131
|
+
# Get all rectangle labels from the label config
|
|
132
|
+
rectangle_labels = []
|
|
133
|
+
for cfg in label_config.values():
|
|
134
|
+
if cfg.get("type") == "RectangleLabels":
|
|
135
|
+
rectangle_labels.extend(cfg.get("labels", []))
|
|
136
|
+
rectangle_labels = list(dict.fromkeys(rectangle_labels)) # Remove duplicates
|
|
137
|
+
|
|
138
|
+
# Create categories for each rectangle label
|
|
139
|
+
categories = []
|
|
140
|
+
category_name_to_id = {}
|
|
141
|
+
|
|
142
|
+
for i, label in enumerate(rectangle_labels):
|
|
143
|
+
categories.append({"id": i, "name": label, "keypoints": keypoint_labels})
|
|
144
|
+
category_name_to_id[label] = i
|
|
145
|
+
|
|
146
|
+
return categories, category_name_to_id
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: label-studio-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.13
|
|
4
4
|
Summary:
|
|
5
5
|
Requires-Python: >=3.9,<4
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -29,6 +29,7 @@ Requires-Dist: jsonschema (>=4.23.0)
|
|
|
29
29
|
Requires-Dist: lxml (>=4.2.5)
|
|
30
30
|
Requires-Dist: nltk (>=3.9.1,<4.0.0)
|
|
31
31
|
Requires-Dist: numpy (>=1.26.4,<3.0.0)
|
|
32
|
+
Requires-Dist: opencv-python (>=4.9.0,<5.0.0)
|
|
32
33
|
Requires-Dist: pandas (>=0.24.0)
|
|
33
34
|
Requires-Dist: pydantic (>=1.9.2)
|
|
34
35
|
Requires-Dist: pydantic-core (>=2.18.2,<3.0.0)
|
|
@@ -50,11 +50,12 @@ label_studio_sdk/comments/client.py,sha256=k359u7Q8YyPtaawoA1FW4dAuYzGVmeGM9rR2G
|
|
|
50
50
|
label_studio_sdk/converter/__init__.py,sha256=qppSJed16HAiZbGons0yVrPRjszuWFia025Rm477q1c,201
|
|
51
51
|
label_studio_sdk/converter/audio.py,sha256=U9oTULkeimodZhIkB16Gl3eJD8YzsbADWxW_r2tPcxw,1905
|
|
52
52
|
label_studio_sdk/converter/brush.py,sha256=jRL3fLl_J06fVEX7Uat31ru0uUZ71C4zrXnX2qOcrIo,13370
|
|
53
|
-
label_studio_sdk/converter/converter.py,sha256=
|
|
53
|
+
label_studio_sdk/converter/converter.py,sha256=Fx3IZpTRLe1_rHPwPYHYeaWsHnK2QJ7RnXpjyabI68Y,49731
|
|
54
54
|
label_studio_sdk/converter/exports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
55
55
|
label_studio_sdk/converter/exports/brush_to_coco.py,sha256=YeVSyZxmXwLbqox7dS3IRuzR1mYTlUhg6YgK4Txq00U,13355
|
|
56
56
|
label_studio_sdk/converter/exports/csv.py,sha256=F4t04tFsg5gBXTZNmkjw_NeEVsobRH_Y_vfFDi7R0Zw,2865
|
|
57
57
|
label_studio_sdk/converter/exports/csv2.py,sha256=9FxcPtIDcuztDF-y4Z7Mm0AgbYUR1oMitYT6BlcOFes,3125
|
|
58
|
+
label_studio_sdk/converter/exports/yolo.py,sha256=0g57qBFDnTEIMJF8oD-vaPYJbtQeecJB74yWISt5EXo,5419
|
|
58
59
|
label_studio_sdk/converter/funsd.py,sha256=QHoa8hzWQLkZQ87e9EgURit9kGGUCgDxoRONcSzmWlw,2544
|
|
59
60
|
label_studio_sdk/converter/imports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
61
|
label_studio_sdk/converter/imports/coco.py,sha256=FDUij8i329XRs3RFZEo-hKWseieyfE7vbQUtHYHpQs8,10312
|
|
@@ -62,6 +63,7 @@ label_studio_sdk/converter/imports/colors.py,sha256=F5_K4FIhOZl6LNEIVT2UU5L8HcmY
|
|
|
62
63
|
label_studio_sdk/converter/imports/label_config.py,sha256=8RT2Jppvi1-Sl3ZNDA1uFyHr2NoU4-gM26S9iAEuQh8,1218
|
|
63
64
|
label_studio_sdk/converter/imports/pathtrack.py,sha256=Xxxbw8fLLHTR57FEjVeeitjh35YbcIh_eVzuw2e5K9w,8096
|
|
64
65
|
label_studio_sdk/converter/imports/yolo.py,sha256=kAJhsUV4ZxOEJE8yk7CvMGf_3w6aslqrj7OUOqHIwbo,8888
|
|
66
|
+
label_studio_sdk/converter/keypoints.py,sha256=KACb3W4aoiM3GBvXehooFS2SqUD82zsLMAXpZgioNyw,4561
|
|
65
67
|
label_studio_sdk/converter/main.py,sha256=gfe5zPV2dnIk4ifG1AT95ExkzOSLzje0EOjnW0oC3q8,6442
|
|
66
68
|
label_studio_sdk/converter/utils.py,sha256=VshPBwZLu2VPIGVsShKAkZwB_zKz0VvMkNRCwWeEqWg,18702
|
|
67
69
|
label_studio_sdk/core/__init__.py,sha256=-t9txgeQZL_1FDw_08GEoj4ft1Cn9Dti6X0Drsadlr0,1519
|
|
@@ -362,7 +364,7 @@ label_studio_sdk/workspaces/members/client.py,sha256=IVM52Yq_9zMQ3TUHT0AkZ5BTQ9a
|
|
|
362
364
|
label_studio_sdk/workspaces/members/types/__init__.py,sha256=ZIa_rd7d6K9ZITjTU6fptyGgvjNDySksJ7Rbn4wyhD4,252
|
|
363
365
|
label_studio_sdk/workspaces/members/types/members_create_response.py,sha256=7Hp5FSWm4xR5ZOEmEIglq5HYtM9KWZZBDp87jw7jYFg,668
|
|
364
366
|
label_studio_sdk/workspaces/members/types/members_list_response_item.py,sha256=DIc5DJoVahI9olBis_iFgOJrAf05m2fCE8g4R5ZeDko,712
|
|
365
|
-
label_studio_sdk-1.0.
|
|
366
|
-
label_studio_sdk-1.0.
|
|
367
|
-
label_studio_sdk-1.0.
|
|
368
|
-
label_studio_sdk-1.0.
|
|
367
|
+
label_studio_sdk-1.0.13.dist-info/LICENSE,sha256=ymVrFcHiJGjHeY30NWZgdV-xzNEtfuC63oK9ZeMDjhs,11341
|
|
368
|
+
label_studio_sdk-1.0.13.dist-info/METADATA,sha256=-1jBn5dwOCPtA4yi0RN5kq7HY373qPlf5dSWNP1r7YU,6033
|
|
369
|
+
label_studio_sdk-1.0.13.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
370
|
+
label_studio_sdk-1.0.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|