Skip to content

Commit 4982616

Browse files
Merge pull request #680 from Labelbox/develop
3.26.1
2 parents a712d2b + 8afcfb9 commit 4982616

File tree

16 files changed

+316
-13
lines changed

16 files changed

+316
-13
lines changed

CHANGELOG.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
# Changelog
22

3-
# Version 3.26.0 (2022-08-12)
3+
# Version 3.26.1 (2022-08-23)
4+
### Changed
5+
* `ModelRun.get_config()`
6+
* Modifies get_config to return un-nested Model Run config
7+
### Added
8+
* `ModelRun.update_config()`
9+
* Updates model run training metadata
10+
* `ModelRun.reset_config()`
11+
* Resets model run training metadata
12+
* `ModelRun.get_config()`
13+
* Fetches model run training metadata
14+
15+
### Changed
16+
* `Model.create_model_run()`
17+
* Add training metadata config as a model run creation param
18+
19+
# Version 3.26.0 (2022-08-15)
420
## Added
521
* `Batch.delete()` which will delete an existing `Batch`
622
* `Batch.delete_labels()` which will delete all `Label`’s created after a `Project`’s mode has been set to batch.
@@ -663,7 +679,3 @@ a `Label`. Default value is 0.0.
663679

664680
## Version 2.2 (2019-10-18)
665681
Changelog not maintained before version 2.2.
666-
667-
### Changed
668-
* `Model.create_model_run()`
669-
* Add training metadata config as a model run creation param

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
copyright = '2021, Labelbox'
2222
author = 'Labelbox'
2323

24-
release = '3.25.1'
24+
release = '3.26.1'
2525

2626
# -- General configuration ---------------------------------------------------
2727

labelbox/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "labelbox"
2-
__version__ = "3.26.0"
2+
__version__ = "3.26.1"
33

44
from labelbox.client import Client
55
from labelbox.schema.project import Project

labelbox/data/serialization/labelbox_v1/label.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ class LBV1Label(BaseModel):
146146
skipped: Optional[bool] = Extra('Skipped')
147147
media_type: Optional[str] = Extra('media_type')
148148
data_split: Optional[str] = Extra('Data Split')
149+
global_key: Optional[str] = Extra('Global Key')
149150

150151
def to_common(self) -> Label:
151152
if isinstance(self.label, list):

labelbox/data/serialization/labelbox_v1/objects.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class LBV1ObjectBase(LBV1Feature):
2222
instanceURI: Optional[str] = None
2323
classifications: List[Union[LBV1Text, LBV1Radio, LBV1Dropdown,
2424
LBV1Checklist]] = []
25+
page: Optional[int] = None
26+
unit: Optional[str] = None
2527

2628
def dict(self, *args, **kwargs) -> Dict[str, Any]:
2729
res = super().dict(*args, **kwargs)
@@ -262,7 +264,7 @@ def from_common(cls, text_entity: TextEntity,
262264
class LBV1Objects(BaseModel):
263265
objects: List[Union[LBV1Line, LBV1Point, LBV1Polygon, LBV1Rectangle,
264266
LBV1TextEntity, LBV1Mask, LBV1TIPoint, LBV1TILine,
265-
LBV1TIPolygon, LBV1TIRectangle]]
267+
LBV1TIPolygon, LBV1TIRectangle,]]
266268

267269
def to_common(self) -> List[ObjectAnnotation]:
268270
objects = [
@@ -285,6 +287,8 @@ def to_common(self) -> List[ObjectAnnotation]:
285287
'color': obj.color,
286288
'feature_id': obj.feature_id,
287289
'value': obj.value,
290+
'page': obj.page,
291+
'unit': obj.unit,
288292
}) for obj in self.objects
289293
]
290294
return objects

