Skip to content

Commit 9cc29ef

Browse files
committed
added the ability to strip schema ids and feature schema ids for when you want to inherit an ontology from a different project. also updated options to return an object class and insert nested_classes to Tool and Option
1 parent 5376a1e commit 9cc29ef

File tree

1 file changed

+53
-51
lines changed

1 file changed

+53
-51
lines changed

labelbox/schema/ontology_generator.py

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
11
'''
22
TODO
3-
2. validate that we can pull in all sorts of project ontology classes
4-
3. do the rest of the stuff below
5-
6-
validate prior to submission, so likely in the o.build() - like have a way to make sure that classifications that need options have options
7-
8-
work on enforcing certain classifications need options (and work on other things other than text)
9-
103
maybe there should be a way to check if a project has an existing ontology, and that it would overwrite it?
114
125
in the future: work on adding NER capability for tool types (?)
13-
14-
EXTRA
15-
#TODO: look into static methods and differences vs. class methods
166
'''
177

188
from dataclasses import dataclass, field
@@ -34,20 +24,19 @@ class Option:
3424
value: str
3525
schema_id: Optional[str] = None
3626
feature_schema_id: Optional[str] = None
37-
#TODO: need to look further into how to make this so that the user cannot input anything here
38-
options: Optional[Classification] = None
27+
nested_classes: List[Classification] = field(default_factory=list)
3928

4029
@property
4130
def label(self):
4231
return self.value
4332

44-
def to_dict(self) -> dict:
33+
def to_dict(self,for_different_project=False) -> dict:
4534
return {
46-
"schemaNodeId": self.schema_id,
47-
"featureSchemaId": self.feature_schema_id,
35+
"schemaNodeId": None if for_different_project else self.schema_id,
36+
"featureSchemaId": None if for_different_project else self.feature_schema_id,
4837
"label": self.label,
4938
"value": self.value,
50-
"options": [classification.to_dict() for classification in self.options]
39+
"options": [classification.to_dict() for classification in self.nested_classes]
5140
}
5241

5342
@classmethod
@@ -61,12 +50,19 @@ def has_nested_classifications(dictionary: dict):
6150
value = dictionary["value"],
6251
schema_id = dictionary["schemaNodeId"],
6352
feature_schema_id = dictionary["featureSchemaId"],
64-
options = has_nested_classifications(dictionary)
53+
nested_classes = has_nested_classifications(dictionary)
6554
)
55+
56+
def add_nested_class(self, *args, **kwargs):
57+
new_classification = Classification(*args, **kwargs)
58+
if new_classification.instructions in (classification.instructions for classification in self.nested_classes):
59+
raise InconsistentOntologyException(f"Duplicate nested classification '{new_classification.instructions}' for option '{self.label}'")
60+
self.nested_classes.append(new_classification)
61+
return new_classification
6662

6763

6864
@dataclass
69-
class Classification:
65+
class Classification:
7066

7167
class Type(Enum):
7268
TEXT = "text"
@@ -81,19 +77,25 @@ class Type(Enum):
8177
schema_id: Optional[str] = None
8278
feature_schema_id: Optional[str] = None
8379

80+
@property
81+
def requires_options(self):
82+
return set((Classification.Type.CHECKLIST, Classification.Type.RADIO, Classification.Type.DROPDOWN))
83+
8484
@property
8585
def name(self):
8686
return self.instructions
8787

88-
def to_dict(self) -> dict:
88+
def to_dict(self, for_different_project=False) -> dict:
89+
if self.class_type in self.requires_options and len(self.options) < 1:
90+
raise InconsistentOntologyException(f"Classification '{self.instructions}' requires options.")
8991
return {
9092
"type": self.class_type.value,
9193
"instructions": self.instructions,
9294
"name": self.name,
9395
"required": self.required,
94-
"options": [option.to_dict() for option in self.options],
95-
"schemaNodeId": self.schema_id,
96-
"featureSchemaId": self.feature_schema_id
96+
"options": [option.to_dict(for_different_project) for option in self.options],
97+
"schemaNodeId": None if for_different_project else self.schema_id,
98+
"featureSchemaId": None if for_different_project else self.feature_schema_id
9799
}
98100

99101
@classmethod
@@ -112,6 +114,7 @@ def add_option(self, *args, **kwargs):
112114
if new_option.value in (option.value for option in self.options):
113115
raise InconsistentOntologyException(f"Duplicate option '{new_option.value}' for classification '{self.name}'.")
114116
self.options.append(new_option)
117+
return new_option
115118

