Skip to content

Commit cef0b7a

Browse files
author
Matt Sokoloff
committed
docs and validation
1 parent 75213b1 commit cef0b7a

File tree

10 files changed

+283
-215
lines changed

10 files changed

+283
-215
lines changed

labelbox/schema/asset_metadata.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ class AssetMetadata(DbObject):
1010
meta_value (str): URL to an external file or a string of text
1111
"""
1212

13-
VALID_TYPES = {
14-
"VIDEO",
15-
"IMAGE",
16-
"TEXT",
17-
"IMAGE_OVERLAY"
18-
}
13+
VIDEO = "VIDEO"
14+
IMAGE = "IMAGE"
15+
TEXT= "TEXT"
16+
IMAGE_OVERLAY = "IMAGE_OVERLAY"
17+
18+
SUPPORTED_TYPES = {VIDEO, IMAGE, TEXT, IMAGE_OVERLAY}
1919

2020
meta_type = Field.String("meta_type")
2121
meta_value = Field.String("meta_value")

labelbox/schema/bulk_import_request.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ class Config:
493493
extra = 'forbid'
494494

495495
@staticmethod
496-
def determinants(parent_cls) -> None:
496+
def determinants(parent_cls) -> List[str]:
497497
#This is a hack for better error messages
498498
return [
499499
k for k, v in parent_cls.__fields__.items()

labelbox/schema/data_row.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from labelbox.orm import query
22
from labelbox.orm.db_object import DbObject, Updateable, BulkDeletable
33
from labelbox.orm.model import Entity, Field, Relationship
4-
from labelbox.pagination import PaginatedCollection
54
from labelbox.schema.asset_metadata import AssetMetadata
65

76

@@ -34,7 +33,6 @@ class DataRow(DbObject, Updateable, BulkDeletable):
3433
labels = Relationship.ToMany("Label", True)
3534
metadata = Relationship.ToMany("AssetMetadata", False, "metadata")
3635
predictions = Relationship.ToMany("Prediction", False)
37-
3836

3937

4038
@staticmethod
@@ -64,7 +62,7 @@ def create_metadata(self, meta_type, meta_value):
6462
`AssetMetadata` DB object.
6563
"""
6664
if meta_type not in AssetMetadata.VALID_TYPES:
67-
raise ValueError(f"metadata must be one of {AssetMetadata.VALID_TYPES}. Found {meta_type}")
65+
raise ValueError(f"metadata type must be one of {AssetMetadata.VALID_TYPES}. Found {meta_type}")
6866

6967
meta_type_param = "metaType"
7068
meta_value_param = "metaValue"

labelbox/schema/dataset.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
import logging
23
from multiprocessing.dummy import Pool as ThreadPool
34
import os
45

@@ -163,7 +164,7 @@ def convert_item(item):
163164
task._user = user
164165
return task
165166

