Skip to content

Commit 3b3d0fa

Browse files
Add labeler performance
1 parent 84ee2d1 commit 3b3d0fa

File tree

4 files changed

+77
-6
lines changed

4 files changed

+77
-6
lines changed

labelbox/orm/query.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,3 +620,17 @@ def delete_benchmark(label):
620620
deleteBenchmark(where: {labelId: $%s}) {id}} """ % (
621621
label_id_param, label_id_param)
622622
return query_str, {label_id_param: label.uid}
623+
624+
625+
def labeler_performance(project):
626+
project_id_param = "projectId"
627+
query_str = """query LabelerPerformancePyApi($%s: ID!) {
628+
project(where: {id: $%s}) {
629+
labelerPerformance(skip: %%d first: %%d) {
630+
count user {%s} secondsPerLabel totalTimeLabeling consensus
631+
averageBenchmarkAgreement lastActivityTime}
632+
}
633+
}""" % (project_id_param, project_id_param,
634+
results_query_part(Entity.named("User")))
635+
636+
return query_str, {project_id_param: project.uid}

labelbox/schema.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections import namedtuple
12
from datetime import datetime, timezone
23
from enum import Enum, auto
34
import json
@@ -6,6 +7,7 @@
67
import os
78
import time
89

10+
from labelbox import utils
911
from labelbox.exceptions import InvalidQueryError, ResourceNotFoundError
1012
from labelbox.orm import query
1113
from labelbox.orm.db_object import (DbObject, Updateable, Deletable,
@@ -103,6 +105,24 @@ def export_labels(self, timeout_seconds=60):
103105
self.uid)
104106
time.sleep(sleep_time)
105107

108+
def labeler_performance(self):
109+
""" Returns the labeler performances for this Project.
110+
Returns:
111+
A PaginatedCollection of LabelerPerformance objects.
112+
"""
113+
query_str, params = query.labeler_performance(self)
114+
115+
def create_labeler_performance(client, result):
116+
result["user"] = User(client, result["user"])
117+
result["lastActivityTime"] = datetime.fromtimestamp(
118+
result["lastActivityTime"] / 1000, timezone.utc)
119+
return LabelerPerformance(**{utils.snake_case(key): value
120+
for key, value in result.items()})
121+
122+
return PaginatedCollection(self.client, query_str, params,
123+
["project", "labelerPerformance"],
124+
create_labeler_performance)
125+
106126
# TODO Relationships
107127
# labeledDatasets
108128
# ...a lot more, define which are required for v0.1
@@ -493,6 +513,13 @@ class User(DbObject):
493513
# TODO other attributes
494514

495515

516+
LabelerPerformance = namedtuple(
517+
"LabelerPerformance", "user count seconds_per_label, total_time_labeling "
518+
"consensus average_benchmark_agreement last_activity_time")
519+
LabelerPerformance.__doc__ = "Named tuple containing info about a labeler's " \
520+
"performance."
521+
522+
496523
class Organization(DbObject):
497524
""" An Organization is a group of Users associated with data created by
498525
Users within that Organization. Typically all Users within an Organization
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from datetime import datetime, timezone, timedelta
2+
import time
3+
4+
5+
IMG_URL = "https://picsum.photos/200/300"
6+
7+
8+
def test_labeler_performance(client, rand_gen):
9+
project = client.create_project(name=rand_gen(str))
10+
assert list(project.labeler_performance()) == []
11+
12+
dataset = client.create_dataset(name=rand_gen(str), projects=project)
13+
data_row = dataset.create_data_row(row_data=IMG_URL)
14+
label = project.create_label(data_row=data_row, label="test",
15+
seconds_to_label=0.0)
16+
# Sleep a bit as it seems labeler performance isn't updated immediately.
17+
time.sleep(5)
18+
19+
labeler_performance = list(project.labeler_performance())
20+
assert len(labeler_performance) == 1
21+
my_performance = labeler_performance[0]
22+
assert my_performance.user == client.get_user()
23+
assert my_performance.count == 1
24+
assert isinstance(my_performance.last_activity_time, datetime)
25+
now_utc = datetime.now().astimezone(timezone.utc)
26+
assert timedelta(0) < now_utc - my_performance.last_activity_time < \
27+
timedelta(seconds=30)

tools/db_object_doc_gen.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@
3535
from labelbox.utils import snake_case
3636
from labelbox.exceptions import LabelboxError
3737
from labelbox.orm.db_object import Deletable, BulkDeletable, Updateable
38+
from labelbox.orm.model import Entity
39+
import labelbox.schema
3840

3941

4042
GENERAL_CLASSES = [labelbox.Client]
4143
SCHEMA_CLASSES = [
4244
labelbox.Project, labelbox.Dataset, labelbox.DataRow, labelbox.Label,
4345
labelbox.AssetMetadata, labelbox.LabelingFrontend, labelbox.Task,
44-
labelbox.Webhook, labelbox.User, labelbox.Organization, labelbox.Review]
46+
labelbox.Webhook, labelbox.User, labelbox.Organization, labelbox.Review,
47+
labelbox.schema.LabelerPerformance]
4548

4649
ERROR_CLASSES = [LabelboxError] + LabelboxError.__subclasses__()
4750

@@ -366,16 +369,16 @@ def generate_constants(cls):
366369
return unordered_list(values)
367370

368371

369-
def generate_class(cls, schema_class):
372+
def generate_class(cls):
370373
""" Generates HelpDocs style documentation for the given class.
371374
Args:
372375
cls (type): The class to generate docs for.
373-
schema_class (bool): If `cls` is a DbObject subclass.
374376
Return:
375377
HelpDocs style documentation for `cls` containing class description,
376378
methods and fields and relationships if `schema_class`.
377379
"""
378380
text = []
381+
schema_class = issubclass(cls, Entity)
379382

380383
title = "Class " + cls.__module__ + "." + cls.__name__
381384
title_id = re.sub(r"\s+", "_", snake_case(title).lower())
@@ -423,11 +426,11 @@ def generate_all():
423426
text.append(unordered_list([qual_class_name(cls) for cls in ERROR_CLASSES]))
424427

425428
text.append(header(1, "General classes"))
426-
text.extend(generate_class(cls, False) for cls in GENERAL_CLASSES)
429+
text.extend(generate_class(cls) for cls in GENERAL_CLASSES)
427430
text.append(header(1, "Data Classes"))
428-
text.extend(generate_class(cls, True) for cls in SCHEMA_CLASSES)
431+
text.extend(generate_class(cls) for cls in SCHEMA_CLASSES)
429432
text.append(header(1, "Error Classes"))
430-
text.extend(generate_class(cls, False) for cls in ERROR_CLASSES)
433+
text.extend(generate_class(cls) for cls in ERROR_CLASSES)
431434
return "\n".join(text)
432435

433436

0 commit comments

Comments
 (0)