Skip to content

Commit 580b9d6

Browse files
author
Matt Sokoloff
committed
Merge branch 'develop' of https://github.com/Labelbox/labelbox-python into ms/video-classification-keyframe
2 parents 543245d + b6a6bc3 commit 580b9d6

File tree

10 files changed

+98
-45
lines changed

10 files changed

+98
-45
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
* Set up a project from an existing ontology with `project.setup_edior()`
1919
* Added new `FeatureSchema` entity
2020
* Add support for new queue modes
21-
* Send batches of data direction to a project with `project.queue()`
22-
* Remove items from the queue with `project.dequeue()`
21+
* Send batches of data directly to a project queue with `project.queue()`
22+
* Remove items from a project queue with `project.dequeue()`
2323
* Query for and toggle the queue mode
2424

2525
# Version 3.8.0 (2021-10-22)

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,9 @@ The package `rasterio` installed by `labelbox[data]` relies on GDAL which could
5757
You may see the following error message:
5858

5959
```
60-
INFO:root:Building on Windows requires extra options to setup.py to locate needed GDAL files. More information is available in the README.
60+
INFO:root:Building on Windows requires extra options to setup.py to locate needed GDAL files. More information is available in the README.
6161
62-
ERROR: A GDAL API version must be specified. Provide a path to gdal-config using a GDAL_CONFIG environment variable or use a GDAL_VERSION environment variable.
62+
ERROR: A GDAL API version must be specified. Provide a path to gdal-config using a GDAL_CONFIG environment variable or use a GDAL_VERSION environment variable.
6363
```
6464

6565
As a workaround:
@@ -72,7 +72,7 @@ As a workaround:
7272

7373
Note: You need to download the right files for your Python version. In the files above `cp38` means CPython 3.8.
7474

75-
2. After downloading the files, please run the following commands, in this particular order.
75+
2. After downloading the files, please run the following commands, in this particular order.
7676

