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 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 format_annotation_results_from_triton(
17
- objects: list["ObjectDetectionRawResult"], image_width: int, image_height: int
18
- ):
19
- """Format annotation results from a Triton object detection model into
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
- bbox = object_.bounding_box
24
- category_name = object_.label
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 = bbox
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": [category_name],
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(help="Hugging Face Datasets repository ID to convert"),
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
- format_annotation_results_from_triton,
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. Tasks without a split field are assigned a split based on the
101
- probability, and updated in the server. Tasks with a non-null split field
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
- split = "train" if random.random() < train_split else "val"
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 Triton server) or "
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
- triton_uri: Annotated[
228
+ server_url: Annotated[
172
229
  Optional[str],
173
- typer.Option(help="URI (host+port) of the Triton Inference Server"),
174
- ] = None,
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 a Triton server to perform "
179
- "the prediction or uses Ultralytics."
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.5 "
200
- "for Triton backend and 0.1 for Ultralytics backend."
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.triton:
262
- if triton_uri is None:
263
- raise typer.BadParameter("Triton URI is required for Triton backend")
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.5
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(ls.tasks.list(project=project_id), desc="tasks"):
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
- else:
290
- output = model.detect_from_image(image, triton_uri=triton_uri)
291
- results = output.select(threshold=threshold)
292
- logger.info("Adding prediction to task: %s", task.id)
293
- label_studio_result = format_annotation_results_from_triton(
294
- results, image.width, image.height
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}-{off_image_id}"
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, return_bytes=True, error_raise=error_raise
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, return_bytes=True, error_raise=error_raise
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
- _, image_bytes = download_output
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")
@@ -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.images import download_image
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": hf_sample["image_id"],
68
- "image_url": hf_meta["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
- "barcode": hf_meta["barcode"],
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
1
+ Metadata-Version: 2.4
2
2
  Name: labelr
3
- Version: 0.2.0
4
- Summary: Add your description here
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.3.4
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
- Provides-Extra: triton
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 with the following command:
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,