Skip to content

Commit a98b182

Browse files
author
Kevin Kim
committed
Merge remote-tracking branch 'origin/develop' into kkim/AL-2896
2 parents 0331600 + 5e1e228 commit a98b182

24 files changed

+707
-111
lines changed

labelbox/data/annotation_types/collection.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@ def assign_feature_schema_ids(
3434
self, ontology_builder: "ontology.OntologyBuilder") -> "LabelList":
3535
"""
3636
Adds schema ids to all FeatureSchema objects in the Labels.
37-
This is necessary for MAL.
3837
3938
Args:
4039
ontology_builder: The ontology that matches the feature names assigned to objects in this LabelList
4140
Returns:
4241
LabelList. useful for chaining these modifying functions
42+
43+
Note: You can now import annotations using names directly without having to lookup schema_ids
4344
"""
4445
for label in self._data:
4546
label.assign_feature_schema_ids(ontology_builder)

labelbox/data/annotation_types/feature.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ class FeatureSchema(BaseModel):
1010
Class that represents a feature schema.
1111
Could be a annotation, a subclass, or an option.
1212
Schema ids might not be known when constructing these objects so both a name and schema id are valid.
13-
14-
Use `LabelList.assign_feature_schema_ids` or `LabelGenerator.assign_feature_schema_ids`
15-
to retroactively add schema ids by looking them up from the names.
1613
"""
1714
name: Optional[str] = None
1815
feature_schema_id: Optional[Cuid] = None
@@ -24,3 +21,11 @@ def must_set_one(cls, values):
2421
"Must set either feature_schema_id or name for all feature schemas"
2522
)
2623
return values
24+
25+
def dict(self, *args, **kwargs):
26+
res = super().dict(*args, **kwargs)
27+
if 'name' in res and res['name'] is None:
28+
res.pop('name')
29+
if 'featureSchemaId' in res and res['featureSchemaId'] is None:
30+
res.pop('featureSchemaId')
31+
return res

labelbox/data/annotation_types/label.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,13 @@ def assign_feature_schema_ids(
130130
self, ontology_builder: ontology.OntologyBuilder) -> "Label":
131131
"""
132132
Adds schema ids to all FeatureSchema objects in the Labels.
133-
This is necessary for MAL.
134133
135134
Args:
136135
ontology_builder: The ontology that matches the feature names assigned to objects in this dataset
137136
Returns:
138137
Label. useful for chaining these modifying functions
138+
139+
Note: You can now import annotations using names directly without having to lookup schema_ids
139140
"""
140141
tool_lookup, classification_lookup = get_feature_schema_lookup(
141142
ontology_builder)

labelbox/data/serialization/ndjson/base.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from typing import Optional
12
from uuid import uuid4
2-
from pydantic import BaseModel, validator, Field
3+
from pydantic import BaseModel, root_validator, validator, Field
34

45
from labelbox.utils import camel_case
56
from ...annotation_types.types import Cuid
@@ -32,12 +33,21 @@ class Config:
3233

3334

3435
class NDAnnotation(NDJsonBase):
35-
schema_id: Cuid
36-
37-
@validator('schema_id', pre=True, always=True)
38-
def validate_id(cls, v):
39-
if v is None:
40-
raise ValueError(
41-
"Schema ids are not set. Use `LabelGenerator.assign_feature_schema_ids`, `LabelList.assign_feature_schema_ids`, or `Label.assign_feature_schema_ids`."
42-
)
43-
return v
36+
name: Optional[str] = None
37+
schema_id: Optional[Cuid] = None
38+
39+
@root_validator()
40+
def must_set_one(cls, values):
41+
if ('schema_id' not in values or
42+
values['schema_id'] is None) and ('name' not in values or
43+
values['name'] is None):
44+
raise ValueError("Schema id or name are not set. Set either one.")
45+
return values
46+
47+
def dict(self, *args, **kwargs):
48+
res = super().dict(*args, **kwargs)
49+
if 'name' in res and res['name'] is None:
50+
res.pop('name')
51+
if 'schemaId' in res and res['schemaId'] is None:
52+
res.pop('schemaId')
53+
return res

labelbox/data/serialization/ndjson/classification.py

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Dict, List, Union, Optional
22

3-
from pydantic import BaseModel, Field, validator
3+
from pydantic import BaseModel, Field, root_validator
44

55
from labelbox.utils import camel_case
66
from ...annotation_types.annotation import ClassificationAnnotation, VideoClassificationAnnotation
@@ -11,15 +11,24 @@
1111

1212

1313
class NDFeature(BaseModel):
14-
schema_id: Cuid
14+
name: Optional[str] = None
15+
schema_id: Optional[Cuid] = None
1516

16-
@validator('schema_id', pre=True, always=True)
17-
def validate_id(cls, v):
18-
if v is None:
19-
raise ValueError(
20-
"Schema ids are not set. Use `LabelGenerator.assign_feature_schema_ids`, `LabelList.assign_feature_schema_ids`, or `Label.assign_feature_schema_ids`."
21-
)
22-
return v
17+
@root_validator()
18+
def must_set_one(cls, values):
19+
if ('schema_id' not in values or
20+
values['schema_id'] is None) and ('name' not in values or
21+
values['name'] is None):
22+
raise ValueError("Schema id or name are not set. Set either one.")
23+
return values
24+
25+
def dict(self, *args, **kwargs):
26+
res = super().dict(*args, **kwargs)
27+
if 'name' in res and res['name'] is None:
28+
res.pop('name')
29+
if 'schemaId' in res and res['schemaId'] is None:
30+
res.pop('schemaId')
31+
return res
2332

2433
class Config:
2534
allow_population_by_field_name = True
@@ -50,27 +59,29 @@ def to_common(self) -> Text:
5059
return Text(answer=self.answer)
5160

5261
@classmethod
53-
def from_common(cls, text: Text,
62+
def from_common(cls, text: Text, name: str,
5463
feature_schema_id: Cuid) -> "NDTextSubclass":
55-
return cls(answer=text.answer, schema_id=feature_schema_id)
64+
return cls(answer=text.answer, name=name, schema_id=feature_schema_id)
5665

5766

5867
class NDChecklistSubclass(NDFeature):
5968
answer: List[NDFeature] = Field(..., alias='answers')
6069

6170
def to_common(self) -> Checklist:
6271
return Checklist(answer=[
63-
ClassificationAnswer(feature_schema_id=answer.schema_id)
72+
ClassificationAnswer(name=answer.name,
73+
feature_schema_id=answer.schema_id)
6474
for answer in self.answer
6575
])
6676

6777
@classmethod
68-
def from_common(cls, checklist: Checklist,
78+
def from_common(cls, checklist: Checklist, name: str,
6979
feature_schema_id: Cuid) -> "NDChecklistSubclass":
7080
return cls(answer=[
71-
NDFeature(schema_id=answer.feature_schema_id)
81+
NDFeature(name=answer.name, schema_id=answer.feature_schema_id)
7282
for answer in checklist.answer
7383
],
84+
name=name,
7485
schema_id=feature_schema_id)
7586

7687
def dict(self, *args, **kwargs):
@@ -85,12 +96,14 @@ class NDRadioSubclass(NDFeature):
8596

8697
def to_common(self) -> Radio:
8798
return Radio(answer=ClassificationAnswer(
88-
feature_schema_id=self.answer.schema_id))
99+
name=self.answer.name, feature_schema_id=self.answer.schema_id))
89100

90101
@classmethod
91-
def from_common(cls, radio: Radio,
102+
def from_common(cls, radio: Radio, name: str,
92103
feature_schema_id: Cuid) -> "NDRadioSubclass":
93-
return cls(answer=NDFeature(schema_id=radio.answer.feature_schema_id),
104+
return cls(answer=NDFeature(name=radio.answer.name,
105+
schema_id=radio.answer.feature_schema_id),
106+
name=name,
94107
schema_id=feature_schema_id)
95108

96109

@@ -100,12 +113,13 @@ def from_common(cls, radio: Radio,
100113
class NDText(NDAnnotation, NDTextSubclass):
101114

102115
@classmethod
103-
def from_common(cls, text: Text, feature_schema_id: Cuid,
116+
def from_common(cls, text: Text, name: str, feature_schema_id: Cuid,
104117
extra: Dict[str, Any], data: Union[TextData,
105118
ImageData]) -> "NDText":
106119
return cls(
107120
answer=text.answer,
108121
data_row={'id': data.uid},
122+
name=name,
109123
schema_id=feature_schema_id,
110124
uuid=extra.get('uuid'),
111125
)
@@ -115,14 +129,15 @@ class NDChecklist(NDAnnotation, NDChecklistSubclass, VideoSupported):
115129

116130
@classmethod
117131
def from_common(
118-
cls, checklist: Checklist, feature_schema_id: Cuid,
132+
cls, checklist: Checklist, name: str, feature_schema_id: Cuid,
119133
extra: Dict[str, Any], data: Union[VideoData, TextData,
120134
ImageData]) -> "NDChecklist":
121135
return cls(answer=[
122-
NDFeature(schema_id=answer.feature_schema_id)
136+
NDFeature(name=answer.name, schema_id=answer.feature_schema_id)
123137
for answer in checklist.answer
124138
],
125139
data_row={'id': data.uid},
140+
name=name,
126141
schema_id=feature_schema_id,
127142
uuid=extra.get('uuid'),
128143
frames=extra.get('frames'))
@@ -131,11 +146,13 @@ def from_common(
131146
class NDRadio(NDAnnotation, NDRadioSubclass, VideoSupported):
132147

133148
@classmethod
134-
def from_common(cls, radio: Radio, feature_schema_id: Cuid,
149+
def from_common(cls, radio: Radio, name: str, feature_schema_id: Cuid,
135150
extra: Dict[str, Any], data: Union[VideoData, TextData,
136151
ImageData]) -> "NDRadio":
137-
return cls(answer=NDFeature(schema_id=radio.answer.feature_schema_id),
152+
return cls(answer=NDFeature(name=radio.answer.name,
153+
schema_id=radio.answer.feature_schema_id),
138154
data_row={'id': data.uid},
155+
name=name,
139156
schema_id=feature_schema_id,
140157
uuid=extra.get('uuid'),
141158
frames=extra.get('frames'))
@@ -152,13 +169,14 @@ def from_common(
152169
raise TypeError(
153170
f"Unable to convert object to MAL format. `{type(annotation.value)}`"
154171
)
155-
return classify_obj.from_common(annotation.value,
172+
return classify_obj.from_common(annotation.value, annotation.name,
156173
annotation.feature_schema_id)
157174

158175
@staticmethod
159176
def to_common(
160177
annotation: "NDClassificationType") -> ClassificationAnnotation:
161178
return ClassificationAnnotation(value=annotation.to_common(),
179+
name=annotation.name,
162180
feature_schema_id=annotation.schema_id)
163181

164182
@staticmethod
@@ -182,6 +200,7 @@ def to_common(
182200
) -> Union[ClassificationAnnotation, VideoClassificationAnnotation]:
183201
common = ClassificationAnnotation(
184202
value=annotation.to_common(),
203+
name=annotation.name,
185204
feature_schema_id=annotation.schema_id,
186205
extra={'uuid': annotation.uuid})
187206
if getattr(annotation, 'frames', None) is None:
@@ -204,7 +223,7 @@ def from_common(
204223
raise TypeError(
205224
f"Unable to convert object to MAL format. `{type(annotation.value)}`"
206225
)
207-
return classify_obj.from_common(annotation.value,
226+
return classify_obj.from_common(annotation.value, annotation.name,
208227
annotation.feature_schema_id,
209228
annotation.extra, data)
210229

labelbox/data/serialization/ndjson/label.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ def _generate_annotations(
5050
for annotation in annotations:
5151
if isinstance(annotation, NDSegments):
5252
annots.extend(
53-
NDSegments.to_common(annotation, annotation.schema_id))
53+
NDSegments.to_common(annotation, annotation.name,
54+
annotation.schema_id))
5455
elif isinstance(annotation, NDObjectType.__args__):
5556
annots.append(NDObject.to_common(annotation))
5657
elif isinstance(annotation, NDClassificationType.__args__):
@@ -97,7 +98,8 @@ def _create_video_annotations(
9798
if isinstance(
9899
annot,
99100
(VideoClassificationAnnotation, VideoObjectAnnotation)):
100-
video_annotations[annot.feature_schema_id].append(annot)
101+
video_annotations[annot.feature_schema_id or
102+
annot.name].append(annot)
101103

102104
for annotation_group in video_annotations.values():
103105
consecutive_frames = cls._get_consecutive_frames(

0 commit comments

Comments
 (0)