116119
@dataclass
117120
class Tool:
@@ -131,15 +134,15 @@ class Type(Enum):
131134
schema_id: Optional[str] = None
132135
feature_schema_id: Optional[str] = None
133136

134-
def to_dict(self) -> dict:
137+
def to_dict(self,for_different_project=False) -> dict:
135138
return {
136139
"tool": self.tool.value,
137140
"name": self.name,
138141
"required": self.required,
139142
"color": self.color,
140-
"classifications": [classification.to_dict() for classification in self.classifications],
141-
"schemaNodeId": self.schema_id,
142-
"featureSchemaId": self.feature_schema_id
143+
"classifications": [classification.to_dict(for_different_project) for classification in self.classifications],
144+
"schemaNodeId": None if for_different_project else self.schema_id,
145+
"featureSchemaId": None if for_different_project else self.feature_schema_id
143146
}
144147

145148
@classmethod
@@ -154,6 +157,13 @@ def from_dict(cls, dictionary: dict):
154157
color = dictionary["color"]
155158
)
156159

160+
def add_nested_class(self, *args, **kwargs):
161+
new_classification = Classification(*args, **kwargs)
162+
if new_classification.instructions in (classification.instructions for classification in self.classifications):
163+
raise InconsistentOntologyException(f"Duplicate nested classification '{new_classification.instructions}' for option '{self.label}'")
164+
self.classifications.append(new_classification)
165+
return new_classification
166+
157167

158168

159169
@dataclass
@@ -164,6 +174,8 @@ class Ontology:
164174

165175
@classmethod
166176
def from_project(cls, project: Project):
177+
#TODO: consider if this should take in a Project object, or the project.uid.
178+
#if we take in project.uid, we need to then get the project from a client object.
167179
ontology = project.ontology().normalized
168180
return_ontology = Ontology()
169181

@@ -189,15 +201,15 @@ def add_classification(self, *args, **kwargs) -> Classification:
189201
self.classifications.append(new_classification)
190202
return new_classification
191203

192-
def build(self):
204+
def build(self, for_different_project=False):
193205
all_tools = []
194206
all_classifications = []
195207

196208
for tool in self.tools:
197-
all_tools.append(tool.to_dict())
209+
all_tools.append(tool.to_dict(for_different_project))
198210

199-
for classification in self.classifications:
200-
all_classifications.append(classification.to_dict())
211+
for classification in self.classifications:
212+
all_classifications.append(classification.to_dict(for_different_project))
201213

202214
return {"tools": all_tools, "classifications": all_classifications}
203215

@@ -206,8 +218,7 @@ def build(self):
206218
'''
207219
def run():
208220
frontend = list(client.get_labeling_frontends(where=LabelingFrontend.name == "Editor"))[0]
209-
project.setup(frontend, o.build())
210-
return project
221+
project.setup(frontend, o.build(for_different_project=False))
211222

212223
def print_stuff():
213224
tools = o.tools
@@ -221,30 +232,21 @@ def print_stuff():
221232
print("\n",classification)
222233

223234
if __name__ == "__main__":
224-
import json
225235
os.system('clear')
226-
apikey = os.environ['apikey']
227-
client = Client(apikey)
228-
project = client.get_project("ckhchkye62xn30796uui5lu34")
236+
# apikey = os.environ['apikey']
237+
# client = Client(apikey)
229238

230-
o = Ontology().from_project(project)
231-
# print_stuff()
232-
239+
# print("START\n")
240+
# project = client.get_project("ckhchkye62xn30796uui5lu34")
241+
# o = Ontology().from_project(project)
233242

234-
# o.add_tool(tool=Tool.Type.POLYGON, name="I AM HERE FOR TESTING YET AGAIN!!")
235-
# checklist = o.add_classification(class_type=Classification.Type.CHECKLIST, instructions="I AM A CHECKLIST2")
236-
# checklist.add_option(value="checklist answer 1")
237-
# checklist.add_option(value="checklist answer 2")
238-
o.add_classification(class_type=Classification.Type.TEXT, instructions="I AM TEXT INFO MAN 2")
243+
# tool = o.add_tool(tool = Tool.Type.POINT, name = "first tool")
244+
# nested_class = tool.add_nested_class(class_type = Classification.Type.DROPDOWN, instructions = "nested class")
245+
# dropdown_option = nested_class.add_option(value="answer")
239246

240247
# print_stuff()
241-
# print("\n\n\n\n\n")
242-
# print(o.build())
243-
# print(type(o.build()))
244-
# print(o.build())
245-
run()
246-
247-
248+
# o.build()
249+
# run()
248250

249251

250252

0 commit comments

Comments
 (0)