import uuid
from typing import Dict, Iterable, List, Optional
from encord.project_ontology.classification_attribute import ClassificationAttribute
from encord.project_ontology.classification_option import ClassificationOption
from encord.project_ontology.classification_type import ClassificationType
from encord.project_ontology.object_type import ObjectShape
from encord.project_ontology.ontology_classification import OntologyClassification
from encord.project_ontology.ontology_object import OntologyObject
# removal may require calling label/delete/object and label/delete/attribute
[docs]def generate_feature_node_hash() -> str:
"""
Utility function to generate an 8-character hex string.
"""
return str(uuid.uuid4())[0:8]
[docs]class Ontology:
"""
BETA: prefer using :class:`encord.objects.ontology_structure.OntologyStructure`
"""
COLORS = (
"#D33115",
"#E27300",
"#16406C",
"#FE9200",
"#FCDC00",
"#DBDF00",
"#A4DD00",
"#68CCCA",
"#73D8FF",
"#AEA1FF",
"#FCC400",
"#B0BC00",
"#68BC00",
"#16A5A5",
"#009CE0",
"#7B64FF",
"#FA28FF",
"#B3B3B3",
"#9F0500",
"#C45100",
"#FB9E00",
"#808900",
"#194D33",
"#0C797D",
"#0062B1",
"#653294",
"#AB149E",
)
def __init__(self):
self.ontology_objects: List[OntologyObject] = []
self.ontology_classifications: List[OntologyClassification] = []
self.color_index = 0
def __str__(self):
return str(self.to_dict())
def __current_object_id(self):
return len(self.ontology_objects) + 1
def __current_classification_id(self):
return len(self.ontology_classifications) + 1
[docs] @classmethod
def from_dict(cls, ontology_dict: Dict):
"""
Convert python dictionary too an :class:`.Ontology` object.
Args:
ontology_dict: The dictionary to convert.
"""
ontology = Ontology()
ontology.ontology_objects = cls.__build_ontology_objects(ontology_dict["objects"])
ontology.ontology_classifications = cls.__build_ontology_classifications(ontology_dict["classifications"])
ontology.color_index = len(ontology.ontology_objects) % len(cls.COLORS)
return ontology
@classmethod
def __build_ontology_objects(cls, ontology_objects_dict: List[Dict]):
ontology_objects = []
for ontology_object_dict in ontology_objects_dict:
ontology_object = OntologyObject(
ontology_object_dict["id"],
ontology_object_dict["color"],
ontology_object_dict["name"],
ObjectShape(ontology_object_dict["shape"]),
ontology_object_dict["featureNodeHash"],
)
ontology_objects.append(ontology_object)
return ontology_objects
@classmethod
def __build_ontology_classifications(cls, ontology_classifications_dict: List[Dict]):
ontology_classifications = []
for classification_object_dict in ontology_classifications_dict:
classification_attributes = []
for attribute in classification_object_dict["attributes"]:
classification_attribute = ClassificationAttribute(
attribute["id"],
attribute["name"],
ClassificationType(attribute["type"]),
attribute["required"],
attribute["featureNodeHash"],
)
options_dict = attribute.get("options")
if options_dict is not None:
options = cls.__build_classification_options(attribute["options"])
classification_attribute.options = options
classification_attributes.append(classification_attribute)
ontology_classification = OntologyClassification(
classification_object_dict["id"],
classification_object_dict["featureNodeHash"],
classification_attributes,
)
ontology_classifications.append(ontology_classification)
return ontology_classifications
@classmethod
def __build_classification_options(cls, options_dict: List[Dict]):
options = []
for option_dict in options_dict:
option = ClassificationOption(
option_dict["id"], option_dict["label"], option_dict["value"], option_dict["featureNodeHash"]
)
options.append(option)
return options
[docs] def add_object(self, name: str, shape: ObjectShape) -> None:
"""
Add an :class:`.OntologyObject` to the ontology.
Args:
name: A descriptive name of the object.
shape: The shape of the object.
"""
ontology_object = OntologyObject(
str(self.__current_object_id()), self.COLORS[self.color_index], name, shape, generate_feature_node_hash()
)
self.ontology_objects.append(ontology_object)
self.color_index = (self.color_index + 1) % len(self.COLORS)
[docs] def add_classification(
self,
name: str,
classification_type: ClassificationType,
required: bool,
options: Optional[Iterable[str]] = None,
) -> None:
"""
Add a classification to the ontology.
Args:
name: A descriptive name of the classification.
classification_type: The type of the classification.
required: Indicate whether annotating this classification is required.
options: Nested classification options.
"""
ontology_classification = OntologyClassification(
str(self.__current_classification_id()),
generate_feature_node_hash(),
[self.__create_classification_attributes(name, classification_type, required, options)],
)
self.ontology_classifications.append(ontology_classification)
def __create_classification_attributes(
self, name: str, classification_type: ClassificationType, required: bool, options: Optional[Iterable[str]]
) -> ClassificationAttribute:
attribute_id = 1
classification_attribute = ClassificationAttribute(
f"{self.__current_classification_id()}.{attribute_id}",
name,
classification_type,
required,
generate_feature_node_hash(),
)
if options:
classification_options = []
for option_id, option in enumerate(options, 1):
classification_option = ClassificationOption(
f"{self.__current_classification_id()}.{attribute_id}.{option_id}",
option,
self.__format_option_value(option),
generate_feature_node_hash(),
)
classification_options.append(classification_option)
classification_attribute.options = classification_options
return classification_attribute
[docs] def to_dict(self) -> dict:
"""
Convert the ontology object to a python dictionary.
"""
objects = []
classifications = []
for ontology_object in self.ontology_objects:
objects.append(self.ontology_object_to_dict(ontology_object))
for ontology_classification in self.ontology_classifications:
classifications.append(self.ontology_classification_to_dict(ontology_classification))
return {"objects": objects, "classifications": classifications}
[docs] def ontology_object_to_dict(self, ontology_object: OntologyObject) -> Dict:
return {
"id": ontology_object.id,
"color": ontology_object.color,
"name": ontology_object.name,
"shape": ontology_object.shape.value,
"featureNodeHash": ontology_object.feature_node_hash,
}
[docs] def ontology_classification_to_dict(self, ontology_classification: OntologyClassification) -> Dict:
classification = {
"id": ontology_classification.id,
"featureNodeHash": ontology_classification.feature_node_hash,
}
attributes = []
for classification_attribute in ontology_classification.attributes:
attribute = {
"id": classification_attribute.id,
"name": classification_attribute.name,
"type": classification_attribute.classification_type.value,
"required": classification_attribute.required,
"featureNodeHash": classification_attribute.feature_node_hash,
}
if classification_attribute.options:
options = []
for classification_option in classification_attribute.options:
option = {
"id": classification_option.id,
"label": classification_option.label,
"value": classification_option.value,
"featureNodeHash": classification_option.feature_node_hash,
}
options.append(option)
attribute["options"] = options
attributes.append(attribute)
classification["attributes"] = attributes
return classification
def __format_option_value(self, label: str):
return label.replace(" ", "_")