Skip to content

Commit bd05089

Browse files
author
Matt Sokoloff
committed
merge with ms/annotation-updates
2 parents c2baea1 + 20c211b commit bd05089

File tree

6 files changed

+85
-69
lines changed

6 files changed

+85
-69
lines changed

.github/workflows/python-package.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,21 @@ jobs:
4747
uses: AlexanderMelde/yapf-action@master
4848
with:
4949
args: --verbose --recursive --parallel --style "google"
50-
50+
- name: dependencies
51+
run: |
52+
sudo apt-get -y update
53+
sudo apt install -y libsm6 \
54+
libxext6 \
55+
ffmpeg \
56+
libfontconfig1 \
57+
libxrender1 \
58+
libgl1-mesa-glx
5159
- name: install labelbox package
5260
run: |
53-
python setup.py install
61+
python -m pip install --upgrade pip
62+
python -m pip install .
5463
- name: mypy
5564
run: |
56-
python -m pip install --upgrade pip
5765
pip install mypy==0.782
5866
mypy -p labelbox --pretty --show-error-codes
5967
- name: Install package and test dependencies

Dockerfile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
FROM python:3.7
22

33
RUN pip install pytest pytest-cases
4-
RUN apt-get -y update && apt install -y libsm6 libxext6 ffmpeg libfontconfig1 libxrender1 libgl1-mesa-glx
4+
RUN apt-get -y update
5+
RUN apt install -y libsm6 \
6+
libxext6 \
7+
ffmpeg \
8+
libfontconfig1 \
9+
libxrender1 \
10+
libgl1-mesa-glx
511

612
WORKDIR /usr/src/labelbox
713
COPY requirements.txt /usr/src/labelbox
814
RUN pip install -r requirements.txt
915
COPY . /usr/src/labelbox
1016

11-
1217
RUN python setup.py install

labelbox/data/generator.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import threading
33
from queue import Queue
44
from typing import Any, Iterable
5+
from concurrent.futures import ThreadPoolExecutor
56

67
logger = logging.getLogger(__name__)
78

@@ -32,10 +33,7 @@ class PrefetchGenerator:
3233
Useful for modifying the generator results based on data from a network
3334
"""
3435

35-
def __init__(self,
36-
data: Iterable[Any],
37-
prefetch_limit=20,
38-
max_concurrency=4):
36+
def __init__(self, data: Iterable[Any], prefetch_limit=20, num_executors=4):
3937
if isinstance(data, (list, tuple)):
4038
self._data = (r for r in data)
4139
else:
@@ -44,14 +42,13 @@ def __init__(self,
4442
self.queue = Queue(prefetch_limit)
4543
self._data = ThreadSafeGen(self._data)
4644
self.completed_threads = 0
47-
self.max_concurrency = max_concurrency
48-
self.threads = [
49-
threading.Thread(target=self.fill_queue)
50-
for _ in range(max_concurrency)
51-
]
52-
for thread in self.threads:
53-
thread.daemon = True
54-
thread.start()
45+
# Can only iterate over once it the queue.get hangs forever.
46+
self.done = False
47+
self.num_executors = num_executors
48+
with ThreadPoolExecutor(max_workers=num_executors) as executor:
49+
self.futures = [
50+
executor.submit(self.fill_queue) for _ in range(num_executors)
51+
]
5552

5653
def _process(self, value) -> Any:
5754
raise NotImplementedError("Abstract method needs to be implemented")
@@ -73,10 +70,13 @@ def __iter__(self):
7370
return self
7471

7572
def __next__(self) -> Any:
73+
if self.done:
74+
raise StopIteration
7675
value = self.queue.get()
7776
while value is None:
7877
self.completed_threads += 1
79-
if self.completed_threads == self.max_concurrency:
78+
if self.completed_threads == self.num_executors:
79+
self.done = True
8080
raise StopIteration
8181
value = self.queue.get()
8282
return value

labelbox/schema/ontology.py

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
1-
import abc
21
from dataclasses import dataclass, field
3-
from enum import Enum, auto
2+
from enum import Enum
43
import colorsys
54

6-
from typing import Any, Callable, Dict, List, Optional, Union
5+
from typing import Any, Dict, List, Optional, Union
76

87
from labelbox.schema.project import Project
9-
from labelbox.orm import query
10-
from labelbox.orm.db_object import DbObject, Updateable, BulkDeletable
11-
from labelbox.orm.model import Entity, Field, Relationship
12-
from labelbox.utils import snake_case, camel_case
8+
from labelbox.orm.db_object import DbObject
9+
from labelbox.orm.model import Field, Relationship
1310
from labelbox.exceptions import InconsistentOntologyException
1411

1512

1613
@dataclass
1714
class Option:
1815
"""
1916
An option is a possible answer within a Classification object in
20-
a Project's ontology.
17+
a Project's ontology.
2118
2219
To instantiate, only the "value" parameter needs to be passed in.
2320
@@ -41,13 +38,11 @@ def label(self):
4138

