Skip to content

Commit af3ec5c

Browse files
authored
Merge pull request #296 from Labelbox/develop
3.6.0
2 parents 5cc02ab + 9ad79ce commit af3ec5c

26 files changed

+654
-230
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
11
# Changelog
2+
# Version 3.6.0 (2021-04-10)
3+
## Added
4+
* Bulk export metadata with `DataRowMetadataOntology.bulk_export()`
5+
* Add docstring examples of annotation types and a few helper methods
6+
7+
## Updated
8+
* Update metadata notebook under examples/basics to include bulk_export.
9+
* Allow color to be a single integer when constructing Mask objects
10+
* Allow users to pass int arrays to RasterData and attempt coercion to uint8
11+
12+
## Removed
13+
* data_row.metadata was removed in favor of bulk exports.
14+
15+
216
# Version 3.5.0 (2021-15-09)
317
## Added
418
* Diagnostics custom metrics

examples/basics/data_row_metadata.ipynb

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@
5555
},
5656
"outputs": [],
5757
"source": [
58-
"%%capture\n",
59-
"!pip install --upgrade tensorflow-hub scikit-learn\n",
60-
"!pip install --upgrade \"labelbox[data]\""
58+
"!pip install -q --upgrade tensorflow-hub \\\n",
59+
" scikit-learn \\\n",
60+
" seaborn \\\n",
61+
" \"labelbox[data]\""
6162
]
6263
},
6364
{
@@ -90,12 +91,13 @@
9091
")\n",
9192
"from sklearn.random_projection import GaussianRandomProjection\n",
9293
"import seaborn as sns\n",
93-
"import datetime\n",
94+
"from datetime import datetime\n",
9495
"from pprint import pprint\n",
9596
"import tensorflow_hub as hub\n",
9697
"from tqdm.notebook import tqdm\n",
9798
"import requests\n",
98-
"import tensorflow as tf"
99+
"import tensorflow as tf\n",
100+
"from pprint import pprint"
99101
]
100102
},
101103
{
@@ -361,7 +363,7 @@
361363
" split = \"cko8sbscr0003h2dk04w86hof\"\n",
362364
" \n",
363365
" embeddings.append(list(model(processor(response.content), training=False)[0].numpy()))\n",
364-
" dt = datetime.datetime.utcnow() \n",
366+
" dt = datetime.utcnow() \n",
365367
" message =\"my-new-message\"\n",
366368
"\n",
367369
" uploads.append(\n",
@@ -525,7 +527,7 @@
525527
},
526528
"outputs": [],
527529
"source": [
528-
"metadata = mdo.parse_metadata([datarow.metadata])"
530+
"metadata = mdo.bulk_export([datarow.uid])[0]"
529531
]
530532
},
531533
{
@@ -584,7 +586,7 @@
584586
},
585587
"outputs": [],
586588
"source": [
587-
"len(client.get_data_row(deletes.data_row_id).metadata[\"fields\"])"
589+
"len(mdo.bulk_export(deletes.data_row_id)[0].fields)"
588590
]
589591
},
590592
{
@@ -608,7 +610,7 @@
608610
},
609611
"outputs": [],
610612
"source": [
611-
"len(client.get_data_row(deletes.data_row_id).metadata[\"fields\"])"
613+
"len(mdo.bulk_export(deletes.data_row_id)[0].fields)"
612614
]
613615
},
614616
{

examples/model_diagnostics/custom_metrics_basics.ipynb

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,11 @@
9696
"metadata": {},
9797
"source": [
9898
"## Custom Metrics\n",
99-
"* Users can provide metrics to provide metric information at different levels of granularity.\n",
100-
" * Users can provide metrics for \n",
101-
" 1. data rows\n",
102-
" 2. features\n",
103-
" 3. subclasses\n",
104-
" * Additionally, metrics can be given custom names to best describe what they are measuring.\n",
99+
"* Users can provide metrics at the following levels of granularity:\n",
100+
" 1. data rows\n",
101+
" 2. features\n",
102+
" 3. subclasses\n",
103+
"* Additionally, metrics can be given custom names to best describe what they are measuring.\n",
105104
" \n",
106105
"* Limits and Behavior:\n",
107106
" * At a data row cannot have more than 20 metrics\n",

labelbox/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
name = "labelbox"
2-
__version__ = "3.5.0"
2+
__version__ = "3.6.0"
33

44
from labelbox.schema.project import Project
55
from labelbox.client import Client
66
from labelbox.schema.model import Model
77
from labelbox.schema.bulk_import_request import BulkImportRequest
8-
from labelbox.schema.annotation_import import MALPredictionImport, MEAPredictionImport
8+
from labelbox.schema.annotation_import import MALPredictionImport, MEAPredictionImport, LabelImport
99
from labelbox.schema.dataset import Dataset
1010
from labelbox.schema.data_row import DataRow
1111
from labelbox.schema.label import Label
Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,92 @@
1+
import abc
12
from typing import Any, Dict, List, Union
23

34
from .classification import Checklist, Dropdown, Radio, Text
45
from .feature import FeatureSchema
5-
from .geometry import Geometry
6+
from .geometry import Geometry, Rectangle, Point
67
from .ner import TextEntity
78

89

9-
class BaseAnnotation(FeatureSchema):
10+
class BaseAnnotation(FeatureSchema, abc.ABC):
1011
""" Base annotation class. Shouldn't be directly instantiated
1112
"""
1213
extra: Dict[str, Any] = {}
1314

1415

1516
class ClassificationAnnotation(BaseAnnotation):
16-
"""Class representing classification annotations (annotations that don't have a location) """
17+
"""Classification annotations (non localized)
18+
19+
>>> ClassificationAnnotation(
20+
>>> value=Text(answer="my caption message"),
21+
>>> feature_schema_id="my-feature-schema-id"
22+
>>> )
23+
24+
Args:
25+
name (Optional[str])
26+
feature_schema_id (Optional[Cuid])
27+
value (Union[Text, Checklist, Radio, Dropdown])
28+
extra (Dict[str, Any])
29+
"""
1730

1831
value: Union[Text, Checklist, Radio, Dropdown]
1932

2033

2134
class ObjectAnnotation(BaseAnnotation):
22-
"""Class representing objects annotations (non classifications or annotations that have a location)
35+
"""Generic localized annotation (non classifications)
36+
37+
>>> ObjectAnnotation(
38+
>>> value=Rectangle(
39+
>>> start=Point(x=0, y=0),
40+
>>> end=Point(x=1, y=1)
41+
>>> ),
42+
>>> feature_schema_id="my-feature-schema-id"
43+
>>> )
44+
45+
Args:
46+
name (Optional[str])
47+
feature_schema_id (Optional[Cuid])
48+
value (Union[TextEntity, Geometry]): Localization of the annotation
49+
classifications (Optional[List[ClassificationAnnotation]]): Optional sub classification of the annotation
50+
extra (Dict[str, Any])
2351
"""
2452
value: Union[TextEntity, Geometry]
2553
classifications: List[ClassificationAnnotation] = []
2654

2755

2856
class VideoObjectAnnotation(ObjectAnnotation):
29-
"""
30-
Class for video objects annotations
57+
"""Video object annotation
58+
59+
>>> VideoObjectAnnotation(
60+
>>> keyframe=True,
61+
>>> frame=10,
62+
>>> value=Rectangle(
63+
>>> start=Point(x=0, y=0),
64+
>>> end=Point(x=1, y=1)
65+
>>> ),
66+
>>> feature_schema_id="my-feature-schema-id"
67+
>>>)
3168
3269
Args:
33-
frame: The frame index that this annotation corresponds to
34-
keyframe: Whether or not this annotation was a human generated or interpolated annotation
70+
name (Optional[str])
71+
feature_schema_id (Optional[Cuid])
72+
value (Geometry)
73+
frame (Int): The frame index that this annotation corresponds to
74+
keyframe (bool): Whether or not this annotation was a human generated or interpolated annotation
75+
classifications (List[ClassificationAnnotation]) = []
76+
extra (Dict[str, Any])
3577
"""
3678
frame: int
3779
keyframe: bool
3880

3981

4082
class VideoClassificationAnnotation(ClassificationAnnotation):
41-
"""
42-
Class for video classification annotations
83+
"""Video classification
4384
4485
Args:
45-
frame: The frame index that this annotation corresponds to
86+
name (Optional[str])
87+
feature_schema_id (Optional[Cuid])
88+
value (Union[Text, Checklist, Radio, Dropdown])
89+
frame (int): The frame index that this annotation corresponds to
90+
extra (Dict[str, Any])
4691
"""
4792
frame: int

labelbox/data/annotation_types/data/raster.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,53 @@
1-
from typing import Callable, Optional
2-
from io import BytesIO
31
from abc import ABC
4-
2+
from io import BytesIO
3+
from typing import Callable, Optional, Union
4+
from typing_extensions import Literal
55
import numpy as np
6-
from pydantic import BaseModel
76
import requests
7+
from PIL import Image
88
from google.api_core import retry
9-
from typing_extensions import Literal
9+
from pydantic import BaseModel
1010
from pydantic import root_validator
11-
from PIL import Image
1211

1312
from .base_data import BaseData
1413
from ..types import TypedArray
1514

1615

1716
class RasterData(BaseModel, ABC):
18-
"""
19-
Represents an image or segmentation mask.
17+
"""Represents an image or segmentation mask.
2018
"""
2119
im_bytes: Optional[bytes] = None
2220
file_path: Optional[str] = None
2321
url: Optional[str] = None
2422
arr: Optional[TypedArray[Literal['uint8']]] = None
2523

2624
@classmethod
27-
def from_2D_arr(cls, arr: TypedArray[Literal['uint8']], **kwargs):
25+
def from_2D_arr(cls, arr: Union[TypedArray[Literal['uint8']],
26+
TypedArray[Literal['int']]], **kwargs):
27+
"""Construct from a 2D numpy array
28+
29+
Args:
30+
arr: uint8 compatible numpy array
31+
32+
Returns:
33+
RasterData
34+
"""
35+
2836
if len(arr.shape) != 2:
2937
raise ValueError(
30-
f"Found array with shape {arr.shape}. Expected two dimensions ([W,H])"
38+
f"Found array with shape {arr.shape}. Expected two dimensions [H, W]"
39+
)
40+
41+
if not np.issubdtype(arr.dtype, np.integer):
42+
raise ValueError("Array must be an integer subtype")
43+
44+
if np.can_cast(arr, np.uint8):
45+
arr = arr.astype(np.uint8)
46+
else:
47+
raise ValueError(
48+
"Could not cast array to uint8, check that values are between 0 and 255"
3149
)
50+
3251
arr = np.stack((arr,) * 3, axis=-1)
3352
return cls(arr=arr, **kwargs)
3453

@@ -153,10 +172,10 @@ def validate_args(cls, values):
153172

154173
def __repr__(self) -> str:
155174
symbol_or_none = lambda data: '...' if data is not None else None
156-
return f"{self.__class__.__name__}(im_bytes={symbol_or_none(self.im_bytes)}," \
157-
f"file_path={self.file_path}," \
158-
f"url={self.url}," \
159-
f"arr={symbol_or_none(self.arr)})"
175+
return f"{self.__class__.__name__}(im_bytes={symbol_or_none(self.im_bytes)}," \
176+
f"file_path={self.file_path}," \
177+
f"url={self.url}," \
178+
f"arr={symbol_or_none(self.arr)})"
160179

161180
class Config:
162181
# Required for sharing references
@@ -166,7 +185,25 @@ class Config:
166185

167186

168187
class MaskData(RasterData):
169-
...
188+
"""Used to represent a segmentation Mask
189+
190+
All segments within a mask must be mutually exclusive. At a
191+
single cell, only one class can be present. All Mask data is
192+
converted to a [H,W,3] image. Classes are
193+
194+
>>> # 3x3 mask with two classes and back ground
195+
>>> MaskData.from_2D_arr([
196+
>>> [0, 0, 0],
197+
>>> [1, 1, 1],
198+
>>> [2, 2, 2],
199+
>>>])
200+
201+
Args:
202+
im_bytes: Optional[bytes] = None
203+
file_path: Optional[str] = None
204+
url: Optional[str] = None
205+
arr: Optional[TypedArray[Literal['uint8']]] = None
206+
"""
170207

171208

172209
class ImageData(RasterData, BaseData):

labelbox/data/annotation_types/geometry/geometry.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99

1010
class Geometry(BaseModel, ABC):
11-
"""
12-
Base class for geometry objects.
11+
"""Abstract base class for geometry objects
1312
"""
1413
extra: Dict[str, Any] = {}
1514

labelbox/data/annotation_types/geometry/line.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99

1010

1111
class Line(Geometry):
12+
"""Line annotation
13+
14+
Args:
15+
points (List[Point]): A list of `Point` geometries
16+
17+
"""
1218
points: List[Point]
1319

1420
@property

0 commit comments

Comments
 (0)