Skip to content

Commit 0a7c603

Browse files
committed
updating ontology classes to ontology file
1 parent c570276 commit 0a7c603

File tree

3 files changed

+179
-233
lines changed

3 files changed

+179
-233
lines changed

labelbox/schema/ontology.py

Lines changed: 170 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,158 @@
11
import abc
2-
from dataclasses import dataclass
2+
from dataclasses import dataclass, field
3+
from enum import Enum, auto
34

45
from typing import Any, Callable, Dict, List, Optional, Union
56

7+
from labelbox.schema.project import Project
68
from labelbox.orm import query
79
from labelbox.orm.db_object import DbObject, Updateable, BulkDeletable
810
from labelbox.orm.model import Entity, Field, Relationship
911
from labelbox.utils import snake_case, camel_case
10-
11-
12-
@dataclass
13-
class OntologyEntity:
14-
required: bool
15-
name: str
12+
from labelbox.exceptions import InconsistentOntologyException
1613

1714

1815
@dataclass
1916
class Option:
20-
label: str
2117
value: str
18+
schema_id: Optional[str] = None
2219
feature_schema_id: Optional[str] = None
23-
schema_node_id: Optional[str] = None
20+
options: List["Classification"] = field(default_factory=list)
21+
22+
@property
23+
def label(self):
24+
return self.value
2425

2526
@classmethod
26-
def from_json(cls, json_dict):
27-
_dict = convert_keys(json_dict, snake_case)
28-
return cls(**_dict)
27+
def from_dict(cls, dictionary: Dict[str, Any]):
28+
return Option(value=dictionary["value"],
29+
schema_id=dictionary["schemaNodeId"],
30+
feature_schema_id=dictionary["featureSchemaId"],
31+
options=[
32+
Classification.from_dict(o)
33+
for o in dictionary.get("options", [])
34+
])
35+
36+
def asdict(self) -> Dict[str, Any]:
37+
return {
38+
"schemaNodeId": self.schema_id,
39+
"featureSchemaId": self.feature_schema_id,
40+
"label": self.label,
41+
"value": self.value,
42+
"options": [o.asdict() for o in self.options]
43+
}
44+
45+
def add_option(self, option: 'Classification') -> 'Classification':
46+
if option.instructions in (o.instructions for o in self.options):
47+
raise InconsistentOntologyException(
48+
f"Duplicate nested classification '{option.instructions}' "
49+
f"for option '{self.label}'")
50+
self.options.append(option)
2951

3052

3153
@dataclass
32-
class Classification(OntologyEntity):
33-
type: str
54+
class Classification:
55+
class Type(Enum):
56+
TEXT = "text"
57+
CHECKLIST = "checklist"
58+
RADIO = "radio"
59+
DROPDOWN = "dropdown"
60+
61+
_REQUIRES_OPTIONS = {Type.CHECKLIST, Type.RADIO, Type.DROPDOWN}
62+
63+
class_type: Type
3464
instructions: str
35-
options: List[Option]
65+
required: bool = False
66+
options: List[Option] = field(default_factory=list)
67+
schema_id: Optional[str] = None
3668
feature_schema_id: Optional[str] = None
37-
schema_node_id: Optional[str] = None
69+
70+
@property
71+
def name(self):
72+
return self.instructions
3873

3974
@classmethod
40-
def from_json(cls, json_dict):
41-
_dict = convert_keys(json_dict, snake_case)
42-
_dict['options'] = [
43-
Option.from_json(option) for option in _dict['options']
44-
]
45-
return cls(**_dict)
75+
def from_dict(cls, dictionary: Dict[str, Any]):
76+
return Classification(
77+
class_type=Classification.Type(dictionary["type"]),
78+
instructions=dictionary["instructions"],
79+
required=dictionary["required"],
80+
options=[Option.from_dict(o) for o in dictionary["options"]],
81+
schema_id=dictionary["schemaNodeId"],
82+
feature_schema_id=dictionary["schemaNodeId"])
83+
84+
def asdict(self) -> Dict[str, Any]:
85+
if self.class_type in Classification._REQUIRES_OPTIONS \
86+
and len(self.options) < 1:
87+
raise InconsistentOntologyException(
88+
f"Classification '{self.instructions}' requires options.")
89+
return {
90+
"type": self.class_type.value,
91+
"instructions": self.instructions,
92+
"name": self.name,
93+
"required": self.required,
94+
"options": [o.asdict() for o in self.options],
95+
"schemaNodeId": self.schema_id,
96+
"featureSchemaId": self.feature_schema_id
97+
}
98+
99+
def add_option(self, option: Option):
100+
if option.value in (o.value for o in self.options):
101+
raise InconsistentOntologyException(
102+
f"Duplicate option '{option.value}' "
103+
f"for classification '{self.name}'.")
104+
self.options.append(option)
46105