166-
def data_row_for_external_id(self, external_id):
167+
def data_rows_for_external_id(self, external_id, limit = 10):
167168
""" Convenience method for getting a single `DataRow` belonging to this
168169
`Dataset` that has the given `external_id`.
169170
@@ -182,10 +183,29 @@ def data_row_for_external_id(self, external_id):
182183
where = DataRow.external_id == external_id
183184

184185
data_rows = self.data_rows(where=where)
185-
# Get at most two data_rows.
186-
data_rows = [row for row, _ in zip(data_rows, range(2))]
186+
# Get at most `limit` data_rows.
187+
data_rows = [row for row, _ in zip(data_rows, range(limit))]
187188

188-
if len(data_rows) != 1:
189+
if not len(data_rows):
189190
raise ResourceNotFoundError(DataRow, where)
191+
return data_rows
192+
193+
def data_row_for_external_id(self, external_id):
194+
""" Convenience method for getting a single `DataRow` belonging to this
195+
`Dataset` that has the given `external_id`.
196+
197+
Args:
198+
external_id (str): External ID of the sought `DataRow`.
190199
200+
Returns:
201+
A single `DataRow` with the given ID.
202+
203+
Raises:
204+
labelbox.exceptions.ResourceNotFoundError: If there is no `DataRow`
205+
in this `DataSet` with the given external ID, or if there are
206+
multiple `DataRows` for it.
207+
"""
208+
data_rows = self.data_rows_for_external_id(external_id=external_id, limit = 2)
209+
if len(data_rows) > 1:
210+
logging.warn("More than one data_row has the provided external_id. Use function data_rows_for_external_id to fetch all")
191211
return data_rows[0]

labelbox/schema/project.py

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from collections import namedtuple
22
from datetime import datetime, timezone
33
import json
4+
from labelbox.schema.data_row import DataRow
45
import logging
56
from pathlib import Path
67
import time
@@ -88,6 +89,7 @@ def create_label(self, **kwargs):
8889
# deprecated and we don't want the Py client lib user to know
8990
# about them. At the same time they're connected to a Label at
9091
# label creation in a non-standard way (connect via name).
92+
logger.warning("This function is deprecated and is not compatible with the new editor.")
9193

9294
Label = Entity.Label
9395

@@ -249,18 +251,56 @@ def setup(self, labeling_frontend, labeling_frontend_options):
249251
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
250252
self.update(setup_complete=timestamp)
251253

254+
def validate_labeling_parameter_overrides(self, data):
255+
for idx, row in enumerate(data):
256+
if len(row) != 3:
257+
raise TypeError(f"Data must be a list of tuples containing a DataRow, priority (int), num_labels (int). Found {len(row)} items")
258+
data_row, priority, num_labels = row
259+
if not isinstance(data_row, DataRow):
260+
raise TypeError(f"Datarow should be be of type DataRow. Found {data_row}")
261+
262+
for name, value in [["priority", priority], ["Number of labels", num_labels]]:
263+
if not isinstance(value, int):
264+
raise TypeError(f"{name} must be an int. Found {type(value)} for data_row {data_row}")
265+
if value < 1:
266+
raise ValueError(f"{name} must be greater than 0 for data_row {data_row}")
267+
252268
def set_labeling_parameter_overrides(self, data):
253269
""" Adds labeling parameter overrides to this project.
254270
271+
Priority:
272+
* data will be labeled in priority order with the lower priority numbers being labeled first
273+
- Minimum priority is 1.
274+
* Priority is not the queue position. The position is determined by the relative priority.
275+
- Eg. [(data_row_1, 5,1), (data_row_2, 2,1), (data_row_3, 10,1)]
276+
will be assigned in the following order: [data_row_2, data_row_1, data_row_3]
277+
* datarows with parameter overrides will appear before datarows without overrides
278+
* The priority only effects items in the queue and assigning a priority will not automatically add the item back the queue
279+
- If a datarow has already been labeled this will not have an effect until it is added back into the queue
280+
281+
Number of labels:
282+
* The number times a data row should be labeled
283+
* This will create duplicates in a project
284+
* The queue will never assign the same datarow to a labeler more than once
285+
- if the number of labels is greater than the number of labelers working on a project then
286+
the extra items will get stuck in the queue (thsi can be fixed by removing the override at any time).
287+
* This can add items to the queue even if they have already been labeled but they will only be assigned to members who have not labeled that image before.
288+
* Set this to 1 if you only want to effect the priority.
289+
290+
291+
See information on priority here:
292+
https://docs.labelbox.com/en/configure-editor/queue-system#reservation-system
293+
255294
>>> project.set_labeling_parameter_overrides([
256295
>>> (data_row_1, 2, 3), (data_row_2, 1, 4)])
257296
258297
Args:
259298
data (iterable): An iterable of tuples. Each tuple must contain
260-
(DataRow, priority, numberOfLabels) for the new override.
299+
(Union[DataRow, datarow_id], priority, numberOfLabels) for the new override.
261300
Returns:
262301
bool, indicates if the operation was a success.
263302
"""
303+
self.validate_labeling_parameter_overrides(data)
264304
data_str = ",\n".join(
265305
"{dataRow: {id: \"%s\"}, priority: %d, numLabels: %d }" %
266306
(data_row.uid, priority, num_labels)
@@ -275,6 +315,8 @@ def set_labeling_parameter_overrides(self, data):
275315
def unset_labeling_parameter_overrides(self, data_rows):
276316
""" Removes labeling parameter overrides to this project.
277317
318+
* This will remove unlabeled duplicates in the queue.
319+
278320
Args:
279321
data_rows (iterable): An iterable of DataRows.
280322
Returns:
@@ -290,12 +332,19 @@ def unset_labeling_parameter_overrides(self, data_rows):
290332
return res["project"]["unsetLabelingParameterOverrides"]["success"]
291333

292334
def upsert_review_queue(self, quota_factor):
293-
""" Reinitiates the review queue for this project.
335+
""" Sets the the proportion of total assets in a project to review.
336+
337+
More information can be found here:
338+
https://docs.labelbox.com/en/quality-assurance/review-labels#configure-review-percentage
294339
295340
Args:
296341
quota_factor (float): Which part (percentage) of the queue
297342
to reinitiate. Between 0 and 1.
298343
"""
344+
345+
if (quota_factor > 1.) or (quota_factor < 0.):
346+
raise ValueError("Quota factor must be in the range of [0,1]")
347+
299348
id_param = "projectId"
300349
quota_param = "quotaFactor"
301350
query_str = """mutation UpsertReviewQueuePyApi($%s: ID!, $%s: Float!){
@@ -307,25 +356,6 @@ def upsert_review_queue(self, quota_factor):
307356
quota_param: quota_factor
308357
})
309358