7777
```
7878
pip install GDAL‑3.3.2‑cp38‑cp38‑win_amd64.wh

docs/source/index.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,23 @@ Enums
154154
:members:
155155
:show-inheritance:
156156

157+
ModelRun
158+
----------------------------
159+
160+
.. automodule:: labelbox.schema.model_run
161+
:members:
162+
:show-inheritance:
163+
164+
Model
165+
----------------------------
166+
167+
.. automodule:: labelbox.schema.model
168+
:members:
169+
:show-inheritance:
170+
171+
DataRowMetadata
172+
----------------------------
173+
174+
.. automodule:: labelbox.schema.data_row_metadata
175+
:members:
176+
:show-inheritance:

labelbox/data/annotation_types/classification/classification.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict, List, Union
1+
from typing import Any, Dict, List, Union, Optional
22

33
try:
44
from typing import Literal
@@ -24,32 +24,47 @@ class ClassificationAnswer(FeatureSchema):
2424
- Represents a classification option.
2525
- Because it inherits from FeatureSchema
2626
the option can be represented with either the name or feature_schema_id
27+
28+
- The key frame arg only applies to video classifications.
29+
Each answer can have a key frame indepdent of the others.
30+
So unlike object annotations, classification annotations
31+
track key frames at a classification answer level.
2732
"""
2833
extra: Dict[str, Any] = {}
34+
keyframe: Optional[bool] = None
2935

30-
31-
class VideoClassificationAnswer(ClassificationAnswer):
32-
"""
33-
Each answer can have a key frame indepdent of the others.
34-
So unlike object annotations, classification annotations
35-
track key frames at a classification answer level.
36-
"""
37-
keyframe: bool
36+
def dict(self, *args, **kwargs):
37+
res = super().dict(*args, **kwargs)
38+
if res['keyframe'] is None:
39+
res.pop('keyframe')
40+
return res
3841

3942

4043
class Radio(BaseModel):
41-
""" A classification with only one selected option allowed """
42-
answer: Union[VideoClassificationAnswer, ClassificationAnswer]
44+
""" A classification with only one selected option allowed
45+
46+
>>> Radio(answer = ClassificationAnswer(name = "dog"))
47+
48+
"""
49+
answer: ClassificationAnswer
4350

4451

4552
class Checklist(_TempName):
46-
""" A classification with many selected options allowed """
53+
""" A classification with many selected options allowed
54+
55+
>>> Checklist(answer = [ClassificationAnswer(name = "cloudy")])
56+
57+
"""
4758
name: Literal["checklist"] = "checklist"
48-
answer: Union[VideoClassificationAnswer, ClassificationAnswer]
59+
answer: ClassificationAnswer
4960

5061

5162
class Text(BaseModel):
52-
""" Free form text """
63+
""" Free form text
64+
65+
>>> Text(answer = "some text answer")
66+
67+
"""
5368
answer: str
5469

5570

@@ -59,4 +74,4 @@ class Dropdown(_TempName):
5974
- This is not currently compatible with MAL.
6075
"""
6176
name: Literal["dropdown"] = "dropdown"
62-
answer: List[Union[VideoClassificationAnswer, ClassificationAnswer]]
77+
answer: List[ClassificationAnswer]

labelbox/data/annotation_types/data/text.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99

1010
class TextData(BaseData):
1111
"""
12-
Represents text data
12+
Represents text data. Requires arg file_path, text, or url
13+
14+
>>> TextData(text="")
15+
16+
Args:
17+
file_path (str)
18+
text (str)
19+
url (str)
1320
"""
1421
file_path: Optional[str] = None
1522
text: Optional[str] = None

labelbox/data/annotation_types/geometry/line.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import geojson
44
import numpy as np
55
import cv2
6+
from pydantic import validator
67

78
from .point import Point
89
from .geometry import Geometry
@@ -14,6 +15,8 @@ class Line(Geometry):
1415
Args:
1516
points (List[Point]): A list of `Point` geometries
1617
18+
>>> Line(points = [Point(x=3,y=4), Point(x=3,y=5)])
19+
1720
"""
1821
points: List[Point]
1922

@@ -47,3 +50,12 @@ def draw(self,
4750
False,
4851
color=color,
4952
thickness=thickness)
53+
54+
@validator('points')
55+
def is_geom_valid(cls, points):
56+
if len(points) < 2:
57+
raise ValueError(
58+
f"A line must have at least 2 points to be valid. Found {points}"
59+
)
60+
61+
return points

labelbox/data/serialization/labelbox_v1/classification.py

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from labelbox.data.annotation_types.classification.classification import VideoClassificationAnswer
21
from typing import List, Union
32

43
from pydantic.main import BaseModel
@@ -10,47 +9,37 @@
109

1110

1211
class LBV1ClassificationAnswer(LBV1Feature):
13-
...
14-
15-
16-
def get_classification_answer(
17-
answer: LBV1ClassificationAnswer
18-
) -> Union[ClassificationAnswer, VideoClassificationAnswer]:
19-
kwargs = dict(feature_schema_id=answer.schema_id,
12+
def to_common(self, answer: "LBV1ClassificationAnswer") -> ClassificationAnswer:
13+
return ClassificationAnswer(feature_schema_id=answer.schema_id,
2014
name=answer.title,
15+
keyframe = answer.keyframe,
2116
extra={
2217
'feature_id': answer.feature_id,
2318
'value': answer.value
2419
})
25-
if answer.keyframe is not None:
26-
return VideoClassificationAnswer(keyframe=answer.keyframe, **kwargs)
27-
else:
28-
return ClassificationAnswer(**kwargs)
2920

30-
31-
def get_lbv1_classification_answer(answer: Union[ClassificationAnswer,
32-
VideoClassificationAnswer]):
33-
return LBV1ClassificationAnswer(
21+
@classmethod
22+
def from_common(cls, answer: ClassificationAnnotation) -> "LBV1ClassificationAnswer":
23+
return cls(
3424
schema_id=answer.feature_schema_id,
3525
title=answer.name,
3626
value=answer.extra.get('value'),
3727
feature_id=answer.extra.get('feature_id'),
38-
keyframe=getattr(answer, 'keyframe',
39-
None) # Only applies to video clasifications
28+
keyframe=answer.keyframe
4029
)
4130

4231

4332
class LBV1Radio(LBV1Feature):
4433
answer: LBV1ClassificationAnswer
4534

4635
def to_common(self) -> Radio:
47-
return Radio(answer=get_classification_answer(self.answer))
36+
return Radio(answer=self.answer.to_common())
4837

4938
@classmethod
5039
def from_common(cls, radio: Radio, feature_schema_id: Cuid,
5140
**extra) -> "LBV1Radio":
5241
return cls(schema_id=feature_schema_id,
53-
answer=get_lbv1_classification_answer(radio.answer),
42+
answer=LBV1ClassificationAnswer.from_common(radio.answer),
5443
**extra)
5544

5645

@@ -59,15 +48,15 @@ class LBV1Checklist(LBV1Feature):
5948

6049
def to_common(self) -> Checklist:
6150
return Checklist(answer=[
62-
get_classification_answer(answer) for answer in self.answers
51+
answer.to_common() for answer in self.answers
6352
])
6453

6554
@classmethod
6655
def from_common(cls, checklist: Checklist, feature_schema_id: Cuid,
6756
**extra) -> "LBV1Checklist":
6857
return cls(schema_id=feature_schema_id,
6958
answers=[
70-
get_lbv1_classification_answer(answer)
59+
LBV1ClassificationAnswer.from_common(answer)
7160
for answer in checklist.answer
7261
],
7362
**extra)
@@ -78,15 +67,15 @@ class LBV1Dropdown(LBV1Feature):
7867

7968
def to_common(self) -> Dropdown:
8069
return Dropdown(answer=[
81-
get_classification_answer(answer) for answer in self.answer
70+
answer.to_common() for answer in self.answer
8271
])
8372

8473
@classmethod
8574
def from_common(cls, dropdown: Dropdown, feature_schema_id: Cuid,
8675
**extra) -> "LBV1Dropdown":
8776
return cls(schema_id=feature_schema_id,
8877
answer=[
89-
get_lbv1_classification_answer(answer)
78+
LBV1ClassificationAnswer.from_common(answer)
9079
for answer in dropdown.answer
9180
],
9281
**extra)

labelbox/schema/data_row_metadata.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class DataRowMetadataSchema(BaseModel):
3232

3333
@property
3434
def id(self):
35+
""" `DataRowMetadataSchema.id is being deprecated after version 3.9
36+
in favor of DataRowMetadataSchema.uid`
37+
"""
3538
warnings.warn("`id` is being deprecated in favor of `uid`")
3639
return self.uid
3740

labelbox/schema/model_run.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ def model_run_data_rows(self):
145145
['annotationGroups', 'pageInfo', 'endCursor'])
146146

147147
def annotation_groups(self):
148+
""" `ModelRun.annotation_groups is being deprecated after version 3.9
149+
in favor of ModelRun.model_run_data_rows`
150+
"""
148151
warnings.warn(
149152
"`ModelRun.annotation_groups` is being deprecated in favor of `ModelRun.model_run_data_rows`"
150153
)
@@ -181,6 +184,9 @@ def delete_model_run_data_rows(self, data_row_ids):
181184
})
182185

183186
def delete_annotation_groups(self, data_row_ids):
187+
""" `ModelRun.delete_annotation_groups is being deprecated after version 3.9
188+
in favor of ModelRun.delete_model_run_data_rows`
189+
"""
184190
warnings.warn(
185191
"`ModelRun.delete_annotation_groups` is being deprecated in favor of `ModelRun.delete_model_run_data_rows`"
186192
)

tests/data/annotation_types/classification/test_classification.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ def test_checklist():
125125

126126
with pytest.raises(ValidationError):
127127
classification = Checklist(answer=answer)
128+
breakpoint()
128129

129130
classification = Checklist(answer=[answer])
130131
assert classification.dict() == {

0 commit comments

Comments
 (0)