label-studio-sdk 1.0.12__py3-none-any.whl → 1.0.14__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/_extensions/label_studio_tools/core/utils/io.py +127 -2
- 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/core/client_wrapper.py +1 -1
- label_studio_sdk/label_interface/interface.py +15 -0
- label_studio_sdk/label_interface/object_tags.py +6 -1
- label_studio_sdk/tokens/client_ext.py +20 -3
- {label_studio_sdk-1.0.12.dist-info → label_studio_sdk-1.0.14.dist-info}/METADATA +2 -1
- {label_studio_sdk-1.0.12.dist-info → label_studio_sdk-1.0.14.dist-info}/RECORD +12 -10
- {label_studio_sdk-1.0.12.dist-info → label_studio_sdk-1.0.14.dist-info}/LICENSE +0 -0
- {label_studio_sdk-1.0.12.dist-info → label_studio_sdk-1.0.14.dist-info}/WHEEL +0 -0
|
@@ -3,6 +3,7 @@ import io
|
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
5
|
import shutil
|
|
6
|
+
import base64
|
|
6
7
|
from contextlib import contextmanager
|
|
7
8
|
from tempfile import mkdtemp
|
|
8
9
|
from urllib.parse import urlparse
|
|
@@ -201,7 +202,7 @@ def download_and_cache(
|
|
|
201
202
|
|
|
202
203
|
# local storage: /data/local-files?d=dir/1.jpg => 1.jpg
|
|
203
204
|
if is_local_storage_file:
|
|
204
|
-
url_filename = os.path.basename(url.split(
|
|
205
|
+
url_filename = os.path.basename(url.split("?d=")[1])
|
|
205
206
|
# cloud storage: s3://bucket/1.jpg => 1.jpg
|
|
206
207
|
elif is_cloud_storage_file:
|
|
207
208
|
url_filename = os.path.basename(url)
|
|
@@ -213,7 +214,11 @@ def download_and_cache(
|
|
|
213
214
|
filepath = os.path.join(cache_dir, url_hash + "__" + url_filename)
|
|
214
215
|
|
|
215
216
|
if not os.path.exists(filepath):
|
|
216
|
-
logger.info(
|
|
217
|
+
logger.info(
|
|
218
|
+
"Download {url} to {filepath}. download_resources: {download_resources}".format(
|
|
219
|
+
url=url, filepath=filepath, download_resources=download_resources
|
|
220
|
+
)
|
|
221
|
+
)
|
|
217
222
|
if download_resources:
|
|
218
223
|
headers = {
|
|
219
224
|
# avoid requests.exceptions.HTTPError: 403 Client Error: Forbidden. Please comply with the User-Agent policy:
|
|
@@ -256,3 +261,123 @@ def get_all_files_from_dir(d):
|
|
|
256
261
|
if os.path.isfile(filepath):
|
|
257
262
|
out.append(filepath)
|
|
258
263
|
return out
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def get_base64_content(
|
|
267
|
+
url,
|
|
268
|
+
hostname=None,
|
|
269
|
+
access_token=None,
|
|
270
|
+
task_id=None,
|
|
271
|
+
):
|
|
272
|
+
"""This helper function is used to download a file and return its base64 representation without saving to filesystem.
|
|
273
|
+
|
|
274
|
+
:param url: File URL to download, it can be a uploaded file, local storage, cloud storage file or just http(s) url
|
|
275
|
+
:param hostname: Label Studio Hostname, it will be used for uploaded files, local storage files and cloud storage files
|
|
276
|
+
if not provided, it will be taken from LABEL_STUDIO_URL env variable
|
|
277
|
+
:param access_token: Label Studio access token, it will be used for uploaded files, local storage files and cloud storage files
|
|
278
|
+
if not provided, it will be taken from LABEL_STUDIO_API_KEY env variable
|
|
279
|
+
:param task_id: Label Studio Task ID, required for cloud storage files
|
|
280
|
+
because the URL will be rebuilt to `{hostname}/tasks/{task_id}/presign/?fileuri={url}`
|
|
281
|
+
|
|
282
|
+
:return: base64 encoded file content
|
|
283
|
+
"""
|
|
284
|
+
# get environment variables
|
|
285
|
+
hostname = (
|
|
286
|
+
hostname
|
|
287
|
+
or os.getenv("LABEL_STUDIO_URL", "")
|
|
288
|
+
or os.getenv("LABEL_STUDIO_HOST", "")
|
|
289
|
+
)
|
|
290
|
+
access_token = (
|
|
291
|
+
access_token
|
|
292
|
+
or os.getenv("LABEL_STUDIO_API_KEY", "")
|
|
293
|
+
or os.getenv("LABEL_STUDIO_ACCESS_TOKEN", "")
|
|
294
|
+
)
|
|
295
|
+
if "localhost" in hostname:
|
|
296
|
+
logger.warning(
|
|
297
|
+
f"Using `localhost` ({hostname}) in LABEL_STUDIO_URL, "
|
|
298
|
+
f"`localhost` is not accessible inside of docker containers. "
|
|
299
|
+
f"You can check your IP with utilities like `ifconfig` and set it as LABEL_STUDIO_URL."
|
|
300
|
+
)
|
|
301
|
+
if hostname and not (
|
|
302
|
+
hostname.startswith("http://") or hostname.startswith("https://")
|
|
303
|
+
):
|
|
304
|
+
raise ValueError(
|
|
305
|
+
f"Invalid hostname in LABEL_STUDIO_URL: {hostname}. "
|
|
306
|
+
"Please provide full URL starting with protocol (http:// or https://)."
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# fix file upload url
|
|
310
|
+
if url.startswith("upload") or url.startswith("/upload"):
|
|
311
|
+
url = "/data" + ("" if url.startswith("/") else "/") + url
|
|
312
|
+
|
|
313
|
+
is_uploaded_file = url.startswith("/data/upload")
|
|
314
|
+
is_local_storage_file = url.startswith("/data/") and "?d=" in url
|
|
315
|
+
is_cloud_storage_file = (
|
|
316
|
+
url.startswith("s3:") or url.startswith("gs:") or url.startswith("azure-blob:")
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Local storage file: try to load locally
|
|
320
|
+
if is_local_storage_file:
|
|
321
|
+
filepath = url.split("?d=")[1]
|
|
322
|
+
filepath = safe_build_path(LOCAL_FILES_DOCUMENT_ROOT, filepath)
|
|
323
|
+
if os.path.exists(filepath):
|
|
324
|
+
logger.debug(
|
|
325
|
+
f"Local Storage file path exists locally, read content directly: {filepath}"
|
|
326
|
+
)
|
|
327
|
+
with open(filepath, "rb") as f:
|
|
328
|
+
return base64.b64encode(f.read()).decode("utf-8")
|
|
329
|
+
|
|
330
|
+
# Upload or Local Storage file
|
|
331
|
+
if is_uploaded_file or is_local_storage_file or is_cloud_storage_file:
|
|
332
|
+
# hostname check
|
|
333
|
+
if not hostname:
|
|
334
|
+
raise FileNotFoundError(
|
|
335
|
+
f"Can't resolve url, hostname not provided: {url}. "
|
|
336
|
+
"You can set LABEL_STUDIO_URL environment variable to use it as a hostname."
|
|
337
|
+
)
|
|
338
|
+
# uploaded and local storage file
|
|
339
|
+
elif is_uploaded_file or is_local_storage_file:
|
|
340
|
+
url = concat_urls(hostname, url)
|
|
341
|
+
logger.info("Resolving url using hostname [" + hostname + "]: " + url)
|
|
342
|
+
# s3, gs, azure-blob file
|
|
343
|
+
elif is_cloud_storage_file:
|
|
344
|
+
if task_id is None:
|
|
345
|
+
raise Exception(
|
|
346
|
+
"Label Studio Task ID is required for cloud storage files"
|
|
347
|
+
)
|
|
348
|
+
url = concat_urls(hostname, f"/tasks/{task_id}/presign/?fileuri={url}")
|
|
349
|
+
logger.info(
|
|
350
|
+
"Cloud storage file: Resolving url using hostname ["
|
|
351
|
+
+ hostname
|
|
352
|
+
+ "]: "
|
|
353
|
+
+ url
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# check access token
|
|
357
|
+
if not access_token:
|
|
358
|
+
raise FileNotFoundError(
|
|
359
|
+
"To access uploaded and local storage files you have to "
|
|
360
|
+
"set LABEL_STUDIO_API_KEY environment variable."
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# Download the content but don't save to filesystem
|
|
364
|
+
headers = {
|
|
365
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
# check if url matches hostname - then uses access token to this Label Studio instance
|
|
369
|
+
parsed_url = urlparse(url)
|
|
370
|
+
if access_token and hostname and parsed_url.netloc == urlparse(hostname).netloc:
|
|
371
|
+
headers["Authorization"] = "Token " + access_token
|
|
372
|
+
logger.debug("Authorization token is used for get_base64_content")
|
|
373
|
+
|
|
374
|
+
try:
|
|
375
|
+
r = requests.get(url, headers=headers, verify=VERIFY_SSL)
|
|
376
|
+
r.raise_for_status()
|
|
377
|
+
return base64.b64encode(r.content).decode("utf-8")
|
|
378
|
+
except requests.exceptions.SSLError as e:
|
|
379
|
+
logger.error(
|
|
380
|
+
f"SSL error during requests.get('{url}'): {e}\n"
|
|
381
|
+
f"Try to set VERIFY_SSL=False in environment variables to bypass SSL verification."
|
|
382
|
+
)
|
|
383
|
+
raise e
|
|
@@ -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
|
|
@@ -18,7 +18,7 @@ class BaseClientWrapper:
|
|
|
18
18
|
|
|
19
19
|
# even in the async case, refreshing access token (when the existing one is expired) should be sync
|
|
20
20
|
from ..tokens.client_ext import TokensClientExt
|
|
21
|
-
self._tokens_client = TokensClientExt(base_url=base_url, api_key=api_key)
|
|
21
|
+
self._tokens_client = TokensClientExt(base_url=base_url, api_key=api_key, client_wrapper=self)
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
def get_timeout(self) -> typing.Optional[float]:
|
|
@@ -8,9 +8,11 @@ import re
|
|
|
8
8
|
import json
|
|
9
9
|
import jsonschema
|
|
10
10
|
|
|
11
|
+
from functools import cached_property
|
|
11
12
|
from typing import Dict, Optional, List, Tuple, Any, Callable, Union
|
|
12
13
|
from pydantic import BaseModel
|
|
13
14
|
|
|
15
|
+
|
|
14
16
|
# from typing import Dict, Optional, List, Tuple, Any
|
|
15
17
|
from collections import defaultdict, OrderedDict
|
|
16
18
|
from lxml import etree
|
|
@@ -517,6 +519,19 @@ class LabelInterface:
|
|
|
517
519
|
|
|
518
520
|
return lst
|
|
519
521
|
|
|
522
|
+
@cached_property
|
|
523
|
+
def ner_tags(self):
|
|
524
|
+
return self.find_tags('controls', lambda t: t.tag.lower() in ('labels', 'hypertextlabels'))
|
|
525
|
+
|
|
526
|
+
@cached_property
|
|
527
|
+
def image_tags(self):
|
|
528
|
+
return self.find_tags('objects', lambda t: t.tag.lower() == 'image')
|
|
529
|
+
|
|
530
|
+
@cached_property
|
|
531
|
+
def pdf_tags(self):
|
|
532
|
+
return self.find_tags('objects', lambda t: t.tag.lower() == 'pdf')
|
|
533
|
+
|
|
534
|
+
|
|
520
535
|
def load_task(self, task):
|
|
521
536
|
"""Loads a task and substitutes the value in each object tag
|
|
522
537
|
with actual data from the task, returning a copy of the
|
|
@@ -189,12 +189,17 @@ class AudioTag(ObjectTag):
|
|
|
189
189
|
|
|
190
190
|
|
|
191
191
|
class ImageTag(ObjectTag):
|
|
192
|
-
""" """
|
|
192
|
+
"""Image tag"""
|
|
193
193
|
tag: str = "Image"
|
|
194
194
|
|
|
195
195
|
def _generate_example(self, examples, only_urls=False):
|
|
196
196
|
""" """
|
|
197
197
|
return examples.get("Image")
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def is_image_list(self):
|
|
201
|
+
"""Check if the tag is an image list, i.e. it has a valueList attribute that accepts list of images"""
|
|
202
|
+
return bool(self.attr.get("valueList")) if self.attr else False
|
|
198
203
|
|
|
199
204
|
|
|
200
205
|
class TableTag(ObjectTag):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import threading
|
|
2
2
|
import typing
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
+
import inspect
|
|
4
5
|
|
|
5
6
|
import httpx
|
|
6
7
|
import jwt
|
|
@@ -12,9 +13,10 @@ from ..types.access_token_response import AccessTokenResponse
|
|
|
12
13
|
class TokensClientExt:
|
|
13
14
|
"""Client for managing authentication tokens."""
|
|
14
15
|
|
|
15
|
-
def __init__(self, base_url: str, api_key: str):
|
|
16
|
+
def __init__(self, base_url: str, api_key: str, client_wrapper=None):
|
|
16
17
|
self._base_url = base_url
|
|
17
18
|
self._api_key = api_key
|
|
19
|
+
self._client_wrapper = client_wrapper
|
|
18
20
|
self._use_legacy_token = not self._is_valid_jwt_token(api_key, raise_if_expired=True)
|
|
19
21
|
|
|
20
22
|
# cache state for access token when using jwt-based api_key
|
|
@@ -78,9 +80,24 @@ class TokensClientExt:
|
|
|
78
80
|
|
|
79
81
|
def refresh(self) -> AccessTokenResponse:
|
|
80
82
|
"""Refresh the access token and return the token response."""
|
|
81
|
-
# We don't do this often, just use a separate httpx client for simplicity here
|
|
83
|
+
# We don't do this often, just use a separate sync httpx client for simplicity here
|
|
82
84
|
# (avoids complicated state management and sync vs async handling)
|
|
83
|
-
with
|
|
85
|
+
# Create a new client with the same parameters as the existing one
|
|
86
|
+
existing_client = self._client_wrapper.httpx_client.httpx_client
|
|
87
|
+
|
|
88
|
+
# Get all parameters from httpx.Client.__init__
|
|
89
|
+
client_params = {}
|
|
90
|
+
sig = inspect.signature(httpx.Client.__init__)
|
|
91
|
+
for param_name in sig.parameters:
|
|
92
|
+
if param_name != 'self': # Skip 'self' parameter
|
|
93
|
+
try:
|
|
94
|
+
value = getattr(existing_client, param_name, None)
|
|
95
|
+
if value is not None:
|
|
96
|
+
client_params[param_name] = value
|
|
97
|
+
except AttributeError:
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
with httpx.Client(**client_params) as sync_client:
|
|
84
101
|
response = sync_client.request(
|
|
85
102
|
method="POST",
|
|
86
103
|
url=f"{self._base_url}/api/token/refresh/",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: label-studio-sdk
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.14
|
|
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)
|
|
@@ -6,7 +6,7 @@ label_studio_sdk/_extensions/label_studio_tools/core/__init__.py,sha256=47DEQpj8
|
|
|
6
6
|
label_studio_sdk/_extensions/label_studio_tools/core/label_config.py,sha256=P1S7dPjFkqF2zIQzk11iljhharrUc9qQRM_rUN38iJQ,6406
|
|
7
7
|
label_studio_sdk/_extensions/label_studio_tools/core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
label_studio_sdk/_extensions/label_studio_tools/core/utils/exceptions.py,sha256=JxaXUMghUp1YvL--s8KFC4mCHVbV39giE3kSBHCmuFU,66
|
|
9
|
-
label_studio_sdk/_extensions/label_studio_tools/core/utils/io.py,sha256=
|
|
9
|
+
label_studio_sdk/_extensions/label_studio_tools/core/utils/io.py,sha256=NJHLJA8Q93_MLmo_Yx9F4_Z-kkWCZmF_Ahi3MtDQCPo,14800
|
|
10
10
|
label_studio_sdk/_extensions/label_studio_tools/core/utils/json_schema.py,sha256=_Lg3DxhRqGhzlk3egGUDufx-iaoEWec19upZKp-Cwic,3378
|
|
11
11
|
label_studio_sdk/_extensions/label_studio_tools/core/utils/params.py,sha256=ZSUb-IXG5OcPQ7pJ8NDRLon-cMxnjVq6XtinxvTuJso,1244
|
|
12
12
|
label_studio_sdk/_extensions/label_studio_tools/etl/__init__.py,sha256=SdN7JGLJ1araqbx-nL2fVdhm6E6CNyru-vWVs6sMswI,31
|
|
@@ -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,11 +63,12 @@ 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
|
|
68
70
|
label_studio_sdk/core/api_error.py,sha256=RE8LELok2QCjABadECTvtDp7qejA1VmINCh6TbqPwSE,426
|
|
69
|
-
label_studio_sdk/core/client_wrapper.py,sha256=
|
|
71
|
+
label_studio_sdk/core/client_wrapper.py,sha256=lAGxJnIC7HMfysZS9sLHX1lnc82jw_n3eo_pTeljlFc,2230
|
|
70
72
|
label_studio_sdk/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
|
|
71
73
|
label_studio_sdk/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
|
|
72
74
|
label_studio_sdk/core/http_client.py,sha256=siUQ6UV0ARZALlxubqWSSAAPC9B4VW8y6MGlHStfaeo,19552
|
|
@@ -155,9 +157,9 @@ label_studio_sdk/label_interface/base.py,sha256=NCgY7ntk0WSc9O9iXu3g37-CxbZgCx_W
|
|
|
155
157
|
label_studio_sdk/label_interface/control_tags.py,sha256=qLe4gsRxvppuNtrxfmgZHFX1ahM-XhePlrchZfnJiL0,30141
|
|
156
158
|
label_studio_sdk/label_interface/create.py,sha256=c3h5_FF4u5J62_mqq1oK2mjqXL-I1559C6MfoxkgO6s,6993
|
|
157
159
|
label_studio_sdk/label_interface/data_examples.json,sha256=uCYvCtMIxPi1-jLlFhwJPh01tLyMIRwTjINeAeW-JzE,8195
|
|
158
|
-
label_studio_sdk/label_interface/interface.py,sha256=
|
|
160
|
+
label_studio_sdk/label_interface/interface.py,sha256=nEC_RQJ9VCCtYRKqJ7AYhggbLqvTKvJkPIRTQeIm8vc,45959
|
|
159
161
|
label_studio_sdk/label_interface/label_tags.py,sha256=nWEo21Gd8IPzIO72UqraLrChIbvrSMCA_eEhzYGnGCc,2282
|
|
160
|
-
label_studio_sdk/label_interface/object_tags.py,sha256=
|
|
162
|
+
label_studio_sdk/label_interface/object_tags.py,sha256=9k3DEYEh7aXSLh2JjH-SWNVupP1qgwHFte85Ix7-4dQ,8944
|
|
161
163
|
label_studio_sdk/label_interface/objects.py,sha256=V1Spp0S9qE7iA-5kPCi0QyHrJ80Du9BUuYMsQUAQqc0,1535
|
|
162
164
|
label_studio_sdk/label_interface/region.py,sha256=th39WeQk8ypi-4krEpsW0BZnoygu4XgvP4w7NkRQp2M,1755
|
|
163
165
|
label_studio_sdk/ml/__init__.py,sha256=J4ncAcAOU_qriOx_Im9eFmXyupKM19SXMcpMcXSmw-I,455
|
|
@@ -213,7 +215,7 @@ label_studio_sdk/tasks/types/tasks_list_request_fields.py,sha256=5YXxQgyzoaL0QjS
|
|
|
213
215
|
label_studio_sdk/tasks/types/tasks_list_response.py,sha256=j1pNluAWQOQ8-d9YXQyRQAefnrl8uLQEB7_L55Z8DME,1136
|
|
214
216
|
label_studio_sdk/tokens/__init__.py,sha256=FTtvy8EDg9nNNg9WCatVgKTRYV8-_v1roeGPAKoa_pw,65
|
|
215
217
|
label_studio_sdk/tokens/client.py,sha256=SvBcKXIsrTihMJC72Ifxv0U1N3gtLGz3JxqdXYA_hD4,19101
|
|
216
|
-
label_studio_sdk/tokens/client_ext.py,sha256=
|
|
218
|
+
label_studio_sdk/tokens/client_ext.py,sha256=LHy29mBizrxVs_xOVMiIJwNsQKcOjS8V8LWfWMdAdYU,4730
|
|
217
219
|
label_studio_sdk/types/__init__.py,sha256=fQykjzHpX04ftslk5I_hWSJQ_H9Kd8XJAmSv18EVOIc,8905
|
|
218
220
|
label_studio_sdk/types/access_token_response.py,sha256=RV9FqkIiFR_9kmKueB-KiqjVyneiqUkMVueAlk5fUyc,624
|
|
219
221
|
label_studio_sdk/types/annotation.py,sha256=AnHm2VjMasWZsaNXVSUzLYbpYrmM4NPZgWQh7WGa6ZQ,3157
|
|
@@ -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.14.dist-info/LICENSE,sha256=ymVrFcHiJGjHeY30NWZgdV-xzNEtfuC63oK9ZeMDjhs,11341
|
|
368
|
+
label_studio_sdk-1.0.14.dist-info/METADATA,sha256=v1D-HsVQxvhu1dESF077Nr-mtaIylYb1KQx6pOxoqww,6033
|
|
369
|
+
label_studio_sdk-1.0.14.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
370
|
+
label_studio_sdk-1.0.14.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|