310-
def extend_reservations(self, queue_type):
311-
""" Extends all the current reservations for the current user on the given
312-
queue type.
313-
314-
Args:
315-
queue_type (str): Either "LabelingQueue" or "ReviewQueue"
316-
Returns:
317-
int, the number of reservations that were extended.
318-
"""
319-
if queue_type not in ("LabelingQueue", "ReviewQueue"):
320-
raise InvalidQueryError("Unsupported queue type: %s" % queue_type)
321-
322-
id_param = "projectId"
323-
query_str = """mutation ExtendReservationsPyApi($%s: ID!){
324-
extendReservations(projectId:$%s queueType:%s)}""" % (
325-
id_param, id_param, queue_type)
326-
res = self.client.execute(query_str, {id_param: self.uid})
327-
return res["extendReservations"]
328-
329359
def create_prediction_model(self, name, version):
330360
""" Creates a PredictionModel connected to a Legacy Editor Project.
331361
@@ -335,6 +365,9 @@ def create_prediction_model(self, name, version):
335365
Returns:
336366
A newly created PredictionModel.
337367
"""
368+
369+
logger.warning("This function is deprecated and is not compatible with the new editor.")
370+
338371
PM = Entity.PredictionModel
339372
model = self.client._create(PM, {
340373
PM.name.name: name,
@@ -360,6 +393,8 @@ def create_prediction(self, label, data_row, prediction_model=None):
360393
is None and this Project's active_prediction_model is also
361394
None.
362395
"""
396+
logger.warning("This function is deprecated and is not compatible with the new editor.")
397+
363398
if prediction_model is None:
364399
prediction_model = self.active_prediction_model()
365400
if prediction_model is None:
@@ -433,6 +468,7 @@ def upload_annotations(
433468
Returns:
434469
BulkImportRequest
435470
"""
471+
436472
if isinstance(annotations, str) or isinstance(annotations, Path):
437473

438474
def _is_url_valid(url: Union[str, Path]) -> bool:
@@ -493,6 +529,8 @@ class LabelingParameterOverride(DbObject):
493529
number_of_labels = Field.Int("number_of_labels")
494530

495531

532+
533+
496534
LabelerPerformance = namedtuple(
497535
"LabelerPerformance", "user count seconds_per_label, total_time_labeling "
498536
"consensus average_benchmark_agreement last_activity_time")

labelbox/schema/webhook.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import logging
12
from labelbox.orm import query
23
from labelbox.orm.db_object import DbObject, Updateable
34
from labelbox.orm.model import Entity, Field, Relationship
45

6+
logger = logging.getLogger(__name__)
57

68
class Webhook(DbObject, Updateable):
79
""" Represents a server-side rule for sending notifications to a web-server
@@ -27,6 +29,19 @@ class Webhook(DbObject, Updateable):
2729
LABEL_UPDATED = "LABEL_UPDATED"
2830
LABEL_DELETED = "LABEL_DELETED"
2931

32+
REVIEW_CREATED ="REVIEW_CREATED"
33+
REVIEW_UPDATED ="REVIEW_UPDATED"
34+
REVIEW_DELETED = "REVIEW_DELETED"
35+
36+
SUPPORTED_TOPICS = {
37+
LABEL_CREATED,
38+
LABEL_UPDATED,
39+
LABEL_DELETED,
40+
REVIEW_CREATED,
41+
REVIEW_UPDATED,
42+
REVIEW_DELETED
43+
}
44+
3045
updated_at = Field.DateTime("updated_at")
3146
created_at = Field.DateTime("created_at")
3247
url = Field.String("url")
@@ -41,7 +56,7 @@ def create(client, topics, url, secret, project):
4156
client (Client): The Labelbox client used to connect
4257
to the server.
4358
topics (list of str): A list of topics this Webhook should
44-
get notifications for.
59+
get notifications for. Must be one of Webhook.SUPPORTED_TOPICS
4560
url (str): The URL to which notifications should be sent
4661
by the Labelbox server.
4762
secret (str): A secret key used for signing notifications.
@@ -50,7 +65,12 @@ def create(client, topics, url, secret, project):
5065
events in your organization.
5166
Returns:
5267
A newly created Webhook.
68+
69+
Information on configuring your server can be found here (this is where the url points to and the secret is set).
70+
https://docs.labelbox.com/en/configure-editor/webhooks-setup#setup-steps
71+
5372
"""
73+
5474
project_str = "" if project is None \
5575
else ("project:{id:\"%s\"}," % project.uid)
5676

@@ -65,14 +85,24 @@ def create(client, topics, url, secret, project):
6585
organization = Relationship.ToOne("Organization")
6686
project = Relationship.ToOne("Project")
6787

88+
def delete(self):
89+
self.update(status = "INACTIVE")
90+
6891
def update(self, topics=None, url=None, status=None):
6992
""" Updates this Webhook.
7093
7194
Args:
7295
topics (list of str): The new topics value, optional.
7396
url (str): The new URL value, optional.
7497
status (str): The new status value, optional.
98+
99+
If values are set to None then there are no updates made to that field.
100+
101+
The following code will delete the webhook.
102+
>>> self.update(status = Webhook.INACTIVE)
103+
75104
"""
105+
76106
# Webhook has a custom `update` function due to custom types
77107
# in `status` and `topics` fields.
78108
topics_str = "" if topics is None \

0 commit comments

Comments
 (0)