labelr 0.10.0__py3-none-any.whl → 0.11.0__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.
@@ -7,25 +7,39 @@ from typing import Annotated, Optional
7
7
  import typer
8
8
  from openfoodfacts.utils import get_logger
9
9
 
10
- from ..config import LABEL_STUDIO_DEFAULT_URL
10
+ from . import typer_description
11
+ from ..config import config
11
12
 
12
13
  app = typer.Typer()
13
14
 
14
15
  logger = get_logger(__name__)
15
16
 
16
17
 
18
+ def check_label_studio_api_key(api_key: str | None):
19
+ if not api_key:
20
+ raise typer.BadParameter(
21
+ "Label Studio API key not provided. Please provide it with the "
22
+ "--api-key option or set the LABELR_LABEL_STUDIO_API_KEY environment variable."
23
+ )
24
+
25
+
17
26
  @app.command()
18
27
  def create(
19
- api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
20
28
  title: Annotated[str, typer.Option(help="Project title")],
21
29
  config_file: Annotated[
22
30
  Path, typer.Option(help="Path to label config file", file_okay=True)
23
31
  ],
24
- label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
32
+ api_key: Annotated[
33
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
34
+ ] = config.label_studio_api_key,
35
+ label_studio_url: Annotated[
36
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
37
+ ] = config.label_studio_url,
25
38
  ):
26
39
  """Create a new Label Studio project."""
27
40
  from label_studio_sdk.client import LabelStudio
28
41
 
42
+ check_label_studio_api_key(api_key)
29
43
  ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
30
44
  label_config = config_file.read_text()
31
45
 