47106

48107
@dataclass
49-
class Tool(OntologyEntity):
50-
tool: str
51-
color: str
52-
classifications: List[Classification]
108+
class Tool:
109+
class Type(Enum):
110+
POLYGON = "polygon"
111+
SEGMENTATION = "superpixel"
112+
POINT = "point"
113+
BBOX = "rectangle"
114+
LINE = "line"
115+
NER = "named-entity"
116+
117+
tool: Type
118+
name: str
119+
required: bool = False
120+
color: str = "#000000"
121+
classifications: List[Classification] = field(default_factory=list)
122+
schema_id: Optional[str] = None
53123
feature_schema_id: Optional[str] = None
54-
schema_node_id: Optional[str] = None
55124

56125
@classmethod
57-
def from_json(cls, json_dict):
58-
_dict = convert_keys(json_dict, snake_case)
59-
_dict['classifications'] = [
60-
Classification.from_json(classification)
61-
for classification in _dict['classifications']
62-
]
63-
return cls(**_dict)
126+
def from_dict(cls, dictionary: Dict[str, Any]):
127+
return Tool(name=dictionary['name'],
128+
schema_id=dictionary["schemaNodeId"],
129+
feature_schema_id=dictionary["featureSchemaId"],
130+
required=dictionary["required"],
131+
tool=Tool.Type(dictionary["tool"]),
132+
classifications=[
133+
Classification.from_dict(c)
134+
for c in dictionary["classifications"]
135+
],
136+
color=dictionary["color"])
137+
138+
def asdict(self) -> Dict[str, Any]:
139+
return {
140+
"tool": self.tool.value,
141+
"name": self.name,
142+
"required": self.required,
143+
"color": self.color,
144+
"classifications": [c.asdict() for c in self.classifications],
145+
"schemaNodeId": self.schema_id,
146+
"featureSchemaId": self.feature_schema_id
147+
}
148+
149+
def add_classification(self, classification: Classification):
150+
if classification.instructions in (c.instructions
151+
for c in self.classifications):
152+
raise InconsistentOntologyException(
153+
f"Duplicate nested classification '{classification.instructions}' "
154+
f"for tool '{self.name}'")
155+
self.classifications.append(classification)
64156

65157

66158
class Ontology(DbObject):
@@ -100,15 +192,15 @@ def tools(self) -> List[Tool]:
100192
"""Get list of tools (AKA objects) in an Ontology."""
101193
if self._tools is None:
102194
self._tools = [
103-
Tool.from_json(tool) for tool in self.normalized['tools']
195+
Tool.from_dict(tool) for tool in self.normalized['tools']
104196
]
105197
return self._tools # type: ignore
106198

107199
def classifications(self) -> List[Classification]:
108200
"""Get list of classifications in an Ontology."""
109201
if self._classifications is None:
110202
self._classifications = [
111-
Classification.from_json(classification)
203+
Classification.from_dict(classification)
112204
for classification in self.normalized['classifications']
113205
]
114206
return self._classifications # type: ignore
@@ -124,3 +216,45 @@ def convert_keys(json_dict: Dict[str, Any],
124216
if isinstance(json_dict, list):
125217
return [convert_keys(ele, converter) for ele in json_dict]
126218
return json_dict
219+
220+
221+
@dataclass
222+
class OntologyBuilder:
223+
224+
tools: List[Tool] = field(default_factory=list)
225+
classifications: List[Classification] = field(default_factory=list)
226+
227+
@classmethod
228+
def from_dict(cls, dictionary: Dict[str, Any]):
229+
return OntologyBuilder(
230+
tools=[Tool.from_dict(t) for t in dictionary["tools"]],
231+
classifications=[
232+
Classification.from_dict(c)
233+
for c in dictionary["classifications"]
234+
])
235+
236+
def asdict(self):
237+
return {
238+
"tools": [t.asdict() for t in self.tools],
239+
"classifications": [c.asdict() for c in self.classifications]
240+
}
241+
242+
@classmethod
243+
def from_project(cls, project: Project):
244+
ontology = project.ontology().normalized
245+
return OntologyBuilder.from_dict(ontology)
246+
247+
def add_tool(self, tool: Tool) -> Tool:
248+
if tool.name in (t.name for t in self.tools):
249+
raise InconsistentOntologyException(
250+
f"Duplicate tool name '{tool.name}'. ")
251+
self.tools.append(tool)
252+
253+
def add_classification(self,
254+
classification: Classification) -> Classification:
255+
if classification.instructions in (c.instructions
256+
for c in self.classifications):
257+
raise InconsistentOntologyException(
258+
f"Duplicate classification instructions '{classification.instructions}'. "
259+
)
260+
self.classifications.append(classification)

0 commit comments

Comments
 (0)