labelbox/schema/data_row.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class DataRow(DbObject, Updateable, BulkDeletable):
1717
1818
Attributes:
1919
external_id (str): User-generated file name or identifier
20+
global_key (str): User-generated globally unique identifier
2021
row_data (str): Paths to local files are uploaded to Labelbox's server.
2122
Otherwise, it's treated as an external URL.
2223
updated_at (datetime)
@@ -33,6 +34,7 @@ class DataRow(DbObject, Updateable, BulkDeletable):
3334
attachments (Relationship) `ToMany` relationship with AssetAttachment
3435
"""
3536
external_id = Field.String("external_id")
37+
global_key = Field.String("global_key")
3638
row_data = Field.String("row_data")
3739
updated_at = Field.DateTime("updated_at")
3840
created_at = Field.DateTime("created_at")

labelbox/schema/model_run.py

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class Status(Enum):
4242
FAILED = "FAILED"
4343

4444
def upsert_labels(self, label_ids, timeout_seconds=60):
45-
""" Adds data rows and labels to a model run
45+
""" Adds data rows and labels to a Model Run
4646
Args:
4747
label_ids (list): label ids to insert
4848
timeout_seconds (float): Max waiting time, in seconds.
@@ -75,7 +75,7 @@ def upsert_labels(self, label_ids, timeout_seconds=60):
7575
timeout_seconds=timeout_seconds)
7676

7777
def upsert_data_rows(self, data_row_ids, timeout_seconds=60):
78-
""" Adds data rows to a model run without any associated labels
78+
""" Adds data rows to a Model Run without any associated labels
7979
Args:
8080
data_row_ids (list): data row ids to add to mea
8181
timeout_seconds (float): Max waiting time, in seconds.
@@ -167,7 +167,7 @@ def model_run_data_rows(self):
167167
['annotationGroups', 'pageInfo', 'endCursor'])
168168

169169
def delete(self):
170-
""" Deletes specified model run.
170+
""" Deletes specified Model Run.
171171
172172
Returns:
173173
Query execution success.
@@ -178,10 +178,10 @@ def delete(self):
178178
self.client.execute(query_str, {ids_param: str(self.uid)})
179179

180180
def delete_model_run_data_rows(self, data_row_ids: List[str]):
181-
""" Deletes data rows from model runs.
181+
""" Deletes data rows from Model Runs.
182182
183183
Args:
184-
data_row_ids (list): List of data row ids to delete from the model run.
184+
data_row_ids (list): List of data row ids to delete from the Model Run.
185185
Returns:
186186
Query execution success.
187187
"""
@@ -262,6 +262,56 @@ def update_status(self,
262262
},
263263
experimental=True)
264264