@@ -35,7 +49,6 @@ def create(
35
49
 
36
50
  @app.command()
37
51
  def import_data(
38
- api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
39
52
  project_id: Annotated[int, typer.Option(help="Label Studio Project ID")],
40
53
  dataset_path: Annotated[
41
54
  Path,
@@ -43,8 +56,15 @@ def import_data(
43
56
  help="Path to the Label Studio dataset JSONL file", file_okay=True
44
57
  ),
45
58
  ],
46
- label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
47
- batch_size: int = 25,
59
+ api_key: Annotated[
60
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
61
+ ] = config.label_studio_api_key,
62
+ label_studio_url: Annotated[
63
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
64
+ ] = config.label_studio_url,
65
+ batch_size: Annotated[
66
+ int, typer.Option(help="Number of tasks to import as a single batch")
67
+ ] = 25,
48
68
  ):
49
69
  """Import tasks from a dataset file to a Label Studio project.
50
70
 
@@ -56,6 +76,7 @@ def import_data(
56
76
  import tqdm
57
77
  from label_studio_sdk.client import LabelStudio
58
78
 
79
+ check_label_studio_api_key(api_key)
59
80
  ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
60
81
 
61
82
  with dataset_path.open("rt") as f:
@@ -67,12 +88,17 @@ def import_data(
67
88
 
68
89
  @app.command()
69
90
  def update_prediction(
70
- api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
71
91
  project_id: Annotated[int, typer.Option(help="Label Studio project ID")],
72
- label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
92
+ api_key: Annotated[
93
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
94
+ ] = config.label_studio_api_key,
95
+ label_studio_url: Annotated[
96
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
97
+ ] = config.label_studio_url,
73
98
  ):
74
99
  from label_studio_sdk.client import LabelStudio
75
100
 
101
+ check_label_studio_api_key(api_key)
76
102
  ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
77
103
 
78
104
  for task in ls.tasks.list(project=project_id, fields="all"):
@@ -91,16 +117,7 @@ def add_split(
91
117
  train_split: Annotated[
92
118
  float, typer.Option(help="fraction of samples to add in train split")
93
119
  ],
94
- api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
95
120
  project_id: Annotated[int, typer.Option(help="Label Studio project ID")],
96
- split_name: Annotated[
97
- Optional[str],
98
- typer.Option(
99
- help="name of the split associated "
100
- "with the task ID file. If --task-id-file is not provided, "
101
- "this field is ignored."
102
- ),
103
- ] = None,
104
121
  train_split_name: Annotated[
105
122
  str,
106
123
  typer.Option(help="name of the train split"),
@@ -110,13 +127,33 @@ def add_split(
110
127
  typer.Option(help="name of the validation split"),
111
128
  ] = "val",
112
129
  task_id_file: Annotated[
113
- Optional[Path],
130
+ Path | None,
114
131
  typer.Option(help="path of a text file containing IDs of samples"),
115
132
  ] = None,
133
+ split_name: Annotated[
134
+ str | None,
135
+ typer.Option(
136
+ help="name of the split associated "
137
+ "with the task ID file. If --task-id-file is not provided, "
138
+ "this field is ignored."
139
+ ),
140
+ ] = None,
116
141
  overwrite: Annotated[
117
142
  bool, typer.Option(help="overwrite existing split field")
118
143
  ] = False,
119
- label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
144
+ view_id: Annotated[
145
+ int | None,
146
+ typer.Option(
147
+ help="ID of the Label Studio view, if any. This option is useful "
148
+ "to filter the task to process."
149
+ ),
150
+ ] = None,
151
+ api_key: Annotated[
152
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
153
+ ] = config.label_studio_api_key,
154
+ label_studio_url: Annotated[
155
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
156
+ ] = config.label_studio_url,
120
157
  ):
121
158
  """Update the split field of tasks in a Label Studio project.
122
159
 
@@ -124,7 +161,7 @@ def add_split(
124
161
 
125
162
  If `--task-id-file` is provided, it should contain a list of task IDs,
126
163
  one per line. The split field of these tasks will be updated to the value
127
- of `--split-name`.
164
+ of `--split-name`. The `--train-split` value is ignored in this case.
128
165
 
129
166
  If `--task-id-file` is not provided, the split field of all tasks in the
130
167
  project will be updated based on the `train_split` probability.
@@ -133,12 +170,16 @@ def add_split(
133
170
 
134
171
  In both cases, tasks with a non-null split field are not updated unless
135
172
  the `--overwrite` flag is provided.
173
+
174
+ The `--view-id` option can be used to only assign the split on a subset
175
+ of the tasks.
136
176
  """
137
177
  import random
138
178
 
139
179
  from label_studio_sdk import Task
140
180
  from label_studio_sdk.client import LabelStudio
141
181
 
182
+ check_label_studio_api_key(api_key)
142
183
  ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
143
184
 
144
185
  task_ids = None
@@ -149,14 +190,17 @@ def add_split(
149
190
  )
150
191
  task_ids = task_id_file.read_text().strip().split("\n")
151
192
 
152
- for task in ls.tasks.list(project=project_id, fields="all"):
193
+ for task in ls.tasks.list(project=project_id, fields="all", view=view_id):
153
194
  task: Task
154
195
  task_id = task.id
155
196
 
156
197
  split = task.data.get("split")
157
198
  if split is None or overwrite:
158
- if task_ids and str(task_id) in task_ids:
159
- split = split_name
199
+ if task_ids:
200
+ if str(task_id) in task_ids:
201
+ split = split_name
202
+ else:
203
+ continue
160
204
  else:
161
205
  split = (
162
206
  train_split_name
@@ -170,12 +214,16 @@ def add_split(
170
214
 
171
215
  @app.command()
172
216
  def annotate_from_prediction(
173
- api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
174
217
  project_id: Annotated[int, typer.Option(help="Label Studio project ID")],
218
+ api_key: Annotated[
219
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
220
+ ] = config.label_studio_api_key,
175
221
  updated_by: Annotated[
176
222
  Optional[int], typer.Option(help="User ID to declare as annotator")
177
223
  ] = None,
178
- label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
224
+ label_studio_url: Annotated[
225
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
226
+ ] = config.label_studio_url,
179
227
  ):
180
228
  """Create annotations for all tasks from predictions.
181
229
 
@@ -186,6 +234,7 @@ def annotate_from_prediction(
186
234
  from label_studio_sdk.client import LabelStudio
187
235
  from label_studio_sdk.types.task import Task
188
236
 
237
+ check_label_studio_api_key(api_key)
189
238
  ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
190
239
 
191
240
  task: Task
@@ -203,42 +252,55 @@ def annotate_from_prediction(
203
252
  )
204
253
 
205
254
 
206
- class PredictorBackend(enum.Enum):
207
- ultralytics = "ultralytics"
208
- robotoff = "robotoff"
255
+ class PredictorBackend(enum.StrEnum):
256
+ ultralytics = enum.auto()
257
+ ultralytics_sam3 = enum.auto()
258
+ robotoff = enum.auto()
209
259
 
210
260
 
211
261
  @app.command()
212
262
  def add_prediction(
213
- api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
214
263
  project_id: Annotated[int, typer.Option(help="Label Studio Project ID")],
264
+ api_key: Annotated[
265
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
266
+ ] = config.label_studio_api_key,
215
267
  view_id: Annotated[
216
- Optional[int],
268
+ int | None,
217
269
  typer.Option(
218
270
  help="Label Studio View ID to filter tasks. If not provided, all tasks in the "
219
271
  "project are processed."
220
272
  ),
221
273
  ] = None,
222
274
  model_name: Annotated[
223
- str,
275
+ str | None,
224
276
  typer.Option(
225
277
  help="Name of the object detection model to run (for Robotoff server) or "
226
- "of the Ultralytics zero-shot model to run."
278
+ "of the Ultralytics zero-shot model to run. If using Ultralytics backend "
279
+ "and no model name is provided, the default is yolov8x-worldv2.pt. "
280
+ "If using ultralytics_sam3 backend, the model name is ignored."
281
+ ),
282
+ ] = None,
283
+ skip_existing: Annotated[
284
+ bool,
285
+ typer.Option(
286
+ help="Skip tasks that already have predictions",
227
287
  ),
228
- ] = "yolov8x-worldv2.pt",
288
+ ] = True,
229
289
  server_url: Annotated[
230
- Optional[str],
231
- typer.Option(help="The Robotoff URL if the backend is robotoff"),
290
+ str | None,
291
+ typer.Option(
292
+ help="The Robotoff URL if the backend is robotoff. If the backend is "
293
+ "different than robotoff, this option is ignored."
294
+ ),
232
295
  ] = "https://robotoff.openfoodfacts.org",
233
296
  backend: Annotated[
234
297
  PredictorBackend,
235
298
  typer.Option(
236
- help="Prediction backend: either use Ultralytics to perform "
237
- "the prediction or Robotoff server."
299
+ help="The prediction backend, possible options are: `ultralytics`, `ultralytics_sam3` and `robotoff`"
238
300
  ),
239
301
  ] = PredictorBackend.ultralytics,
240
302
  labels: Annotated[
241
- Optional[list[str]],
303
+ list[str] | None,
242
304
  typer.Option(
243
305
  help="List of class labels to use for Yolo model. If you're using Yolo-World or other "
244
306
  "zero-shot models, this is the list of label names that are going to be provided to the "
@@ -247,15 +309,20 @@ def add_prediction(
247
309
  ),
248
310
  ] = None,
249
311
  label_mapping: Annotated[
250
- Optional[str],
251
- typer.Option(help="Mapping of model labels to class names, as a JSON string"),
312
+ str | None,
313
+ typer.Option(
314
+ help='Mapping of model labels to class names, as a JSON string. Example: \'{"price tag": "price-tag"}\''
315
+ ),
252
316
  ] = None,
253
- label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
317
+ label_studio_url: Annotated[
318
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
319
+ ] = config.label_studio_url,
254
320
  threshold: Annotated[
255
- Optional[float],
321
+ float | None,
256
322
  typer.Option(
257
323
  help="Confidence threshold for selecting bounding boxes. The default is 0.3 "
258
- "for robotoff backend and 0.1 for ultralytics backend."
324
+ "for robotoff backend, 0.1 for ultralytics backend and 0.25 for "
325
+ "ultralytics_sam3 backend."
259
326
  ),
260
327
  ] = None,
261
328
  max_det: Annotated[int, typer.Option(help="Maximum numbers of detections")] = 300,
@@ -270,14 +337,24 @@ def add_prediction(
270
337
  typer.Option(help="Raise an error if image download fails"),
271
338
  ] = True,
272
339
  model_version: Annotated[
273
- Optional[str],
274
- typer.Option(help="Model version to use for the prediction"),
340
+ str | None,
341
+ typer.Option(
342
+ help="Set the model version field of the prediction sent to Label Studio. "
343
+ "This is used to track which model generated the prediction."
344
+ ),
345
+ ] = None,
346
+ imgsz: Annotated[
347
+ int | None,
348
+ typer.Option(
349
+ help="Image size to use for Ultralytics models. If not provided, "
350
+ "the default size of the model is used."
351
+ ),
275
352
  ] = None,
276
353
  ):
277
- """Add predictions as pre-annotations to Label Studio tasks,
278
- for an object detection model running on Triton Inference Server."""
354
+ """Add predictions as pre-annotations to Label Studio tasks."""
279
355
 
280
356
  import tqdm
357
+ from huggingface_hub import hf_hub_download
281
358
  from label_studio_sdk.client import LabelStudio
282
359
  from openfoodfacts.utils import get_image_from_url, http_session
283
360
  from PIL import Image
@@ -287,6 +364,8 @@ def add_prediction(
287
364
  format_annotation_results_from_ultralytics,
288
365
  )
289
366
 
367
+ check_label_studio_api_key(api_key)
368
+
290
369
  label_mapping_dict = None
291
370
  if label_mapping:
292
371
  label_mapping_dict = json.loads(label_mapping)
@@ -305,7 +384,10 @@ def add_prediction(
305
384
  ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
306
385
 
307
386
  if backend == PredictorBackend.ultralytics:
308
- from ultralytics import YOLO
387
+ from ultralytics import YOLO, YOLOWorld
388
+
389
+ if model_name is None:
390
+ model_name = "yolov8x-worldv2.pt"
309
391
 
310
392
  if labels is None:
311
393
  raise typer.BadParameter("Labels are required for Ultralytics backend")
@@ -315,9 +397,33 @@ def add_prediction(
315
397
 
316
398
  model = YOLO(model_name)
317
399
  if hasattr(model, "set_classes"):
400
+ model = typing.cast(YOLOWorld, model)
318
401
  model.set_classes(labels)
319
402
  else:
320
403
  logger.warning("The model does not support setting classes directly.")
404
+ elif backend == PredictorBackend.ultralytics_sam3:
405
+ from ultralytics.models.sam import SAM3SemanticPredictor
406
+
407
+ if threshold is None:
408
+ threshold = 0.25
409
+
410
+ # SAM3 cannot be downloaded directly using to to a gated access. Use a
411
+ # proxy repo.
412
+ model_path = hf_hub_download(
413
+ "1038lab/sam3",
414
+ filename="sam3.pt",
415
+ revision="f055b060a4de0a040891ba2ebac9c5cb3c1c0132",
416
+ )
417
+ overrides = dict(
418
+ task="segment",
419
+ mode="predict",
420
+ model=model_path,
421
+ save=False,
422
+ )
423
+
424
+ if imgsz is not None:
425
+ overrides["imgsz"] = imgsz
426
+ model = SAM3SemanticPredictor(overrides=overrides)
321
427
  elif backend == PredictorBackend.robotoff:
322
428
  if server_url is None:
323
429
  raise typer.BadParameter("--server-url is required for Robotoff backend")
@@ -331,22 +437,32 @@ def add_prediction(
331
437
  for task in tqdm.tqdm(
332
438
  ls.tasks.list(project=project_id, view=view_id), desc="tasks"
333
439
  ):
334
- if task.total_predictions == 0:
440
+ if not (skip_existing and task.total_predictions > 0):
335
441
  image_url = task.data["image_url"]
336
442
  image = typing.cast(
337
443
  Image.Image,
338
444
  get_image_from_url(image_url, error_raise=error_raise),
339
445
  )
446
+ min_score = None
340
447
  if backend == PredictorBackend.ultralytics:
341
- results = model.predict(
342
- image,
343
- conf=threshold,
344
- max_det=max_det,
345
- )[0]
448
+ predict_kwargs = {
449
+ "conf": threshold,
450
+ "max_det": max_det,
451
+ }
452
+ if imgsz is not None:
453
+ predict_kwargs["imgsz"] = imgsz
454
+ results = model.predict(image, **predict_kwargs)[0]
346
455
  labels = typing.cast(list[str], labels)
347
456
  label_studio_result = format_annotation_results_from_ultralytics(
348
457
  results, labels, label_mapping_dict
349
458
  )
459
+ elif backend == PredictorBackend.ultralytics_sam3:
460
+ model.set_image(image)
461
+ results = model(text=labels)[0]
462
+ label_studio_result = format_annotation_results_from_ultralytics(
463
+ results, labels, label_mapping_dict
464
+ )
465
+ min_score = min(results.boxes.conf.tolist(), default=None)
350
466
  elif backend == PredictorBackend.robotoff:
351
467
  r = http_session.get(
352
468
  f"{server_url}/api/v1/images/predict",
@@ -372,7 +488,9 @@ def add_prediction(
372
488
  task=task.id,
373
489
  result=label_studio_result,
374
490
  model_version=model_version,
491
+ score=min_score,
375
492
  )
493
+ logger.info("Prediction added for task: %s", task.id)
376
494
 
377
495
 
378
496
  @app.command()
@@ -449,28 +567,61 @@ def create_config_file(
449
567
  @app.command()
450
568
  def check_dataset(
451
569
  project_id: Annotated[int, typer.Option(help="Label Studio Project ID")],
570
+ view_id: Annotated[int, typer.Option(help="Label Studio View ID, if any.")] = None,
452
571
  api_key: Annotated[
453
- Optional[str], typer.Option(envvar="LABEL_STUDIO_API_KEY")
454
- ] = None,
455
- label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
572
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
573
+ ] = config.label_studio_api_key,
574
+ label_studio_url: Annotated[
575
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
576
+ ] = config.label_studio_url,
577
+ delete_missing_images: Annotated[
578
+ bool,
579
+ typer.Option(help="Delete tasks with missing images from the dataset"),
580
+ ] = False,
581
+ delete_duplicate_images: Annotated[
582
+ bool, typer.Option(help="Delete duplicate images from the dataset")
583
+ ] = False,
456
584
  ):
457
- """Check a dataset for duplicate images on Label Studio."""
585
+ """Perform sanity checks of a Label Studio dataset.
586
+
587
+ This function checks for:
588
+ - Tasks with missing images (404)
589
+ - Duplicate images based on perceptual hash (pHash)
590
+ - Tasks with multiple annotations
591
+
592
+ This function doesn't perform any modifications to the dataset, except
593
+ optionally deleting tasks with missing images if --delete-missing-images
594
+ is provided and tasks with duplicate images if --delete-duplicate-images
595
+ is provided.
596
+ """
458
597
  from label_studio_sdk.client import LabelStudio
459
598
 
460
599
  from ..check import check_ls_dataset
461
600
 
601
+ check_label_studio_api_key(api_key)
462
602
  ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
463
- check_ls_dataset(ls, project_id)
603
+ check_ls_dataset(
604
+ ls=ls,
605
+ project_id=project_id,
606
+ view_id=view_id,
607
+ delete_missing_images=delete_missing_images,
608
+ delete_duplicate_images=delete_duplicate_images,
609
+ )
464
610
 
465
611
 
466
612
  @app.command()
467
613
  def list_users(
468
- api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
469
- label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
614
+ api_key: Annotated[
615
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
616
+ ] = config.label_studio_api_key,
617
+ label_studio_url: Annotated[
618
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
619
+ ] = config.label_studio_url,
470
620
  ):
471
621
  """List all users in Label Studio."""
472
622
  from label_studio_sdk.client import LabelStudio
473
623
 
624
+ check_label_studio_api_key(api_key)
474
625
  ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
475
626
 
476
627
  for user in ls.users.list():
@@ -480,11 +631,57 @@ def list_users(
480
631
  @app.command()
481
632
  def delete_user(
482
633
  user_id: int,
483
- api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
484
- label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
634
+ api_key: Annotated[
635
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
636
+ ] = config.label_studio_api_key,
637
+ label_studio_url: Annotated[
638
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
639
+ ] = config.label_studio_url,
485
640
  ):
486
641
  """Delete a user from Label Studio."""
487
642
  from label_studio_sdk.client import LabelStudio
488
643
 
644
+ check_label_studio_api_key(api_key)
489
645
  ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
490
646
  ls.users.delete(user_id)
647
+
648
+
649
+ @app.command()
650
+ def dump_dataset(
651
+ project_id: Annotated[int, typer.Option(help="Label Studio Project ID")],
652
+ output_file: Annotated[
653
+ Path, typer.Option(help="Path of the output file", writable=True)
654
+ ],
655
+ api_key: Annotated[
656
+ str, typer.Option(help=typer_description.LABEL_STUDIO_API_KEY)
657
+ ] = config.label_studio_api_key,
658
+ view_id: Annotated[
659
+ int | None,
660
+ typer.Option(
661
+ help="ID of the Label Studio view, if any. This option is useful "
662
+ "to filter the tasks to dump."
663
+ ),
664
+ ] = None,
665
+ label_studio_url: Annotated[
666
+ str, typer.Option(help=typer_description.LABEL_STUDIO_URL)
667
+ ] = config.label_studio_url,
668
+ ):
669
+ """Dump all the tasks of a dataset in a JSONL file.
670
+
671
+ All fields of the tasks are exported. A subset of the tasks can be
672
+ selected by filtering tasks based on a view (=tab) using the `--view-id`
673
+ option.
674
+ """
675
+ import orjson
676
+ import tqdm
677
+ from label_studio_sdk.client import LabelStudio
678
+
679
+ check_label_studio_api_key(api_key)
680
+ ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
681
+
682
+ with output_file.open("wb") as f:
683
+ for task in tqdm.tqdm(
684
+ ls.tasks.list(project=project_id, view=view_id), desc="tasks"
685
+ ):
686
+ content = orjson.dumps(task.dict())
687
+ f.write(content + b"\n")
@@ -0,0 +1,2 @@
1
+ LABEL_STUDIO_API_KEY = """API Key to authenticate to the Label Studio server. Can also be set with the LABELR_LABEL_STUDIO_API_KEY environment variable."""
2
+ LABEL_STUDIO_URL = """URL of the Label Studio server. Can also be set with the LABELR_LABEL_STUDIO_URL environment variable."""