4239
@classmethod
4340
def from_dict(cls, dictionary: Dict[str, Any]):
44-
return Option(value=dictionary["value"],
45-
schema_id=dictionary.get("schemaNodeId", None),
46-
feature_schema_id=dictionary.get("featureSchemaId", None),
47-
options=[
48-
Classification.from_dict(o)
49-
for o in dictionary.get("options", [])
50-
])
41+
return cls(
42+
value=dictionary["value"],
43+
schema_id=dictionary.get("schemaNodeId", None),
44+
feature_schema_id=dictionary.get("featureSchemaId", None),
45+
options=[cls.from_dict(o) for o in dictionary.get("options", [])])
5146

5247
def asdict(self) -> Dict[str, Any]:
5348
return {
@@ -69,13 +64,13 @@ def add_option(self, option: 'Classification'):
6964
@dataclass
7065
class Classification:
7166
"""
72-
A classfication to be added to a Project's ontology. The
67+
A classfication to be added to a Project's ontology. The
7368
classification is dependent on the Classification Type.
7469
7570
To instantiate, the "class_type" and "instructions" parameters must
7671
be passed in.
7772
78-
The "options" parameter holds a list of Option objects. This is not
73+
The "options" parameter holds a list of Option objects. This is not
7974
necessary for some Classification types, such as TEXT. To see which
8075
types require options, look at the "_REQUIRES_OPTIONS" class variable.
8176
@@ -120,16 +115,15 @@ def name(self):
120115

121116
@classmethod
122117
def from_dict(cls, dictionary: Dict[str, Any]):
123-
return Classification(
124-
class_type=Classification.Type(dictionary["type"]),
125-
instructions=dictionary["instructions"],
126-
required=dictionary.get("required", False),
127-
options=[Option.from_dict(o) for o in dictionary["options"]],
128-
schema_id=dictionary.get("schemaNodeId", None),
129-
feature_schema_id=dictionary.get("featureSchemaId", None))
118+
return cls(class_type=cls.Type(dictionary["type"]),
119+
instructions=dictionary["instructions"],
120+
required=dictionary.get("required", False),
121+
options=[Option.from_dict(o) for o in dictionary["options"]],
122+
schema_id=dictionary.get("schemaNodeId", None),
123+
feature_schema_id=dictionary.get("featureSchemaId", None))
130124

131125
def asdict(self) -> Dict[str, Any]:
132-
if self.class_type in Classification._REQUIRES_OPTIONS \
126+
if self.class_type in self._REQUIRES_OPTIONS \
133127
and len(self.options) < 1:
134128
raise InconsistentOntologyException(
135129
f"Classification '{self.instructions}' requires options.")
@@ -160,13 +154,13 @@ class Tool:
160154
To instantiate, the "tool" and "name" parameters must
161155
be passed in.
162156
163-
The "classifications" parameter holds a list of Classification objects.
157+
The "classifications" parameter holds a list of Classification objects.
164158
This can be used to add nested classifications to a tool.
165159
166160
Example(s):
167161
tool = Tool(
168162
tool = Tool.Type.LINE,
169-
name = "Tool example")
163+
name = "Tool example")
170164
classification = Classification(
171165
class_type = Classification.Type.TEXT,
172166
instructions = "Classification Example")
@@ -200,16 +194,16 @@ class Type(Enum):
200194

201195
@classmethod
202196
def from_dict(cls, dictionary: Dict[str, Any]):
203-
return Tool(name=dictionary['name'],
204-
schema_id=dictionary.get("schemaNodeId", None),
205-
feature_schema_id=dictionary.get("featureSchemaId", None),
206-
required=dictionary.get("required", False),
207-
tool=Tool.Type(dictionary["tool"]),
208-
classifications=[
209-
Classification.from_dict(c)
210-
for c in dictionary["classifications"]
211-
],
212-
color=dictionary["color"])
197+
return cls(name=dictionary['name'],
198+
schema_id=dictionary.get("schemaNodeId", None),
199+
feature_schema_id=dictionary.get("featureSchemaId", None),
200+
required=dictionary.get("required", False),
201+
tool=cls.Type(dictionary["tool"]),
202+
classifications=[
203+
Classification.from_dict(c)
204+
for c in dictionary["classifications"]
205+
],
206+
color=dictionary["color"])
213207

214208
def asdict(self) -> Dict[str, Any]:
215209
return {
@@ -287,9 +281,9 @@ class OntologyBuilder:
287281
for making Project ontologies from scratch. OntologyBuilder can also
288282
pull from an already existing Project's ontology.
289283
290-
There are no required instantiation arguments.
284+
There are no required instantiation arguments.
291285
292-
To create an ontology, use the asdict() method after fully building your
286+
To create an ontology, use the asdict() method after fully building your
293287
ontology within this class, and inserting it into project.setup() as the
294288
"labeling_frontend_options" parameter.
295289
@@ -303,19 +297,18 @@ class OntologyBuilder:
303297
tools: (list)
304298
classifications: (list)
305299
306-
300+
307301
"""
308302
tools: List[Tool] = field(default_factory=list)
309303
classifications: List[Classification] = field(default_factory=list)
310304

311305
@classmethod
312306
def from_dict(cls, dictionary: Dict[str, Any]):
313-
return OntologyBuilder(
314-
tools=[Tool.from_dict(t) for t in dictionary["tools"]],
315-
classifications=[
316-
Classification.from_dict(c)
317-
for c in dictionary["classifications"]
318-
])
307+
return cls(tools=[Tool.from_dict(t) for t in dictionary["tools"]],
308+
classifications=[
309+
Classification.from_dict(c)
310+
for c in dictionary["classifications"]
311+
])
319312

320313
def asdict(self):
321314
self._update_colors()
@@ -337,7 +330,7 @@ def _update_colors(self):
337330
@classmethod
338331
def from_project(cls, project: Project):
339332
ontology = project.ontology().normalized
340-
return OntologyBuilder.from_dict(ontology)
333+
return cls.from_dict(ontology)
341334

342335
def add_tool(self, tool: Tool):
343336
if tool.name in (t.name for t in self.tools):

mypy.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ ignore_missing_imports = True
66

77
[mypy-google.*]
88
ignore_missing_imports = True
9+
10+
[mypy-labelbox.data.*]
11+
ignore_errors = True

setup.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,18 @@
2222
install_requires=[
2323
"backoff==1.10.0",
2424
"backports-datetime-fromisoformat==1.0.0; python_version < '3.7.0'",
25-
"dataclasses==0.7; python_version < '3.7.0'", "ndjson==0.3.1",
26-
"requests>=2.22.0", "google-api-core>=1.22.1", "pydantic>=1.8,<2.0",
27-
"shapely", "geojson", "numpy", "rasterio", "PILLOW", "opencv-python",
28-
"typeguard", "tqdm"
25+
"dataclasses==0.7; python_version < '3.7.0'",
26+
"ndjson==0.3.1",
27+
"requests>=2.22.0",
28+
"google-api-core>=1.22.1",
29+
"pydantic>=1.8,<2.0",
2930
],
31+
extras_require={
32+
'data': [
33+
"shapely", "geojson", "numpy", "rasterio", "PILLOW",
34+
"opencv-python", "typeguard", "tqdm"
35+
],
36+
},
3037
classifiers=[
3138
'Development Status :: 3 - Alpha',
3239
'License :: OSI Approved :: Apache Software License',

0 commit comments

Comments
 (0)