265+
@experimental
266+
def update_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
267+
"""
268+
Updates the Model Run's training metadata config
269+
Args:
270+
config (dict): A dictionary of keys and values
271+
Returns:
272+
Model Run id and updated training metadata
273+
"""
274+
data: Dict[str, Any] = {'config': config}
275+
res = self.client.execute(
276+
"""mutation updateModelRunConfigPyApi($modelRunId: ID!, $data: UpdateModelRunConfigInput!){
277+
updateModelRunConfig(modelRun: {id : $modelRunId}, data: $data){trainingMetadata}
278+
}
279+
""", {
280+
'modelRunId': self.uid,
281+
'data': data
282+
},
283+
experimental=True)
284+
return res["updateModelRunConfig"]
285+
286+
@experimental
287+
def reset_config(self) -> Dict[str, Any]:
288+
"""
289+
Resets Model Run's training metadata config
290+
Returns:
291+
Model Run id and reset training metadata
292+
"""
293+
res = self.client.execute(
294+
"""mutation resetModelRunConfigPyApi($modelRunId: ID!){
295+
resetModelRunConfig(modelRun: {id : $modelRunId}){trainingMetadata}
296+
}
297+
""", {'modelRunId': self.uid},
298+
experimental=True)
299+
return res["resetModelRunConfig"]
300+
301+
@experimental
302+
def get_config(self) -> Dict[str, Any]:
303+
"""
304+
Gets Model Run's training metadata
305+
Returns:
306+
training metadata as a dictionary
307+
"""
308+
res = self.client.execute("""query ModelRunPyApi($modelRunId: ID!){
309+
modelRun(where: {id : $modelRunId}){trainingMetadata}
310+
}
311+
""", {'modelRunId': self.uid},
312+
experimental=True)
313+
return res["modelRun"]["trainingMetadata"]
314+
265315
@experimental
266316
def export_labels(
267317
self,

labelbox/schema/project.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,22 @@ class QueueMode(Enum):
9393
Dataset = "Dataset"
9494

9595
def update(self, **kwargs):
96+
""" Updates this project with the specified attributes
97+
98+
Args:
99+
kwargs: a dictionary containing attributes to be upserted
100+
101+
Note that the quality setting cannot be changed after a project has been created. The quality mode
102+
for a project is inferred through the following attributes:
103+
Benchmark:
104+
auto_audit_number_of_labels = 1
105+
auto_audit_percentage = 1.0
106+
Consensus:
107+
auto_audit_number_of_labels > 1
108+
auto_audit_percentage <= 1.0
109+
Attempting to switch between benchmark and consensus modes is an invalid operation and will result
110+
in an error.
111+
"""
96112
mode: Optional[Project.QueueMode] = kwargs.pop("queue_mode", None)
97113
if mode:
98114
self._update_queue_mode(mode)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
[{
2+
"ID": "cl6xnzi4a7ldn0729381g7104",
3+
"DataRow ID": "cl6xnv9h61fv0085yhtoq06ht",
4+
"Labeled Data": "https://storage.labelbox.com/ckcz6bubudyfi0855o1dt1g9s%2F4cef4e08-e13d-8a5e-fbbf-c7624babb490-Airbnb_%20Labelbox%20-%20Focus%20on%20Workforce%20-%20Labelbox%20Labeling%20Operations%20(1).pdf?Expires=1661971050348&KeyName=labelbox-assets-key-3&Signature=JK6ral5CXF7T9Q5LaQqKvJy5A2A",
5+
"Label": {
6+
"objects": [{
7+
"featureId": "cl6xnzjpq0dmr07yocs2vfot8",
8+
"schemaId": "cl6xnuwt95lqq07330tbb3mfd",
9+
"color": "#1CE6FF",
10+
"title": "boxy",
11+
"value": "boxy",
12+
"bbox": {
13+
"top": 144.68,
14+
"left": 107.84,
15+
"height": 441.6,
16+
"width": 9.48
17+
},
18+
"page": 0,
19+
"unit": "POINTS",
20+
"instanceURI": "https://api.labelbox.com/masks/feature/cl6xnzjpq0dmr07yocs2vfot8?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJja2NjOWZtbXc0aGNkMDczOHFpeWM2YW54Iiwib3JnYW5pemF0aW9uSWQiOiJja2N6NmJ1YnVkeWZpMDg1NW8xZHQxZzlzIiwiaWF0IjoxNjYwNzYxNDUwLCJleHAiOjE2NjMzNTM0NTB9.X4-j6zee8o685PUrL9C6oC2m6TayKuJQHhN8iLgG8kI"
21+
}, {
22+
"featureId": "cl6xnzjpq0dms07yobwv68gxf",
23+
"schemaId": "cl6xnuwt95lqq07330tbb3mfd",
24+
"color": "#1CE6FF",
25+
"title": "boxy",
26+
"value": "boxy",
27+
"bbox": {
28+
"top": 162.73,
29+
"left": 32.45,
30+
"height": 388.17,
31+
"width": 101.66
32+
},
33+
"page": 4,
34+
"unit": "POINTS",
35+
"instanceURI": "https://api.labelbox.com/masks/feature/cl6xnzjpq0dms07yobwv68gxf?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJja2NjOWZtbXc0aGNkMDczOHFpeWM2YW54Iiwib3JnYW5pemF0aW9uSWQiOiJja2N6NmJ1YnVkeWZpMDg1NW8xZHQxZzlzIiwiaWF0IjoxNjYwNzYxNDUwLCJleHAiOjE2NjMzNTM0NTB9.X4-j6zee8o685PUrL9C6oC2m6TayKuJQHhN8iLgG8kI"
36+
}, {
37+
"featureId": "cl6xnzjpq0dmt07yo8pp45gru",
38+
"schemaId": "cl6xnuwt95lqq07330tbb3mfd",
39+
"color": "#1CE6FF",
40+
"title": "boxy",
41+
"value": "boxy",
42+
"bbox": {
43+
"top": 223.26,
44+
"left": 251.42,
45+
"height": 457.04,
46+
"width": 186.78
47+
},
48+
"page": 7,
49+
"unit": "POINTS",
50+
"instanceURI": "https://api.labelbox.com/masks/feature/cl6xnzjpq0dmt07yo8pp45gru?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJja2NjOWZtbXc0aGNkMDczOHFpeWM2YW54Iiwib3JnYW5pemF0aW9uSWQiOiJja2N6NmJ1YnVkeWZpMDg1NW8xZHQxZzlzIiwiaWF0IjoxNjYwNzYxNDUwLCJleHAiOjE2NjMzNTM0NTB9.X4-j6zee8o685PUrL9C6oC2m6TayKuJQHhN8iLgG8kI"
51+
}, {
52+
"featureId": "cl6xnzjpq0dmu07yo2qik0en4",
53+
"schemaId": "cl6xnuwt95lqq07330tbb3mfd",
54+
"color": "#1CE6FF",
55+
"title": "boxy",
56+
"value": "boxy",
57+
"bbox": {
58+
"top": 32.52,
59+
"left": 218.17,
60+
"height": 231.73,
61+
"width": 110.56
62+
},
63+
"page": 6,
64+
"unit": "POINTS",
65+
"instanceURI": "https://api.labelbox.com/masks/feature/cl6xnzjpq0dmu07yo2qik0en4?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJja2NjOWZtbXc0aGNkMDczOHFpeWM2YW54Iiwib3JnYW5pemF0aW9uSWQiOiJja2N6NmJ1YnVkeWZpMDg1NW8xZHQxZzlzIiwiaWF0IjoxNjYwNzYxNDUwLCJleHAiOjE2NjMzNTM0NTB9.X4-j6zee8o685PUrL9C6oC2m6TayKuJQHhN8iLgG8kI"
66+
}, {
67+
"featureId": "cl6xnzjpq0dmv07yo7phz7ofz",
68+
"schemaId": "cl6xnuwt95lqq07330tbb3mfd",
69+
"color": "#1CE6FF",
70+
"title": "boxy",
71+
"value": "boxy",
72+
"bbox": {
73+
"top": 117.39,
74+
"left": 4.25,
75+
"height": 456.92,
76+
"width": 164.83
77+
},
78+
"page": 7,
79+
"unit": "POINTS",
80+
"instanceURI": "https://api.labelbox.com/masks/feature/cl6xnzjpq0dmv07yo7phz7ofz?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJja2NjOWZtbXc0aGNkMDczOHFpeWM2YW54Iiwib3JnYW5pemF0aW9uSWQiOiJja2N6NmJ1YnVkeWZpMDg1NW8xZHQxZzlzIiwiaWF0IjoxNjYwNzYxNDUwLCJleHAiOjE2NjMzNTM0NTB9.X4-j6zee8o685PUrL9C6oC2m6TayKuJQHhN8iLgG8kI"
81+
}, {
82+
"featureId": "cl6xnzjpq0dmw07yofocp6uf6",
83+
"schemaId": "cl6xnuwt95lqq07330tbb3mfd",
84+
"color": "#1CE6FF",
85+
"title": "boxy",
86+
"value": "boxy",
87+
"bbox": {
88+
"top": 82.13,
89+
"left": 217.28,
90+
"height": 279.76,
91+
"width": 82.43
92+
},
93+
"page": 8,
94+
"unit": "POINTS",
95+
"instanceURI": "https://api.labelbox.com/masks/feature/cl6xnzjpq0dmw07yofocp6uf6?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJja2NjOWZtbXc0aGNkMDczOHFpeWM2YW54Iiwib3JnYW5pemF0aW9uSWQiOiJja2N6NmJ1YnVkeWZpMDg1NW8xZHQxZzlzIiwiaWF0IjoxNjYwNzYxNDUwLCJleHAiOjE2NjMzNTM0NTB9.X4-j6zee8o685PUrL9C6oC2m6TayKuJQHhN8iLgG8kI"
96+
}, {
97+
"featureId": "cl6xnzjpq0dmx07yo0qh40z0n",
98+
"schemaId": "cl6xnuwt95lqq07330tbb3mfd",
99+
"color": "#1CE6FF",
100+
"title": "boxy",
101+
"value": "boxy",
102+
"bbox": {
103+
"top": 298.12,
104+
"left": 83.34,
105+
"height": 203.83,
106+
"width": 0.38
107+
},
108+
"page": 3,
109+
"unit": "POINTS",
110+
"instanceURI": "https://api.labelbox.com/masks/feature/cl6xnzjpq0dmx07yo0qh40z0n?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJja2NjOWZtbXc0aGNkMDczOHFpeWM2YW54Iiwib3JnYW5pemF0aW9uSWQiOiJja2N6NmJ1YnVkeWZpMDg1NW8xZHQxZzlzIiwiaWF0IjoxNjYwNzYxNDUwLCJleHAiOjE2NjMzNTM0NTB9.X4-j6zee8o685PUrL9C6oC2m6TayKuJQHhN8iLgG8kI"
111+
}],
112+
"classifications": [],
113+
"relationships": []
114+
},
115+
"Created By": "[email protected]",
116+
"Project Name": "PDF MAL Test",
117+
"Created At": "2022-08-17T18:37:18.000Z",
118+
"Updated At": "2022-08-17T18:37:20.073Z",
119+
"Seconds to Label": 15.003,
120+
"External ID": "Airbnb_ Labelbox - Focus on Workforce - Labelbox Labeling Operations (1).pdf",
121+
"Global Key": null,
122+
"Agreement": -1,
123+
"Benchmark Agreement": -1,
124+
"Benchmark ID": null,
125+
"Dataset Name": "PDF ",
126+
"Reviews": [],
127+
"View Label": "https://editor.labelbox.com?project=cl6xntneb7t28072bggdydv7a&label=cl6xnzi4a7ldn0729381g7104",
128+
"Has Open Issues": 0,
129+
"Skipped": false
130+
}]
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import json
2+
from typing import Dict, Any
3+
4+
from labelbox.data.serialization.labelbox_v1.converter import LBV1Converter
5+
6+
IGNORE_KEYS = [
7+
"Data Split", "media_type", "DataRow Metadata", "Media Attributes"
8+
]
9+
10+
11+
def round_dict(data: Dict[str, Any]) -> Dict[str, Any]:
12+
for key in data:
13+
if isinstance(data[key], float):
14+
data[key] = int(data[key])
15+
elif isinstance(data[key], dict):
16+
data[key] = round_dict(data[key])
17+
return data
18+
19+
20+
def test_pdf():
21+
"""
22+
Tests an export from a pdf document with only bounding boxes
23+
"""
24+
payload = json.load(
25+
open('tests/data/assets/labelbox_v1/pdf_export.json', 'r'))
26+
collection = LBV1Converter.deserialize(payload)
27+
serialized = next(LBV1Converter.serialize(collection))
28+
29+
payload = payload[0] # only one document in the export
30+
31+
serialized = {k: v for k, v in serialized.items() if k not in IGNORE_KEYS}
32+
33+
assert serialized.keys() == payload.keys()
34+
for key in payload.keys():
35+
if key == 'Label':
36+
serialized_no_classes = [{
37+
k: v for k, v in dic.items() if k != 'classifications'
38+
} for dic in serialized[key]['objects']]
39+
serialized_round = [
40+
round_dict(dic) for dic in serialized_no_classes
41+
]
42+
payload_round = [round_dict(dic) for dic in payload[key]['objects']]
43+
assert payload_round == serialized_round
44+
else:
45+
assert serialized[key] == payload[key]

0 commit comments

Comments
 (0)