labelr 0.2.0__py3-none-any.whl → 0.3.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.
- labelr/annotate.py +16 -15
- labelr/apps/datasets.py +3 -1
- labelr/apps/projects.py +115 -34
- labelr/export.py +5 -7
- labelr/project_config.py +45 -0
- labelr/sample.py +39 -5
- {labelr-0.2.0.dist-info → labelr-0.3.0.dist-info}/METADATA +16 -9
- labelr-0.3.0.dist-info/RECORD +20 -0
- {labelr-0.2.0.dist-info → labelr-0.3.0.dist-info}/WHEEL +1 -1
- labelr-0.2.0.dist-info/RECORD +0 -19
- {labelr-0.2.0.dist-info → labelr-0.3.0.dist-info}/entry_points.txt +0 -0
- {labelr-0.2.0.dist-info → labelr-0.3.0.dist-info/licenses}/LICENSE +0 -0
- {labelr-0.2.0.dist-info → labelr-0.3.0.dist-info}/top_level.txt +0 -0
labelr/annotate.py
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
import random
|
|
2
2
|
import string
|
|
3
3
|
|
|
4
|
+
from openfoodfacts.types import JSONType
|
|
4
5
|
from openfoodfacts.utils import get_logger
|
|
5
6
|
|
|
6
|
-
try:
|
|
7
|
-
from openfoodfacts.ml.object_detection import ObjectDetectionRawResult
|
|
8
|
-
from ultralytics.engine.results import Results
|
|
9
|
-
except ImportError:
|
|
10
|
-
pass
|
|
11
|
-
|
|
12
|
-
|
|
13
7
|
logger = get_logger(__name__)
|
|
14
8
|
|
|
15
9
|
|
|
16
|
-
def
|
|
17
|
-
objects: list[
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
def format_annotation_results_from_robotoff(
|
|
11
|
+
objects: list[JSONType],
|
|
12
|
+
image_width: int,
|
|
13
|
+
image_height: int,
|
|
14
|
+
label_mapping: dict[str, str] | None = None,
|
|
15
|
+
) -> list[JSONType]:
|
|
16
|
+
"""Format annotation results from Robotoff prediction endpoint into
|
|
20
17
|
Label Studio format."""
|
|
21
18
|
annotation_results = []
|
|
22
19
|
for object_ in objects:
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
bounding_box = object_["bounding_box"]
|
|
21
|
+
label_name = object_["label"]
|
|
22
|
+
|
|
23
|
+
if label_mapping:
|
|
24
|
+
label_name = label_mapping.get(label_name, label_name)
|
|
25
|
+
|
|
25
26
|
# These are relative coordinates (between 0.0 and 1.0)
|
|
26
|
-
y_min, x_min, y_max, x_max =
|
|
27
|
+
y_min, x_min, y_max, x_max = bounding_box
|
|
27
28
|
# Make sure the coordinates are within the image boundaries,
|
|
28
29
|
# and convert them to percentages
|
|
29
30
|
y_min = min(max(0, y_min), 1.0) * 100
|
|
@@ -51,7 +52,7 @@ def format_annotation_results_from_triton(
|
|
|
51
52
|
"y": y,
|
|
52
53
|
"width": width,
|
|
53
54
|
"height": height,
|
|
54
|
-
"rectanglelabels": [
|
|
55
|
+
"rectanglelabels": [label_name],
|
|
55
56
|
},
|
|
56
57
|
},
|
|
57
58
|
)
|
labelr/apps/datasets.py
CHANGED
|
@@ -132,7 +132,9 @@ def export(
|
|
|
132
132
|
api_key: Annotated[Optional[str], typer.Option(envvar="LABEL_STUDIO_API_KEY")],
|
|
133
133
|
repo_id: Annotated[
|
|
134
134
|
Optional[str],
|
|
135
|
-
typer.Option(
|
|
135
|
+
typer.Option(
|
|
136
|
+
help="Hugging Face Datasets repository ID to convert (only if --from or --to is `hf`)"
|
|
137
|
+
),
|
|
136
138
|
] = None,
|
|
137
139
|
label_names: Annotated[
|
|
138
140
|
Optional[str],
|
labelr/apps/projects.py
CHANGED
|
@@ -9,7 +9,7 @@ from openfoodfacts.utils import get_logger
|
|
|
9
9
|
from PIL import Image
|
|
10
10
|
|
|
11
11
|
from ..annotate import (
|
|
12
|
-
|
|
12
|
+
format_annotation_results_from_robotoff,
|
|
13
13
|
format_annotation_results_from_ultralytics,
|
|
14
14
|
)
|
|
15
15
|
from ..config import LABEL_STUDIO_DEFAULT_URL
|
|
@@ -90,16 +90,48 @@ def add_split(
|
|
|
90
90
|
train_split: Annotated[
|
|
91
91
|
float, typer.Option(help="fraction of samples to add in train split")
|
|
92
92
|
],
|
|
93
|
+
split_name: Annotated[
|
|
94
|
+
Optional[str],
|
|
95
|
+
typer.Option(
|
|
96
|
+
help="name of the split associated "
|
|
97
|
+
"with the task ID file. If --task-id-file is not provided, "
|
|
98
|
+
"this field is ignored."
|
|
99
|
+
),
|
|
100
|
+
],
|
|
93
101
|
api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
|
|
94
102
|
project_id: Annotated[int, typer.Option(help="Label Studio project ID")],
|
|
103
|
+
train_split_name: Annotated[
|
|
104
|
+
str,
|
|
105
|
+
typer.Option(help="name of the train split"),
|
|
106
|
+
] = "train",
|
|
107
|
+
val_split_name: Annotated[
|
|
108
|
+
str,
|
|
109
|
+
typer.Option(help="name of the validation split"),
|
|
110
|
+
] = "val",
|
|
111
|
+
task_id_file: Annotated[
|
|
112
|
+
Optional[Path],
|
|
113
|
+
typer.Option(help="path of a text file containing IDs of samples"),
|
|
114
|
+
] = None,
|
|
115
|
+
overwrite: Annotated[
|
|
116
|
+
bool, typer.Option(help="overwrite existing split field")
|
|
117
|
+
] = False,
|
|
95
118
|
label_studio_url: str = LABEL_STUDIO_DEFAULT_URL,
|
|
96
119
|
):
|
|
97
120
|
"""Update the split field of tasks in a Label Studio project.
|
|
98
121
|
|
|
122
|
+
The behavior of this command depends on the `--task-id-file` option.
|
|
123
|
+
|
|
124
|
+
If `--task-id-file` is provided, it should contain a list of task IDs,
|
|
125
|
+
one per line. The split field of these tasks will be updated to the value
|
|
126
|
+
of `--split-name`.
|
|
127
|
+
|
|
128
|
+
If `--task-id-file` is not provided, the split field of all tasks in the
|
|
129
|
+
project will be updated based on the `train_split` probability.
|
|
99
130
|
The split field is set to "train" with probability `train_split`, and "val"
|
|
100
|
-
otherwise.
|
|
101
|
-
|
|
102
|
-
are not updated
|
|
131
|
+
otherwise.
|
|
132
|
+
|
|
133
|
+
In both cases, tasks with a non-null split field are not updated unless
|
|
134
|
+
the `--overwrite` flag is provided.
|
|
103
135
|
"""
|
|
104
136
|
import random
|
|
105
137
|
|
|
@@ -108,11 +140,29 @@ def add_split(
|
|
|
108
140
|
|
|
109
141
|
ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
|
|
110
142
|
|
|
143
|
+
task_ids = None
|
|
144
|
+
if task_id_file is not None:
|
|
145
|
+
if split_name is None or split_name not in (train_split_name, val_split_name):
|
|
146
|
+
raise typer.BadParameter(
|
|
147
|
+
"--split-name is required when using --task-id-file"
|
|
148
|
+
)
|
|
149
|
+
task_ids = task_id_file.read_text().strip().split("\n")
|
|
150
|
+
|
|
111
151
|
for task in ls.tasks.list(project=project_id, fields="all"):
|
|
112
152
|
task: Task
|
|
153
|
+
task_id = task.id
|
|
154
|
+
|
|
113
155
|
split = task.data.get("split")
|
|
114
|
-
if split is None:
|
|
115
|
-
|
|
156
|
+
if split is None or overwrite:
|
|
157
|
+
if task_ids and str(task_id) in task_ids:
|
|
158
|
+
split = split_name
|
|
159
|
+
else:
|
|
160
|
+
split = (
|
|
161
|
+
train_split_name
|
|
162
|
+
if random.random() < train_split
|
|
163
|
+
else val_split_name
|
|
164
|
+
)
|
|
165
|
+
|
|
116
166
|
logger.info("Updating task: %s, split: %s", task.id, split)
|
|
117
167
|
ls.tasks.update(task.id, data={**task.data, "split": split})
|
|
118
168
|
|
|
@@ -153,30 +203,37 @@ def annotate_from_prediction(
|
|
|
153
203
|
|
|
154
204
|
|
|
155
205
|
class PredictorBackend(enum.Enum):
|
|
156
|
-
triton = "triton"
|
|
157
206
|
ultralytics = "ultralytics"
|
|
207
|
+
robotoff = "robotoff"
|
|
158
208
|
|
|
159
209
|
|
|
160
210
|
@app.command()
|
|
161
211
|
def add_prediction(
|
|
162
212
|
api_key: Annotated[str, typer.Option(envvar="LABEL_STUDIO_API_KEY")],
|
|
163
213
|
project_id: Annotated[int, typer.Option(help="Label Studio Project ID")],
|
|
214
|
+
view_id: Annotated[
|
|
215
|
+
Optional[int],
|
|
216
|
+
typer.Option(
|
|
217
|
+
help="Label Studio View ID to filter tasks. If not provided, all tasks in the "
|
|
218
|
+
"project are processed."
|
|
219
|
+
),
|
|
220
|
+
] = None,
|
|
164
221
|
model_name: Annotated[
|
|
165
222
|
str,
|
|
166
223
|
typer.Option(
|
|
167
|
-
help="Name of the object detection model to run (for
|
|
224
|
+
help="Name of the object detection model to run (for Robotoff server) or "
|
|
168
225
|
"of the Ultralytics zero-shot model to run."
|
|
169
226
|
),
|
|
170
227
|
] = "yolov8x-worldv2.pt",
|
|
171
|
-
|
|
228
|
+
server_url: Annotated[
|
|
172
229
|
Optional[str],
|
|
173
|
-
typer.Option(help="
|
|
174
|
-
] =
|
|
230
|
+
typer.Option(help="The Robotoff URL if the backend is robotoff"),
|
|
231
|
+
] = "https://robotoff.openfoodfacts.org",
|
|
175
232
|
backend: Annotated[
|
|
176
233
|
PredictorBackend,
|
|
177
234
|
typer.Option(
|
|
178
|
-
help="Prediction backend: either use
|
|
179
|
-
"the prediction or
|
|
235
|
+
help="Prediction backend: either use Ultralytics to perform "
|
|
236
|
+
"the prediction or Robotoff server."
|
|
180
237
|
),
|
|
181
238
|
] = PredictorBackend.ultralytics,
|
|
182
239
|
labels: Annotated[
|
|
@@ -196,8 +253,8 @@ def add_prediction(
|
|
|
196
253
|
threshold: Annotated[
|
|
197
254
|
Optional[float],
|
|
198
255
|
typer.Option(
|
|
199
|
-
help="Confidence threshold for selecting bounding boxes. The default is 0.
|
|
200
|
-
"for
|
|
256
|
+
help="Confidence threshold for selecting bounding boxes. The default is 0.3 "
|
|
257
|
+
"for robotoff backend and 0.1 for ultralytics backend."
|
|
201
258
|
),
|
|
202
259
|
] = None,
|
|
203
260
|
max_det: Annotated[int, typer.Option(help="Maximum numbers of detections")] = 300,
|
|
@@ -221,9 +278,7 @@ def add_prediction(
|
|
|
221
278
|
|
|
222
279
|
import tqdm
|
|
223
280
|
from label_studio_sdk.client import LabelStudio
|
|
224
|
-
from openfoodfacts.utils import get_image_from_url
|
|
225
|
-
|
|
226
|
-
from labelr.triton.object_detection import ObjectDetectionModelRegistry
|
|
281
|
+
from openfoodfacts.utils import get_image_from_url, http_session
|
|
227
282
|
|
|
228
283
|
label_mapping_dict = None
|
|
229
284
|
if label_mapping:
|
|
@@ -242,8 +297,6 @@ def add_prediction(
|
|
|
242
297
|
)
|
|
243
298
|
ls = LabelStudio(base_url=label_studio_url, api_key=api_key)
|
|
244
299
|
|
|
245
|
-
model: ObjectDetectionModelRegistry | "YOLO"
|
|
246
|
-
|
|
247
300
|
if backend == PredictorBackend.ultralytics:
|
|
248
301
|
from ultralytics import YOLO
|
|
249
302
|
|
|
@@ -258,18 +311,19 @@ def add_prediction(
|
|
|
258
311
|
model.set_classes(labels)
|
|
259
312
|
else:
|
|
260
313
|
logger.warning("The model does not support setting classes directly.")
|
|
261
|
-
elif backend == PredictorBackend.
|
|
262
|
-
if
|
|
263
|
-
raise typer.BadParameter("
|
|
314
|
+
elif backend == PredictorBackend.robotoff:
|
|
315
|
+
if server_url is None:
|
|
316
|
+
raise typer.BadParameter("--server-url is required for Robotoff backend")
|
|
264
317
|
|
|
265
318
|
if threshold is None:
|
|
266
|
-
threshold = 0.
|
|
267
|
-
|
|
268
|
-
model = ObjectDetectionModelRegistry.load(model_name)
|
|
319
|
+
threshold = 0.1
|
|
320
|
+
server_url = server_url.rstrip("/")
|
|
269
321
|
else:
|
|
270
322
|
raise typer.BadParameter(f"Unsupported backend: {backend}")
|
|
271
323
|
|
|
272
|
-
for task in tqdm.tqdm(
|
|
324
|
+
for task in tqdm.tqdm(
|
|
325
|
+
ls.tasks.list(project=project_id, view=view_id), desc="tasks"
|
|
326
|
+
):
|
|
273
327
|
if task.total_predictions == 0:
|
|
274
328
|
image_url = task.data["image_url"]
|
|
275
329
|
image = typing.cast(
|
|
@@ -286,12 +340,22 @@ def add_prediction(
|
|
|
286
340
|
label_studio_result = format_annotation_results_from_ultralytics(
|
|
287
341
|
results, labels, label_mapping_dict
|
|
288
342
|
)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
343
|
+
elif backend == PredictorBackend.robotoff:
|
|
344
|
+
r = http_session.get(
|
|
345
|
+
f"{server_url}/api/v1/images/predict",
|
|
346
|
+
params={
|
|
347
|
+
"models": model_name,
|
|
348
|
+
"output_image": 0,
|
|
349
|
+
"image_url": image_url,
|
|
350
|
+
},
|
|
351
|
+
)
|
|
352
|
+
r.raise_for_status()
|
|
353
|
+
response = r.json()
|
|
354
|
+
label_studio_result = format_annotation_results_from_robotoff(
|
|
355
|
+
response["predictions"][model_name],
|
|
356
|
+
image.width,
|
|
357
|
+
image.height,
|
|
358
|
+
label_mapping_dict,
|
|
295
359
|
)
|
|
296
360
|
if dry_run:
|
|
297
361
|
logger.info("image_url: %s", image_url)
|
|
@@ -339,7 +403,7 @@ def create_dataset_file(
|
|
|
339
403
|
extra_meta["barcode"] = barcode
|
|
340
404
|
off_image_id = Path(extract_source_from_url(url)).stem
|
|
341
405
|
extra_meta["off_image_id"] = off_image_id
|
|
342
|
-
image_id = f"{barcode}
|
|
406
|
+
image_id = f"{barcode}_{off_image_id}"
|
|
343
407
|
|
|
344
408
|
image = get_image_from_url(url, error_raise=False)
|
|
345
409
|
|
|
@@ -351,3 +415,20 @@ def create_dataset_file(
|
|
|
351
415
|
image_id, url, image.width, image.height, extra_meta
|
|
352
416
|
)
|
|
353
417
|
f.write(json.dumps(label_studio_sample) + "\n")
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@app.command()
|
|
421
|
+
def create_config_file(
|
|
422
|
+
output_file: Annotated[
|
|
423
|
+
Path, typer.Option(help="Path to the output label config file", exists=False)
|
|
424
|
+
],
|
|
425
|
+
labels: Annotated[
|
|
426
|
+
list[str], typer.Option(help="List of class labels to use for the model")
|
|
427
|
+
],
|
|
428
|
+
):
|
|
429
|
+
"""Create a Label Studio label config file for object detection tasks."""
|
|
430
|
+
from labelr.project_config import create_object_detection_label_config
|
|
431
|
+
|
|
432
|
+
config = create_object_detection_label_config(labels)
|
|
433
|
+
output_file.write_text(config)
|
|
434
|
+
logger.info("Label config file created: %s", output_file)
|
labelr/export.py
CHANGED
|
@@ -164,16 +164,14 @@ def export_from_ls_to_ultralytics(
|
|
|
164
164
|
|
|
165
165
|
if has_valid_annotation:
|
|
166
166
|
download_output = download_image(
|
|
167
|
-
image_url,
|
|
167
|
+
image_url, return_struct=True, error_raise=error_raise
|
|
168
168
|
)
|
|
169
169
|
if download_output is None:
|
|
170
170
|
logger.error("Failed to download image: %s", image_url)
|
|
171
171
|
continue
|
|
172
172
|
|
|
173
|
-
_, image_bytes = typing.cast(tuple[Image.Image, bytes], download_output)
|
|
174
|
-
|
|
175
173
|
with (images_dir / split / f"{image_id}.jpg").open("wb") as f:
|
|
176
|
-
f.write(image_bytes)
|
|
174
|
+
f.write(download_output.image_bytes)
|
|
177
175
|
|
|
178
176
|
with (output_dir / "data.yaml").open("w") as f:
|
|
179
177
|
f.write("path: data\n")
|
|
@@ -215,14 +213,14 @@ def export_from_hf_to_ultralytics(
|
|
|
215
213
|
|
|
216
214
|
if download_images:
|
|
217
215
|
download_output = download_image(
|
|
218
|
-
image_url,
|
|
216
|
+
image_url, return_struct=True, error_raise=error_raise
|
|
219
217
|
)
|
|
220
218
|
if download_output is None:
|
|
221
219
|
logger.error("Failed to download image: %s", image_url)
|
|
222
220
|
continue
|
|
223
|
-
|
|
221
|
+
|
|
224
222
|
with (split_images_dir / f"{image_id}.jpg").open("wb") as f:
|
|
225
|
-
f.write(image_bytes)
|
|
223
|
+
f.write(download_output.image_bytes)
|
|
226
224
|
else:
|
|
227
225
|
image = sample["image"]
|
|
228
226
|
image.save(split_images_dir / f"{image_id}.jpg")
|
labelr/project_config.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
COLORS = [
|
|
2
|
+
"blue",
|
|
3
|
+
"green",
|
|
4
|
+
"yellow",
|
|
5
|
+
"red",
|
|
6
|
+
"purple",
|
|
7
|
+
"orange",
|
|
8
|
+
"pink",
|
|
9
|
+
"brown",
|
|
10
|
+
"gray",
|
|
11
|
+
"black",
|
|
12
|
+
"white",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_object_detection_label_config(labels_names: list[str]) -> str:
|
|
17
|
+
"""Create a Label Studio label configuration for object detection tasks.
|
|
18
|
+
|
|
19
|
+
The format is the following:
|
|
20
|
+
```xml
|
|
21
|
+
<View>
|
|
22
|
+
<Image name="image" value="$image_url"/>
|
|
23
|
+
<RectangleLabels name="label" toName="image">
|
|
24
|
+
<Label value="nutrition-table" background="green"/>
|
|
25
|
+
<Label value="nutrition-table-small" background="blue"/>
|
|
26
|
+
<Label value="nutrition-table-small-energy" background="yellow"/>
|
|
27
|
+
<Label value="nutrition-table-text" background="red"/>
|
|
28
|
+
</RectangleLabels>
|
|
29
|
+
</View>
|
|
30
|
+
```
|
|
31
|
+
"""
|
|
32
|
+
if len(labels_names) > len(COLORS):
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"Too many labels ({len(labels_names)}) for the available colors ({len(COLORS)})."
|
|
35
|
+
)
|
|
36
|
+
labels_xml = "\n".join(
|
|
37
|
+
f' <Label value="{label}" background="{color}"/>'
|
|
38
|
+
for label, color in zip(labels_names, COLORS[: len(labels_names)])
|
|
39
|
+
)
|
|
40
|
+
return f"""<View>
|
|
41
|
+
<Image name="image" value="$image_url"/>
|
|
42
|
+
<RectangleLabels name="label" toName="image">
|
|
43
|
+
{labels_xml}
|
|
44
|
+
</RectangleLabels>
|
|
45
|
+
</View>"""
|
labelr/sample.py
CHANGED
|
@@ -3,7 +3,9 @@ import random
|
|
|
3
3
|
import string
|
|
4
4
|
|
|
5
5
|
import datasets
|
|
6
|
-
from openfoodfacts
|
|
6
|
+
from openfoodfacts import Flavor
|
|
7
|
+
from openfoodfacts.barcode import normalize_barcode
|
|
8
|
+
from openfoodfacts.images import download_image, generate_image_url
|
|
7
9
|
|
|
8
10
|
logger = logging.getLogger(__name__)
|
|
9
11
|
|
|
@@ -62,17 +64,49 @@ def format_object_detection_sample_from_hf(hf_sample: dict, split: str) -> dict:
|
|
|
62
64
|
annotation_results = format_annotation_results_from_hf(
|
|
63
65
|
objects, image_width, image_height
|
|
64
66
|
)
|
|
67
|
+
image_id = hf_sample["image_id"]
|
|
68
|
+
image_url = hf_meta["image_url"]
|
|
69
|
+
meta_kwargs = {}
|
|
70
|
+
|
|
71
|
+
if "off_image_id" in hf_meta:
|
|
72
|
+
# If `off_image_id` is present, we assume this is an Open Food Facts
|
|
73
|
+
# dataset sample.
|
|
74
|
+
# We normalize the barcode, and generate a new image URL
|
|
75
|
+
# to make sure that:
|
|
76
|
+
# - the image URL is valid with correct path
|
|
77
|
+
# - we use the images subdomain everywhere
|
|
78
|
+
off_image_id = hf_meta["off_image_id"]
|
|
79
|
+
meta_kwargs["off_image_id"] = off_image_id
|
|
80
|
+
barcode = normalize_barcode(hf_meta["barcode"])
|
|
81
|
+
meta_kwargs["barcode"] = barcode
|
|
82
|
+
image_id = f"{barcode}_{off_image_id}"
|
|
83
|
+
|
|
84
|
+
if ".openfoodfacts." in image_url:
|
|
85
|
+
flavor = Flavor.off
|
|
86
|
+
elif ".openbeautyfacts." in image_url:
|
|
87
|
+
flavor = Flavor.obf
|
|
88
|
+
elif ".openpetfoodfacts." in image_url:
|
|
89
|
+
flavor = Flavor.opf
|
|
90
|
+
elif ".openproductsfacts." in image_url:
|
|
91
|
+
flavor = Flavor.opf
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"Unknown Open Food Facts flavor for image URL: {image_url}"
|
|
95
|
+
)
|
|
96
|
+
image_url = generate_image_url(
|
|
97
|
+
code=barcode, image_id=off_image_id, flavor=flavor
|
|
98
|
+
)
|
|
99
|
+
|
|
65
100
|
return {
|
|
66
101
|
"data": {
|
|
67
|
-
"image_id":
|
|
68
|
-
"image_url":
|
|
102
|
+
"image_id": image_id,
|
|
103
|
+
"image_url": image_url,
|
|
69
104
|
"batch": "null",
|
|
70
105
|
"split": split,
|
|
71
106
|
"meta": {
|
|
72
107
|
"width": image_width,
|
|
73
108
|
"height": image_height,
|
|
74
|
-
|
|
75
|
-
"off_image_id": hf_meta["off_image_id"],
|
|
109
|
+
**meta_kwargs,
|
|
76
110
|
},
|
|
77
111
|
},
|
|
78
112
|
"predictions": [{"result": annotation_results}],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: labelr
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A command-line tool to manage labeling tasks with Label Studio.
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE
|
|
@@ -9,14 +9,11 @@ Requires-Dist: datasets>=3.2.0
|
|
|
9
9
|
Requires-Dist: imagehash>=4.3.1
|
|
10
10
|
Requires-Dist: label-studio-sdk>=1.0.8
|
|
11
11
|
Requires-Dist: more-itertools>=10.5.0
|
|
12
|
-
Requires-Dist: openfoodfacts>=2.
|
|
13
|
-
Requires-Dist: protobuf>=5.29.1
|
|
12
|
+
Requires-Dist: openfoodfacts>=2.9.0
|
|
14
13
|
Requires-Dist: typer>=0.15.1
|
|
15
14
|
Provides-Extra: ultralytics
|
|
16
15
|
Requires-Dist: ultralytics>=8.3.49; extra == "ultralytics"
|
|
17
|
-
|
|
18
|
-
Requires-Dist: tritonclient>=2.52.0; extra == "triton"
|
|
19
|
-
Requires-Dist: openfoodfacts[ml]>=2.3.4; extra == "triton"
|
|
16
|
+
Dynamic: license-file
|
|
20
17
|
|
|
21
18
|
# Labelr
|
|
22
19
|
|
|
@@ -67,7 +64,17 @@ For all the commands that interact with Label Studio, you need to provide an API
|
|
|
67
64
|
|
|
68
65
|
#### Create a project
|
|
69
66
|
|
|
70
|
-
Once you have a Label Studio instance running, you can create a project
|
|
67
|
+
Once you have a Label Studio instance running, you can create a project easily. First, you need to create a configuration file for the project. The configuration file is an XML file that defines the labeling interface and the labels to use for the project. You can find an example of a configuration file in the [Label Studio documentation](https://labelstud.io/guide/setup).
|
|
68
|
+
|
|
69
|
+
For an object detection task, a command allows you to create the configuration file automatically:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
labelr projects create-config --labels 'label1' --labels 'label2' --output-file label_config.xml
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
where `label1` and `label2` are the labels you want to use for the object detection task, and `label_config.xml` is the output file that will contain the configuration.
|
|
76
|
+
|
|
77
|
+
Then, you can create a project on Label Studio with the following command:
|
|
71
78
|
|
|
72
79
|
```bash
|
|
73
80
|
labelr projects create --title my_project --api-key API_KEY --config-file label_config.xml
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
labelr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
labelr/__main__.py,sha256=G4e95-IfhI-lOmkOBP6kQ8wl1x_Fl7dZlLOYr90K83c,66
|
|
3
|
+
labelr/annotate.py,sha256=3fJ9FYbcozcOoKuhNtzPHV8sSnp-45FsNnMc8UeBHGU,3503
|
|
4
|
+
labelr/check.py,sha256=3wK6mE0UsKvoBNm0_lyWhCMq7gxkv5r50pvO70damXY,2476
|
|
5
|
+
labelr/config.py,sha256=3RXF_NdkSuHvfVMGMlYmjlw45fU77zQkLX7gmZq7NxM,64
|
|
6
|
+
labelr/export.py,sha256=MuU7M0H1THg3FcA6IEYPKFb58nIakNCCpcItQSSwNzM,10070
|
|
7
|
+
labelr/main.py,sha256=gQ8I287mpLy3HIUWqZUyoLAfPwkphwOIzut7hEbH8tY,2135
|
|
8
|
+
labelr/project_config.py,sha256=CIHEcgSOfXb53naHWEBkTDm2V9m3abAu8C54VSzHjAs,1260
|
|
9
|
+
labelr/sample.py,sha256=WPWKJbyFDp1T-pmd1DfCpz2LWUApGJ71MvnMYkHeORU,7164
|
|
10
|
+
labelr/types.py,sha256=CahqnkLnGj23Jg0X9nftK7Jiorq50WYQqR8u9Ln4E-k,281
|
|
11
|
+
labelr/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
labelr/apps/datasets.py,sha256=OihvNIaqvny9QH64kq3oUrITRppGPz6WlJUSeObS3kE,7991
|
|
13
|
+
labelr/apps/projects.py,sha256=mF25efdNsNaOyMJindi60EHdKP6kR_7L6KFBEbqMlqM,15146
|
|
14
|
+
labelr/apps/users.py,sha256=twQSlpHxE0hrYkgrJpEFbK8lYfWnpJr8vyfLHLtdAUU,909
|
|
15
|
+
labelr-0.3.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
16
|
+
labelr-0.3.0.dist-info/METADATA,sha256=bxgaoKo6fCFfTtFI2YSjb_cSDbURB_JDsMA_5PV09gs,6583
|
|
17
|
+
labelr-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
18
|
+
labelr-0.3.0.dist-info/entry_points.txt,sha256=OACukVeR_2z54i8yQuWqqk_jdEHlyTwmTFOFBmxPp1k,43
|
|
19
|
+
labelr-0.3.0.dist-info/top_level.txt,sha256=bjZo50aGZhXIcZYpYOX4sdAQcamxh8nwfEh7A9RD_Ag,7
|
|
20
|
+
labelr-0.3.0.dist-info/RECORD,,
|
labelr-0.2.0.dist-info/RECORD
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
labelr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
labelr/__main__.py,sha256=G4e95-IfhI-lOmkOBP6kQ8wl1x_Fl7dZlLOYr90K83c,66
|
|
3
|
-
labelr/annotate.py,sha256=aphaxyGvKVTjB4DQvj00HpX-X8Xz70UHoKSf4QFWaO4,3456
|
|
4
|
-
labelr/check.py,sha256=3wK6mE0UsKvoBNm0_lyWhCMq7gxkv5r50pvO70damXY,2476
|
|
5
|
-
labelr/config.py,sha256=3RXF_NdkSuHvfVMGMlYmjlw45fU77zQkLX7gmZq7NxM,64
|
|
6
|
-
labelr/export.py,sha256=tcOmVnOdJidWfNouNWoQ4OJgHMbbG-bLFHkId9huiS0,10170
|
|
7
|
-
labelr/main.py,sha256=gQ8I287mpLy3HIUWqZUyoLAfPwkphwOIzut7hEbH8tY,2135
|
|
8
|
-
labelr/sample.py,sha256=cpzvgZWVU6GzwD35tqGKEFVKAgqQbSHlWW6IL9FG15Q,5918
|
|
9
|
-
labelr/types.py,sha256=CahqnkLnGj23Jg0X9nftK7Jiorq50WYQqR8u9Ln4E-k,281
|
|
10
|
-
labelr/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
labelr/apps/datasets.py,sha256=DXU8XZx0iEHDI5SvUeI8atCKSUmj9YJwO6xTgMZDgEI,7936
|
|
12
|
-
labelr/apps/projects.py,sha256=HpulSciBVTk1sSR1uXjtHytny9t-rN8wiaQ5llNBX6Y,12420
|
|
13
|
-
labelr/apps/users.py,sha256=twQSlpHxE0hrYkgrJpEFbK8lYfWnpJr8vyfLHLtdAUU,909
|
|
14
|
-
labelr-0.2.0.dist-info/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
15
|
-
labelr-0.2.0.dist-info/METADATA,sha256=nxbEiMBsVEQS71pzZ39uLL_GCVebIB71wyxvFsueGcU,5960
|
|
16
|
-
labelr-0.2.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
17
|
-
labelr-0.2.0.dist-info/entry_points.txt,sha256=OACukVeR_2z54i8yQuWqqk_jdEHlyTwmTFOFBmxPp1k,43
|
|
18
|
-
labelr-0.2.0.dist-info/top_level.txt,sha256=bjZo50aGZhXIcZYpYOX4sdAQcamxh8nwfEh7A9RD_Ag,7
|
|
19
|
-
labelr-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|