From 9131d21aaf02f4aa7d2e7c4700db19c920ed5c15 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Mon, 16 Dec 2024 13:01:26 -0700 Subject: [PATCH 01/18] [PTDT-2863]: Feature schema attributes --- feature-schema-attributes-testing.py | 106 ++++++++++++++++++ libs/labelbox/src/labelbox/client.py | 5 + libs/labelbox/src/labelbox/schema/ontology.py | 8 +- .../schema/tool_building/classification.py | 11 +- .../labelbox/schema/tool_building/types.py | 12 +- .../tests/integration/test_ontology.py | 1 + requirements-dev.lock | 5 +- requirements.lock | 3 +- 8 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 feature-schema-attributes-testing.py diff --git a/feature-schema-attributes-testing.py b/feature-schema-attributes-testing.py new file mode 100644 index 000000000..9d623f44a --- /dev/null +++ b/feature-schema-attributes-testing.py @@ -0,0 +1,106 @@ +import os +from libs.labelbox.src.labelbox import Client +from libs.labelbox.src.labelbox.schema.ontology import OntologyBuilder, Tool +from libs.labelbox.src.labelbox.schema.tool_building.classification import Classification, Option +from libs.labelbox.src.labelbox.schema.tool_building.types import FeatureSchemaAttribute +from libs.labelbox.src.labelbox.schema.media_type import MediaType +from libs.labelbox.src.labelbox.schema.ontology_kind import OntologyKind +import json + + +# client = Client( +# api_key=os.environ.get('STAGE_API_KEY'), +# endpoint="https://app.lb-stage.xyz/api/_gql/graphql", +# rest_endpoint="https://app.lb-stage.xyz/api/api/v1") + +client = Client( + api_key=os.environ.get('LOCALHOST_API_KEY'), + endpoint="http://localhost:8080/graphql", + rest_endpoint="http://localhost:3000/api/api/v1") + +builder = OntologyBuilder( + + tools=[ + Tool( + name="Auto OCR", + tool=Tool.Type.BBOX, + attributes=[ + FeatureSchemaAttribute( + attributeName="auto-ocr", + attributeValue="true" + ) + ], + classifications=[ + Classification( + name="Auto ocr text class value", + instructions="This is an auto OCR text value classification", + class_type=Classification.Type.TEXT, + scope=Classification.Scope.GLOBAL, + attributes=[ + FeatureSchemaAttribute( + attributeName="auto-ocr-text-value", + attributeValue="true" + ) + ] + ) + ] + ) + ] +) + +# client.create_ontology("Auto OCR ontology", builder.asdict(), media_type=MediaType.Document) + +builder = OntologyBuilder( + classifications=[ + Classification( + name="prompt message scope text classification", + instructions="This is a prompt message scoped text classification", + class_type=Classification.Type.TEXT, + scope=Classification.Scope.INDEX, + attributes=[ + FeatureSchemaAttribute( + attributeName="prompt-message-scope", + attributeValue="true" + ) + ] + ) + ] +) + +# client.create_ontology('MMC Ontology with prompt message scope class', builder.asdict(), media_type=MediaType.Conversational, ontology_kind=OntologyKind.ModelEvaluation) + +builder = OntologyBuilder( + classifications=[ + Classification( + name="Requires connection checklist classification", + instructions="This is a requires connection checklist classification", + class_type=Classification.Type.CHECKLIST, + scope=Classification.Scope.GLOBAL, + attributes=[ + FeatureSchemaAttribute( + attributeName="required-connection", + attributeValue="true" + ) + ], + options=[ + Option( + value='First option' + ), + Option( + value='Second option' + ) + ] + ) + ] +); + +# client.create_ontology('Image ontology with requires connection classes', builder.asdict(), media_type=MediaType.Image) + + +feature_schema = client.upsert_feature_schema(Tool(name='Auto OCR from upsert feature schema', tool=Tool.Type.BBOX, attributes=[FeatureSchemaAttribute(attributeName='auto-ocr', attributeValue='true')]).asdict()) +fetched_feature_schema = client.get_feature_schema(feature_schema.uid) +feature_schemas_with_name = client.get_feature_schemas('Auto OCR') + +# Iterate over the feature schemas +for schema in feature_schemas_with_name: + print(schema) diff --git a/libs/labelbox/src/labelbox/client.py b/libs/labelbox/src/labelbox/client.py index 6269f4927..256aab3a6 100644 --- a/libs/labelbox/src/labelbox/client.py +++ b/libs/labelbox/src/labelbox/client.py @@ -1073,6 +1073,7 @@ def rootSchemaPayloadToFeatureSchema(client, payload): ["rootSchemaNodes", "nextCursor"], ) +# TODO tkerr: def create_ontology_from_feature_schemas( self, name, @@ -1242,6 +1243,9 @@ def upsert_feature_schema(self, feature_schema: Dict) -> FeatureSchema: endpoint, json={"normalized": json.dumps(feature_schema)} ) + print('json.dumps(feature_schema)') + print(json.dumps(feature_schema)) + if response.status_code == requests.codes.ok: return self.get_feature_schema(response.json()["schemaId"]) else: @@ -1328,6 +1332,7 @@ def get_unused_feature_schemas(self, after: str = None) -> List[str]: + str(response.json()["message"]) ) +# TODO tkerr: def create_ontology( self, name, diff --git a/libs/labelbox/src/labelbox/schema/ontology.py b/libs/labelbox/src/labelbox/schema/ontology.py index 0032aaad1..4d3e04628 100644 --- a/libs/labelbox/src/labelbox/schema/ontology.py +++ b/libs/labelbox/src/labelbox/schema/ontology.py @@ -25,6 +25,8 @@ from labelbox.schema.tool_building.tool_type_mapping import ( map_tool_type_to_tool_cls, ) +from labelbox.schema.tool_building.types import FeatureSchemaAttributes + class DeleteFeatureFromOntologyResult: @@ -43,7 +45,6 @@ class FeatureSchema(DbObject): color = Field.String("name") normalized = Field.Json("normalized") - @dataclass class Tool: """ @@ -73,6 +74,7 @@ class Tool: classifications: (list) schema_id: (str) feature_schema_id: (str) + attributes: (list) """ class Type(Enum): @@ -95,7 +97,7 @@ class Type(Enum): classifications: List[Classification] = field(default_factory=list) schema_id: Optional[str] = None feature_schema_id: Optional[str] = None - + attributes: Optional[FeatureSchemaAttributes] = None @classmethod def from_dict(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]: return cls( @@ -109,6 +111,7 @@ def from_dict(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]: for c in dictionary["classifications"] ], color=dictionary["color"], + attributes=dictionary.get("attributes", None), ) def asdict(self) -> Dict[str, Any]: @@ -122,6 +125,7 @@ def asdict(self) -> Dict[str, Any]: ], "schemaNodeId": self.schema_id, "featureSchemaId": self.feature_schema_id, + "attributes": self.attributes, } def add_classification(self, classification: Classification) -> None: diff --git a/libs/labelbox/src/labelbox/schema/tool_building/classification.py b/libs/labelbox/src/labelbox/schema/tool_building/classification.py index 9c0c69bea..54b2dffe1 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/classification.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/classification.py @@ -5,9 +5,10 @@ from lbox.exceptions import InconsistentOntologyException -from labelbox.schema.tool_building.types import FeatureSchemaId +from labelbox.schema.tool_building.types import FeatureSchemaId, FeatureSchemaAttributes +# TODO tkerr: Update all these tools & classifications to use attributes @dataclass class Classification: """ @@ -42,6 +43,7 @@ class Classification: schema_id: (str) feature_schema_id: (str) scope: (str) + attributes: (list) """ class Type(Enum): @@ -70,6 +72,7 @@ class UIMode(Enum): ui_mode: Optional[UIMode] = ( None # How this classification should be answered (e.g. hotkeys / autocomplete, etc) ) + attributes: Optional[FeatureSchemaAttributes] = None def __post_init__(self): if self.name is None: @@ -88,9 +91,13 @@ def __post_init__(self): else: if self.instructions is None: self.instructions = self.name + if self.attributes is not None: + warnings.warn('Attributes are an experimental feature and may change in the future.') @classmethod def from_dict(cls, dictionary: Dict[str, Any]) -> "Classification": + print('attributes:') + print(dictionary.get("attributes", None)) return cls( class_type=Classification.Type(dictionary["type"]), name=dictionary["name"], @@ -103,6 +110,7 @@ def from_dict(cls, dictionary: Dict[str, Any]) -> "Classification": schema_id=dictionary.get("schemaNodeId", None), feature_schema_id=dictionary.get("featureSchemaId", None), scope=cls.Scope(dictionary.get("scope", cls.Scope.GLOBAL)), + attributes=FeatureSchemaAttributes(dictionary.get("attributes", None)), ) def asdict(self, is_subclass: bool = False) -> Dict[str, Any]: @@ -118,6 +126,7 @@ def asdict(self, is_subclass: bool = False) -> Dict[str, Any]: "options": [o.asdict() for o in self.options], "schemaNodeId": self.schema_id, "featureSchemaId": self.feature_schema_id, + "attributes": self.attributes if self.attributes is not None else None, } if ( self.class_type == self.Type.RADIO diff --git a/libs/labelbox/src/labelbox/schema/tool_building/types.py b/libs/labelbox/src/labelbox/schema/tool_building/types.py index 0d6e34717..fe537861e 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/types.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/types.py @@ -1,6 +1,14 @@ -from typing import Annotated +from typing import Annotated, List +from pydantic import Field, BaseModel +from typing import TypedDict -from pydantic import Field + +class FeatureSchemaAttribute(TypedDict): + attributeName: str + attributeValue: str + +FeatureSchemaAttriubte = Annotated[FeatureSchemaAttribute, Field()] FeatureSchemaId = Annotated[str, Field(min_length=25, max_length=25)] SchemaId = Annotated[str, Field(min_length=25, max_length=25)] +FeatureSchemaAttributes = Annotated[List[FeatureSchemaAttribute], Field(default_factory=list)] diff --git a/libs/labelbox/tests/integration/test_ontology.py b/libs/labelbox/tests/integration/test_ontology.py index d56f54958..fffd80c45 100644 --- a/libs/labelbox/tests/integration/test_ontology.py +++ b/libs/labelbox/tests/integration/test_ontology.py @@ -225,6 +225,7 @@ def feature_schema_cat_normalized(name_for_read): "name": name_for_read, "color": "black", "classifications": [], + "attributes": [] # TODO tkerr: Finish } diff --git a/requirements-dev.lock b/requirements-dev.lock index 7616ff075..9fb3157cb 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -6,6 +6,7 @@ # features: [] # all-features: true # with-sources: false +# generate-hashes: false -e file:libs/labelbox -e file:libs/lbox-clients @@ -133,7 +134,7 @@ nbconvert==7.16.4 nbformat==5.10.4 # via nbclient # via nbconvert -numpy==1.24.4 +numpy==2.0.2 # via labelbox # via opencv-python-headless # via pandas @@ -147,7 +148,7 @@ packaging==24.1 # via pytest-cases # via pytest-rerunfailures # via sphinx -pandas==2.0.3 +pandas==2.2.3 pandocfilters==1.5.1 # via nbconvert parso==0.8.4 diff --git a/requirements.lock b/requirements.lock index 36871f22b..5fcff65e4 100644 --- a/requirements.lock +++ b/requirements.lock @@ -6,6 +6,7 @@ # features: [] # all-features: true # with-sources: false +# generate-hashes: false -e file:libs/labelbox -e file:libs/lbox-clients @@ -54,7 +55,7 @@ mypy==1.10.1 # via labelbox mypy-extensions==1.0.0 # via mypy -numpy==1.24.4 +numpy==2.0.2 # via labelbox # via opencv-python-headless # via shapely From a58ce6c33ca8dc90ad0435cdaf385434efaa609d Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Mon, 16 Dec 2024 14:03:10 -0700 Subject: [PATCH 02/18] Remove todos --- feature-schema-attributes-testing.py | 37 +++++++++++++++++----------- libs/labelbox/src/labelbox/client.py | 2 -- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/feature-schema-attributes-testing.py b/feature-schema-attributes-testing.py index 9d623f44a..cb9d8085d 100644 --- a/feature-schema-attributes-testing.py +++ b/feature-schema-attributes-testing.py @@ -8,15 +8,15 @@ import json -# client = Client( -# api_key=os.environ.get('STAGE_API_KEY'), -# endpoint="https://app.lb-stage.xyz/api/_gql/graphql", -# rest_endpoint="https://app.lb-stage.xyz/api/api/v1") - client = Client( - api_key=os.environ.get('LOCALHOST_API_KEY'), - endpoint="http://localhost:8080/graphql", - rest_endpoint="http://localhost:3000/api/api/v1") + api_key=os.environ.get('STAGE_API_KEY'), + endpoint="https://app.lb-stage.xyz/api/_gql/graphql", + rest_endpoint="https://app.lb-stage.xyz/api/api/v1") + +# client = Client( +# api_key=os.environ.get('LOCALHOST_API_KEY'), +# endpoint="http://localhost:8080/graphql", +# rest_endpoint="http://localhost:3000/api/api/v1") builder = OntologyBuilder( @@ -97,10 +97,19 @@ # client.create_ontology('Image ontology with requires connection classes', builder.asdict(), media_type=MediaType.Image) -feature_schema = client.upsert_feature_schema(Tool(name='Auto OCR from upsert feature schema', tool=Tool.Type.BBOX, attributes=[FeatureSchemaAttribute(attributeName='auto-ocr', attributeValue='true')]).asdict()) -fetched_feature_schema = client.get_feature_schema(feature_schema.uid) -feature_schemas_with_name = client.get_feature_schemas('Auto OCR') +# feature_schema = client.upsert_feature_schema(Tool(name='Testing', tool=Tool.Type.BBOX, attributes=[FeatureSchemaAttribute(attributeName='auto-ocr', attributeValue='true')]).asdict()) +# print(feature_schema) +# fetched_feature_schema = client.get_feature_schema(feature_schema.uid) +# feature_schemas_with_name = client.get_feature_schemas('Auto OCR') + +# # Iterate over the feature schemas +# for schema in feature_schemas_with_name: +# print(schema) + +# ontology = client.create_ontology_from_feature_schemas('Ontology from feature schemas', ['cm4rc1nl90h36070782v9hlpt']) + +# feature_schema = client.update_feature_schema_title('cm4rc1nl90h36070782v9hlpt', 'This is a new title - did it remove the feature schema attributes? UPDATED') +# client.delete_unused_feature_schema('cm4rhzhn7026e07wm2az681di') -# Iterate over the feature schemas -for schema in feature_schemas_with_name: - print(schema) +feature_schema = client.create_feature_schema(normalized={'tool': 'rectangle', 'name': 'cat', 'color': 'black', 'attributes': [{'attributeName': 'auto-ocr', 'attributeValue': 'true'}]}) +print(feature_schema) \ No newline at end of file diff --git a/libs/labelbox/src/labelbox/client.py b/libs/labelbox/src/labelbox/client.py index 256aab3a6..d8bcb20f5 100644 --- a/libs/labelbox/src/labelbox/client.py +++ b/libs/labelbox/src/labelbox/client.py @@ -1073,7 +1073,6 @@ def rootSchemaPayloadToFeatureSchema(client, payload): ["rootSchemaNodes", "nextCursor"], ) -# TODO tkerr: def create_ontology_from_feature_schemas( self, name, @@ -1332,7 +1331,6 @@ def get_unused_feature_schemas(self, after: str = None) -> List[str]: + str(response.json()["message"]) ) -# TODO tkerr: def create_ontology( self, name, From 81fb2f4ed079cba8a51e8cb6c6976d7b8be32ed5 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Mon, 16 Dec 2024 14:04:43 -0700 Subject: [PATCH 03/18] cleanup --- libs/labelbox/src/labelbox/client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/libs/labelbox/src/labelbox/client.py b/libs/labelbox/src/labelbox/client.py index d8bcb20f5..6269f4927 100644 --- a/libs/labelbox/src/labelbox/client.py +++ b/libs/labelbox/src/labelbox/client.py @@ -1242,9 +1242,6 @@ def upsert_feature_schema(self, feature_schema: Dict) -> FeatureSchema: endpoint, json={"normalized": json.dumps(feature_schema)} ) - print('json.dumps(feature_schema)') - print(json.dumps(feature_schema)) - if response.status_code == requests.codes.ok: return self.get_feature_schema(response.json()["schemaId"]) else: From 0e0680609cf785d5bc2a2a7e8ee504c2d918b9f6 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Mon, 16 Dec 2024 14:05:54 -0700 Subject: [PATCH 04/18] cleanup --- .../labelbox/src/labelbox/schema/tool_building/classification.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/labelbox/src/labelbox/schema/tool_building/classification.py b/libs/labelbox/src/labelbox/schema/tool_building/classification.py index 54b2dffe1..5565bde29 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/classification.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/classification.py @@ -8,7 +8,6 @@ from labelbox.schema.tool_building.types import FeatureSchemaId, FeatureSchemaAttributes -# TODO tkerr: Update all these tools & classifications to use attributes @dataclass class Classification: """ From eb16f048b030a6d9be4162ca7ce94635f721365f Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Mon, 16 Dec 2024 15:28:34 -0700 Subject: [PATCH 05/18] Fixed unit tests --- .../labelbox/schema/tool_building/classification.py | 4 +--- libs/labelbox/tests/integration/test_ontology.py | 1 - libs/labelbox/tests/unit/test_unit_ontology.py | 10 ++++++++++ .../labelbox/tests/unit/test_unit_prompt_issue_tool.py | 1 + 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/tool_building/classification.py b/libs/labelbox/src/labelbox/schema/tool_building/classification.py index 5565bde29..7f2efc155 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/classification.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/classification.py @@ -95,8 +95,6 @@ def __post_init__(self): @classmethod def from_dict(cls, dictionary: Dict[str, Any]) -> "Classification": - print('attributes:') - print(dictionary.get("attributes", None)) return cls( class_type=Classification.Type(dictionary["type"]), name=dictionary["name"], @@ -109,7 +107,7 @@ def from_dict(cls, dictionary: Dict[str, Any]) -> "Classification": schema_id=dictionary.get("schemaNodeId", None), feature_schema_id=dictionary.get("featureSchemaId", None), scope=cls.Scope(dictionary.get("scope", cls.Scope.GLOBAL)), - attributes=FeatureSchemaAttributes(dictionary.get("attributes", None)), + attributes=dictionary.get("attributes", None), ) def asdict(self, is_subclass: bool = False) -> Dict[str, Any]: diff --git a/libs/labelbox/tests/integration/test_ontology.py b/libs/labelbox/tests/integration/test_ontology.py index fffd80c45..d56f54958 100644 --- a/libs/labelbox/tests/integration/test_ontology.py +++ b/libs/labelbox/tests/integration/test_ontology.py @@ -225,7 +225,6 @@ def feature_schema_cat_normalized(name_for_read): "name": name_for_read, "color": "black", "classifications": [], - "attributes": [] # TODO tkerr: Finish } diff --git a/libs/labelbox/tests/unit/test_unit_ontology.py b/libs/labelbox/tests/unit/test_unit_ontology.py index 61c9f523a..7afc55e02 100644 --- a/libs/labelbox/tests/unit/test_unit_ontology.py +++ b/libs/labelbox/tests/unit/test_unit_ontology.py @@ -15,6 +15,7 @@ "color": "#FF0000", "tool": "polygon", "classifications": [], + "attributes": None }, { "schemaNodeId": None, @@ -24,6 +25,7 @@ "color": "#FF0000", "tool": "superpixel", "classifications": [], + "attributes": None }, { "schemaNodeId": None, @@ -32,6 +34,7 @@ "name": "bbox", "color": "#FF0000", "tool": "rectangle", + "attributes": None, "classifications": [ { "schemaNodeId": None, @@ -56,6 +59,7 @@ "name": "nested nested text", "type": "text", "options": [], + "attributes": None } ], }, @@ -67,6 +71,7 @@ "options": [], }, ], + "attributes": None }, { "schemaNodeId": None, @@ -76,6 +81,7 @@ "name": "nested text", "type": "text", "options": [], + "attributes": None }, ], }, @@ -87,6 +93,7 @@ "color": "#FF0000", "tool": "point", "classifications": [], + "attributes": None }, { "schemaNodeId": None, @@ -96,6 +103,7 @@ "color": "#FF0000", "tool": "line", "classifications": [], + "attributes": None }, { "schemaNodeId": None, @@ -105,6 +113,7 @@ "color": "#FF0000", "tool": "named-entity", "classifications": [], + "attributes": None }, ], "classifications": [ @@ -117,6 +126,7 @@ "type": "radio", "scope": "global", "uiMode": "searchable", + "attributes": None, "options": [ { "schemaNodeId": None, diff --git a/libs/labelbox/tests/unit/test_unit_prompt_issue_tool.py b/libs/labelbox/tests/unit/test_unit_prompt_issue_tool.py index 5a18d5248..8ac73d93c 100644 --- a/libs/labelbox/tests/unit/test_unit_prompt_issue_tool.py +++ b/libs/labelbox/tests/unit/test_unit_prompt_issue_tool.py @@ -47,6 +47,7 @@ def test_as_dict(): "schemaNodeId": None, "featureSchemaId": None, "scope": "global", + "attributes": None } ], "color": None, From 6c3fc0e0237a6030c6e1b957a1e24b31e698d05b Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 08:31:25 -0700 Subject: [PATCH 06/18] Revert changes to lock files --- feature-schema-attributes-testing.py | 64 +++++++++++++++++++++++----- requirements-dev.lock | 5 +-- requirements.lock | 3 +- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/feature-schema-attributes-testing.py b/feature-schema-attributes-testing.py index cb9d8085d..def9fca07 100644 --- a/feature-schema-attributes-testing.py +++ b/feature-schema-attributes-testing.py @@ -1,11 +1,10 @@ import os -from libs.labelbox.src.labelbox import Client -from libs.labelbox.src.labelbox.schema.ontology import OntologyBuilder, Tool -from libs.labelbox.src.labelbox.schema.tool_building.classification import Classification, Option -from libs.labelbox.src.labelbox.schema.tool_building.types import FeatureSchemaAttribute -from libs.labelbox.src.labelbox.schema.media_type import MediaType -from libs.labelbox.src.labelbox.schema.ontology_kind import OntologyKind -import json +from labelbox import Client +from labelbox.schema.ontology import OntologyBuilder, Tool, PromptIssueTool +from labelbox.schema.tool_building.classification import Classification, Option +from labelbox.schema.tool_building.types import FeatureSchemaAttribute +from labelbox.schema.media_type import MediaType +from labelbox.schema.ontology_kind import OntologyKind client = Client( @@ -48,7 +47,7 @@ ] ) -# client.create_ontology("Auto OCR ontology", builder.asdict(), media_type=MediaType.Document) +# client.create_ontology("Auto OCR ontology from sdk", builder.asdict(), media_type=MediaType.Document) builder = OntologyBuilder( classifications=[ @@ -78,7 +77,7 @@ scope=Classification.Scope.GLOBAL, attributes=[ FeatureSchemaAttribute( - attributeName="required-connection", + attributeName="requires-connection", attributeValue="true" ) ], @@ -111,5 +110,48 @@ # feature_schema = client.update_feature_schema_title('cm4rc1nl90h36070782v9hlpt', 'This is a new title - did it remove the feature schema attributes? UPDATED') # client.delete_unused_feature_schema('cm4rhzhn7026e07wm2az681di') -feature_schema = client.create_feature_schema(normalized={'tool': 'rectangle', 'name': 'cat', 'color': 'black', 'attributes': [{'attributeName': 'auto-ocr', 'attributeValue': 'true'}]}) -print(feature_schema) \ No newline at end of file +# feature_schema = client.create_feature_schema(normalized={'tool': 'rectangle', 'name': 'cat', 'color': 'black', 'attributes': [{'attributeName': 'auto-ocr', 'attributeValue': 'true'}]}) +# print(feature_schema) + +# classification = Classification.from_dict({ +# "name": "Test Classification", +# "instructions": "Test instructions", +# "type": "text", # or "checklist" or other valid Classification.Type values +# "scope": "index", # or "index" for Classification.Scope values +# "required": False, # optional +# "attributes": [ # optional +# { +# "attributeName": "prompt-message-scope", +# "attributeValue": "true" +# } +# ], +# "options": [] +# }) + +# tool = Tool.from_dict({ +# "name": "Test Tool", +# "type": "rectangle", # or "checklist" or other valid Classification.Type values +# "required": False, # optional +# "attributes": [ # optional +# { +# "attributeName": "auto-ocr", +# "attributeValue": "true" +# } +# ], +# "options": [] +# }) + +tool = PromptIssueTool.from_dict({ + "name": "Test Tool", + "type": "rectangle", # or "checklist" or other valid Classification.Type values + "required": False, # optional + "attributes": [ # optional + { + "attributeName": "auto-ocr", + "attributeValue": "true" + } + ], + "options": [], +}) + +# print(tool) \ No newline at end of file diff --git a/requirements-dev.lock b/requirements-dev.lock index 9fb3157cb..7616ff075 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -6,7 +6,6 @@ # features: [] # all-features: true # with-sources: false -# generate-hashes: false -e file:libs/labelbox -e file:libs/lbox-clients @@ -134,7 +133,7 @@ nbconvert==7.16.4 nbformat==5.10.4 # via nbclient # via nbconvert -numpy==2.0.2 +numpy==1.24.4 # via labelbox # via opencv-python-headless # via pandas @@ -148,7 +147,7 @@ packaging==24.1 # via pytest-cases # via pytest-rerunfailures # via sphinx -pandas==2.2.3 +pandas==2.0.3 pandocfilters==1.5.1 # via nbconvert parso==0.8.4 diff --git a/requirements.lock b/requirements.lock index 5fcff65e4..36871f22b 100644 --- a/requirements.lock +++ b/requirements.lock @@ -6,7 +6,6 @@ # features: [] # all-features: true # with-sources: false -# generate-hashes: false -e file:libs/labelbox -e file:libs/lbox-clients @@ -55,7 +54,7 @@ mypy==1.10.1 # via labelbox mypy-extensions==1.0.0 # via mypy -numpy==2.0.2 +numpy==1.24.4 # via labelbox # via opencv-python-headless # via shapely From 488a02b78fcebe4d695fb77e1218d4fbcdeca1a8 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 08:43:33 -0700 Subject: [PATCH 07/18] removed python testing script file --- feature-schema-attributes-testing.py | 157 --------------------------- 1 file changed, 157 deletions(-) delete mode 100644 feature-schema-attributes-testing.py diff --git a/feature-schema-attributes-testing.py b/feature-schema-attributes-testing.py deleted file mode 100644 index def9fca07..000000000 --- a/feature-schema-attributes-testing.py +++ /dev/null @@ -1,157 +0,0 @@ -import os -from labelbox import Client -from labelbox.schema.ontology import OntologyBuilder, Tool, PromptIssueTool -from labelbox.schema.tool_building.classification import Classification, Option -from labelbox.schema.tool_building.types import FeatureSchemaAttribute -from labelbox.schema.media_type import MediaType -from labelbox.schema.ontology_kind import OntologyKind - - -client = Client( - api_key=os.environ.get('STAGE_API_KEY'), - endpoint="https://app.lb-stage.xyz/api/_gql/graphql", - rest_endpoint="https://app.lb-stage.xyz/api/api/v1") - -# client = Client( -# api_key=os.environ.get('LOCALHOST_API_KEY'), -# endpoint="http://localhost:8080/graphql", -# rest_endpoint="http://localhost:3000/api/api/v1") - -builder = OntologyBuilder( - - tools=[ - Tool( - name="Auto OCR", - tool=Tool.Type.BBOX, - attributes=[ - FeatureSchemaAttribute( - attributeName="auto-ocr", - attributeValue="true" - ) - ], - classifications=[ - Classification( - name="Auto ocr text class value", - instructions="This is an auto OCR text value classification", - class_type=Classification.Type.TEXT, - scope=Classification.Scope.GLOBAL, - attributes=[ - FeatureSchemaAttribute( - attributeName="auto-ocr-text-value", - attributeValue="true" - ) - ] - ) - ] - ) - ] -) - -# client.create_ontology("Auto OCR ontology from sdk", builder.asdict(), media_type=MediaType.Document) - -builder = OntologyBuilder( - classifications=[ - Classification( - name="prompt message scope text classification", - instructions="This is a prompt message scoped text classification", - class_type=Classification.Type.TEXT, - scope=Classification.Scope.INDEX, - attributes=[ - FeatureSchemaAttribute( - attributeName="prompt-message-scope", - attributeValue="true" - ) - ] - ) - ] -) - -# client.create_ontology('MMC Ontology with prompt message scope class', builder.asdict(), media_type=MediaType.Conversational, ontology_kind=OntologyKind.ModelEvaluation) - -builder = OntologyBuilder( - classifications=[ - Classification( - name="Requires connection checklist classification", - instructions="This is a requires connection checklist classification", - class_type=Classification.Type.CHECKLIST, - scope=Classification.Scope.GLOBAL, - attributes=[ - FeatureSchemaAttribute( - attributeName="requires-connection", - attributeValue="true" - ) - ], - options=[ - Option( - value='First option' - ), - Option( - value='Second option' - ) - ] - ) - ] -); - -# client.create_ontology('Image ontology with requires connection classes', builder.asdict(), media_type=MediaType.Image) - - -# feature_schema = client.upsert_feature_schema(Tool(name='Testing', tool=Tool.Type.BBOX, attributes=[FeatureSchemaAttribute(attributeName='auto-ocr', attributeValue='true')]).asdict()) -# print(feature_schema) -# fetched_feature_schema = client.get_feature_schema(feature_schema.uid) -# feature_schemas_with_name = client.get_feature_schemas('Auto OCR') - -# # Iterate over the feature schemas -# for schema in feature_schemas_with_name: -# print(schema) - -# ontology = client.create_ontology_from_feature_schemas('Ontology from feature schemas', ['cm4rc1nl90h36070782v9hlpt']) - -# feature_schema = client.update_feature_schema_title('cm4rc1nl90h36070782v9hlpt', 'This is a new title - did it remove the feature schema attributes? UPDATED') -# client.delete_unused_feature_schema('cm4rhzhn7026e07wm2az681di') - -# feature_schema = client.create_feature_schema(normalized={'tool': 'rectangle', 'name': 'cat', 'color': 'black', 'attributes': [{'attributeName': 'auto-ocr', 'attributeValue': 'true'}]}) -# print(feature_schema) - -# classification = Classification.from_dict({ -# "name": "Test Classification", -# "instructions": "Test instructions", -# "type": "text", # or "checklist" or other valid Classification.Type values -# "scope": "index", # or "index" for Classification.Scope values -# "required": False, # optional -# "attributes": [ # optional -# { -# "attributeName": "prompt-message-scope", -# "attributeValue": "true" -# } -# ], -# "options": [] -# }) - -# tool = Tool.from_dict({ -# "name": "Test Tool", -# "type": "rectangle", # or "checklist" or other valid Classification.Type values -# "required": False, # optional -# "attributes": [ # optional -# { -# "attributeName": "auto-ocr", -# "attributeValue": "true" -# } -# ], -# "options": [] -# }) - -tool = PromptIssueTool.from_dict({ - "name": "Test Tool", - "type": "rectangle", # or "checklist" or other valid Classification.Type values - "required": False, # optional - "attributes": [ # optional - { - "attributeName": "auto-ocr", - "attributeValue": "true" - } - ], - "options": [], -}) - -# print(tool) \ No newline at end of file From e22265fb04da6a286d8424ee0f11d488597a0406 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 08:47:04 -0700 Subject: [PATCH 08/18] Formatted files --- libs/labelbox/src/labelbox/schema/ontology.py | 3 ++- .../schema/tool_building/classification.py | 13 ++++++++++--- .../src/labelbox/schema/tool_building/types.py | 5 ++++- libs/labelbox/tests/unit/test_unit_ontology.py | 16 ++++++++-------- .../tests/unit/test_unit_prompt_issue_tool.py | 2 +- 5 files changed, 25 insertions(+), 14 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/ontology.py b/libs/labelbox/src/labelbox/schema/ontology.py index 4d3e04628..b9724d370 100644 --- a/libs/labelbox/src/labelbox/schema/ontology.py +++ b/libs/labelbox/src/labelbox/schema/ontology.py @@ -28,7 +28,6 @@ from labelbox.schema.tool_building.types import FeatureSchemaAttributes - class DeleteFeatureFromOntologyResult: archived: bool deleted: bool @@ -45,6 +44,7 @@ class FeatureSchema(DbObject): color = Field.String("name") normalized = Field.Json("normalized") + @dataclass class Tool: """ @@ -98,6 +98,7 @@ class Type(Enum): schema_id: Optional[str] = None feature_schema_id: Optional[str] = None attributes: Optional[FeatureSchemaAttributes] = None + @classmethod def from_dict(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]: return cls( diff --git a/libs/labelbox/src/labelbox/schema/tool_building/classification.py b/libs/labelbox/src/labelbox/schema/tool_building/classification.py index 7f2efc155..ea9d60e0d 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/classification.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/classification.py @@ -5,7 +5,10 @@ from lbox.exceptions import InconsistentOntologyException -from labelbox.schema.tool_building.types import FeatureSchemaId, FeatureSchemaAttributes +from labelbox.schema.tool_building.types import ( + FeatureSchemaId, + FeatureSchemaAttributes, +) @dataclass @@ -91,7 +94,9 @@ def __post_init__(self): if self.instructions is None: self.instructions = self.name if self.attributes is not None: - warnings.warn('Attributes are an experimental feature and may change in the future.') + warnings.warn( + "Attributes are an experimental feature and may change in the future." + ) @classmethod def from_dict(cls, dictionary: Dict[str, Any]) -> "Classification": @@ -123,7 +128,9 @@ def asdict(self, is_subclass: bool = False) -> Dict[str, Any]: "options": [o.asdict() for o in self.options], "schemaNodeId": self.schema_id, "featureSchemaId": self.feature_schema_id, - "attributes": self.attributes if self.attributes is not None else None, + "attributes": self.attributes + if self.attributes is not None + else None, } if ( self.class_type == self.Type.RADIO diff --git a/libs/labelbox/src/labelbox/schema/tool_building/types.py b/libs/labelbox/src/labelbox/schema/tool_building/types.py index fe537861e..be74cb788 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/types.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/types.py @@ -7,8 +7,11 @@ class FeatureSchemaAttribute(TypedDict): attributeName: str attributeValue: str + FeatureSchemaAttriubte = Annotated[FeatureSchemaAttribute, Field()] FeatureSchemaId = Annotated[str, Field(min_length=25, max_length=25)] SchemaId = Annotated[str, Field(min_length=25, max_length=25)] -FeatureSchemaAttributes = Annotated[List[FeatureSchemaAttribute], Field(default_factory=list)] +FeatureSchemaAttributes = Annotated[ + List[FeatureSchemaAttribute], Field(default_factory=list) +] diff --git a/libs/labelbox/tests/unit/test_unit_ontology.py b/libs/labelbox/tests/unit/test_unit_ontology.py index 7afc55e02..3a4bb94c4 100644 --- a/libs/labelbox/tests/unit/test_unit_ontology.py +++ b/libs/labelbox/tests/unit/test_unit_ontology.py @@ -15,7 +15,7 @@ "color": "#FF0000", "tool": "polygon", "classifications": [], - "attributes": None + "attributes": None, }, { "schemaNodeId": None, @@ -25,7 +25,7 @@ "color": "#FF0000", "tool": "superpixel", "classifications": [], - "attributes": None + "attributes": None, }, { "schemaNodeId": None, @@ -59,7 +59,7 @@ "name": "nested nested text", "type": "text", "options": [], - "attributes": None + "attributes": None, } ], }, @@ -71,7 +71,7 @@ "options": [], }, ], - "attributes": None + "attributes": None, }, { "schemaNodeId": None, @@ -81,7 +81,7 @@ "name": "nested text", "type": "text", "options": [], - "attributes": None + "attributes": None, }, ], }, @@ -93,7 +93,7 @@ "color": "#FF0000", "tool": "point", "classifications": [], - "attributes": None + "attributes": None, }, { "schemaNodeId": None, @@ -103,7 +103,7 @@ "color": "#FF0000", "tool": "line", "classifications": [], - "attributes": None + "attributes": None, }, { "schemaNodeId": None, @@ -113,7 +113,7 @@ "color": "#FF0000", "tool": "named-entity", "classifications": [], - "attributes": None + "attributes": None, }, ], "classifications": [ diff --git a/libs/labelbox/tests/unit/test_unit_prompt_issue_tool.py b/libs/labelbox/tests/unit/test_unit_prompt_issue_tool.py index 8ac73d93c..bfabbd632 100644 --- a/libs/labelbox/tests/unit/test_unit_prompt_issue_tool.py +++ b/libs/labelbox/tests/unit/test_unit_prompt_issue_tool.py @@ -47,7 +47,7 @@ def test_as_dict(): "schemaNodeId": None, "featureSchemaId": None, "scope": "global", - "attributes": None + "attributes": None, } ], "color": None, From a8e89112001ff05f608203e1bd4739e69a668814 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 09:28:28 -0700 Subject: [PATCH 09/18] Added attributes to unit tests --- libs/labelbox/tests/unit/test_unit_ontology.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/libs/labelbox/tests/unit/test_unit_ontology.py b/libs/labelbox/tests/unit/test_unit_ontology.py index 3a4bb94c4..67d01cb85 100644 --- a/libs/labelbox/tests/unit/test_unit_ontology.py +++ b/libs/labelbox/tests/unit/test_unit_ontology.py @@ -4,6 +4,7 @@ from lbox.exceptions import InconsistentOntologyException from labelbox import Classification, OntologyBuilder, Option, Tool +from labelbox.schema.tool_building.types import FeatureSchemaAttribute _SAMPLE_ONTOLOGY = { "tools": [ @@ -34,7 +35,12 @@ "name": "bbox", "color": "#FF0000", "tool": "rectangle", - "attributes": None, + "attributes": [ + FeatureSchemaAttribute( + attriubteName="auto-ocr", + attributeValue="true" + ) + ], "classifications": [ { "schemaNodeId": None, @@ -71,7 +77,12 @@ "options": [], }, ], - "attributes": None, + "attributes": [ + FeatureSchemaAttribute( + attributeName="requires-connection", + attributeValue="true" + ) + ], }, { "schemaNodeId": None, From d95092f343f2d4b30d7cbd7b4dc0741b218b67de Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 09:32:40 -0700 Subject: [PATCH 10/18] Formatting --- libs/labelbox/tests/unit/test_unit_ontology.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/labelbox/tests/unit/test_unit_ontology.py b/libs/labelbox/tests/unit/test_unit_ontology.py index 67d01cb85..377fc81aa 100644 --- a/libs/labelbox/tests/unit/test_unit_ontology.py +++ b/libs/labelbox/tests/unit/test_unit_ontology.py @@ -37,8 +37,7 @@ "tool": "rectangle", "attributes": [ FeatureSchemaAttribute( - attriubteName="auto-ocr", - attributeValue="true" + attriubteName="auto-ocr", attributeValue="true" ) ], "classifications": [ @@ -80,7 +79,7 @@ "attributes": [ FeatureSchemaAttribute( attributeName="requires-connection", - attributeValue="true" + attributeValue="true", ) ], }, From 10e9ce29bd38f24db46fc6f45600addc6837fb3d Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 10:28:50 -0700 Subject: [PATCH 11/18] Wrote integration tests for feature schema attributes --- libs/labelbox/tests/integration/conftest.py | 71 +++++++++++++++++++ .../tests/integration/test_feature_schema.py | 26 +++++++ 2 files changed, 97 insertions(+) diff --git a/libs/labelbox/tests/integration/conftest.py b/libs/labelbox/tests/integration/conftest.py index 11984cbd7..87aea0468 100644 --- a/libs/labelbox/tests/integration/conftest.py +++ b/libs/labelbox/tests/integration/conftest.py @@ -25,6 +25,7 @@ from labelbox.schema.data_row import DataRowMetadataField from labelbox.schema.ontology_kind import OntologyKind from labelbox.schema.user import User +from labelbox.schema.tool_building.types import FeatureSchemaAttribute @pytest.fixture @@ -552,6 +553,76 @@ def point(): ) +@pytest.fixture +def auto_ocr_text_value_class(): + return Classification( + class_type=Classification.Type.TEXT, + name="Auto OCR Text Value", + instructions="Text value for ocr bboxes", + scope=Classification.Scope.GLOBAL, + required=False, + attributes=[ + FeatureSchemaAttribute( + attributeName="auto-ocr-text-value", attributeValue="true" + ) + ], + ) + + +@pytest.fixture +def auto_ocr_bbox(auto_ocr_text_value_class): + return Tool( + tool=Tool.Type.BBOX, + name="Auto ocr bbox", + color="ff0000", + attributes=[ + FeatureSchemaAttribute( + attributeName="auto-ocr", attributeValue="true" + ) + ], + classifications=[auto_ocr_text_value_class], + ) + + +@pytest.fixture +def requires_connection_classification(): + return Classification( + name="Requires connection radio", + instructions="Classification that requires a connection", + class_type=Classification.Type.RADIO, + attributes=[ + FeatureSchemaAttribute( + attributeName="requires-connection", attributeValue="true" + ) + ], + options=[Option(value="A"), Option(value="B")], + ) + + +@pytest.fixture +def requires_connection_classification_feature_schema( + client, requires_connection_classification +): + created_feature_schema = client.upsert_feature_schema( + requires_connection_classification.asdict() + ) + yield created_feature_schema + client.delete_unused_feature_schema( + created_feature_schema.normalized["featureSchemaId"] + ) + + +@pytest.fixture +def auto_ocr_bbox_feature_schema(client, auto_ocr_bbox): + created_feature_schema = client.upsert_feature_schema( + auto_ocr_bbox.asdict() + ) + yield created_feature_schema + client.delete_unused_feature_schema( + created_feature_schema.normalized["featureSchemaId"] + ) + + @pytest.fixture def feature_schema(client, point): created_feature_schema = client.upsert_feature_schema(point.asdict()) diff --git a/libs/labelbox/tests/integration/test_feature_schema.py b/libs/labelbox/tests/integration/test_feature_schema.py index 46ec8c067..5713a067b 100644 --- a/libs/labelbox/tests/integration/test_feature_schema.py +++ b/libs/labelbox/tests/integration/test_feature_schema.py @@ -115,3 +115,29 @@ def test_does_not_include_used_feature_schema(client, feature_schema): assert feature_schema_id not in unused_feature_schemas client.delete_unused_ontology(ontology.uid) + + +def test_upsert_tool_with_attributes(auto_ocr_bbox_feature_schema): + auto_ocr_attributes = auto_ocr_bbox_feature_schema.normalized["attributes"] + auto_ocr_text_value_attributes = auto_ocr_bbox_feature_schema.normalized[ + "classifications" + ][0]["attributes"] + assert auto_ocr_attributes == [ + {"attributeName": "auto-ocr", "attributeValue": "true"} + ] + assert auto_ocr_text_value_attributes == [ + {"attributeName": "auto-ocr-text-value", "attributeValue": "true"} + ] + + +def test_upsert_classification_with_attributes( + requires_connection_classification_feature_schema, +): + requires_connection_attributes = ( + requires_connection_classification_feature_schema.normalized[ + "attributes" + ] + ) + assert requires_connection_attributes == [ + {"attributeName": "requires-connection", "attributeValue": "true"} + ] From ac91b9a597f646df9fbdc8fe9e1a2536d7516606 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 10:33:09 -0700 Subject: [PATCH 12/18] Lint fix --- libs/labelbox/src/labelbox/schema/tool_building/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/labelbox/src/labelbox/schema/tool_building/types.py b/libs/labelbox/src/labelbox/schema/tool_building/types.py index be74cb788..e30b54eca 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/types.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/types.py @@ -1,5 +1,5 @@ from typing import Annotated, List -from pydantic import Field, BaseModel +from pydantic import Field from typing import TypedDict From 608246332b07745d5ab8f74579e4c4198fe3c91f Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 11:43:11 -0700 Subject: [PATCH 13/18] Added warning to the Tool class when the user uses the attributes property --- libs/labelbox/src/labelbox/schema/ontology.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libs/labelbox/src/labelbox/schema/ontology.py b/libs/labelbox/src/labelbox/schema/ontology.py index b9724d370..10d01d5e0 100644 --- a/libs/labelbox/src/labelbox/schema/ontology.py +++ b/libs/labelbox/src/labelbox/schema/ontology.py @@ -26,6 +26,7 @@ map_tool_type_to_tool_cls, ) from labelbox.schema.tool_building.types import FeatureSchemaAttributes +import warnings class DeleteFeatureFromOntologyResult: @@ -99,6 +100,12 @@ class Type(Enum): feature_schema_id: Optional[str] = None attributes: Optional[FeatureSchemaAttributes] = None + def __post_init__(self): + if self.attributes is not None: + warnings.warn( + "Attributes are an experimental feature and may change in the future." + ) + @classmethod def from_dict(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]: return cls( From d4d9896645cf66d2d22e7f830612a54a18f7f724 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 12:37:53 -0700 Subject: [PATCH 14/18] Refactored FeatureSchemaAttribute to be a dataclass --- libs/labelbox/src/labelbox/schema/ontology.py | 19 ++++++++++++++++--- .../schema/tool_building/classification.py | 13 +++++++++++-- .../labelbox/schema/tool_building/types.py | 14 ++++++++++++-- .../labelbox/tests/unit/test_unit_ontology.py | 15 ++++++++------- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/ontology.py b/libs/labelbox/src/labelbox/schema/ontology.py index 10d01d5e0..da178f238 100644 --- a/libs/labelbox/src/labelbox/schema/ontology.py +++ b/libs/labelbox/src/labelbox/schema/ontology.py @@ -25,7 +25,10 @@ from labelbox.schema.tool_building.tool_type_mapping import ( map_tool_type_to_tool_cls, ) -from labelbox.schema.tool_building.types import FeatureSchemaAttributes +from labelbox.schema.tool_building.types import ( + FeatureSchemaAttribute, + FeatureSchemaAttributes, +) import warnings @@ -119,7 +122,15 @@ def from_dict(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]: for c in dictionary["classifications"] ], color=dictionary["color"], - attributes=dictionary.get("attributes", None), + attributes=[ + FeatureSchemaAttribute( + attributeName=attr["attributeName"], + attributeValue=attr["attributeValue"], + ) + for attr in dictionary.get("attributes", []) or [] + ] + if dictionary.get("attributes") + else None, ) def asdict(self) -> Dict[str, Any]: @@ -133,7 +144,9 @@ def asdict(self) -> Dict[str, Any]: ], "schemaNodeId": self.schema_id, "featureSchemaId": self.feature_schema_id, - "attributes": self.attributes, + "attributes": [a.asdict() for a in self.attributes] + if self.attributes is not None + else None, } def add_classification(self, classification: Classification) -> None: diff --git a/libs/labelbox/src/labelbox/schema/tool_building/classification.py b/libs/labelbox/src/labelbox/schema/tool_building/classification.py index ea9d60e0d..7b2ba9132 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/classification.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/classification.py @@ -8,6 +8,7 @@ from labelbox.schema.tool_building.types import ( FeatureSchemaId, FeatureSchemaAttributes, + FeatureSchemaAttribute, ) @@ -112,7 +113,15 @@ def from_dict(cls, dictionary: Dict[str, Any]) -> "Classification": schema_id=dictionary.get("schemaNodeId", None), feature_schema_id=dictionary.get("featureSchemaId", None), scope=cls.Scope(dictionary.get("scope", cls.Scope.GLOBAL)), - attributes=dictionary.get("attributes", None), + attributes=[ + FeatureSchemaAttribute( + attributeName=attr["attributeName"], + attributeValue=attr["attributeValue"], + ) + for attr in dictionary.get("attributes", []) or [] + ] + if dictionary.get("attributes") + else None, ) def asdict(self, is_subclass: bool = False) -> Dict[str, Any]: @@ -128,7 +137,7 @@ def asdict(self, is_subclass: bool = False) -> Dict[str, Any]: "options": [o.asdict() for o in self.options], "schemaNodeId": self.schema_id, "featureSchemaId": self.feature_schema_id, - "attributes": self.attributes + "attributes": [a.asdict() for a in self.attributes] if self.attributes is not None else None, } diff --git a/libs/labelbox/src/labelbox/schema/tool_building/types.py b/libs/labelbox/src/labelbox/schema/tool_building/types.py index e30b54eca..59082641e 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/types.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/types.py @@ -3,12 +3,22 @@ from typing import TypedDict -class FeatureSchemaAttribute(TypedDict): +from dataclasses import dataclass + + +@dataclass +class FeatureSchemaAttribute: attributeName: str attributeValue: str + def asdict(self): + return { + "attributeName": self.attributeName, + "attributeValue": self.attributeValue, + } + -FeatureSchemaAttriubte = Annotated[FeatureSchemaAttribute, Field()] +FeatureSchemaAttribute = Annotated[FeatureSchemaAttribute, Field()] FeatureSchemaId = Annotated[str, Field(min_length=25, max_length=25)] SchemaId = Annotated[str, Field(min_length=25, max_length=25)] diff --git a/libs/labelbox/tests/unit/test_unit_ontology.py b/libs/labelbox/tests/unit/test_unit_ontology.py index 377fc81aa..94b97609c 100644 --- a/libs/labelbox/tests/unit/test_unit_ontology.py +++ b/libs/labelbox/tests/unit/test_unit_ontology.py @@ -36,9 +36,10 @@ "color": "#FF0000", "tool": "rectangle", "attributes": [ - FeatureSchemaAttribute( - attriubteName="auto-ocr", attributeValue="true" - ) + { + "attributeName": "auto-ocr", + "attributeValue": "true", + } ], "classifications": [ { @@ -77,10 +78,10 @@ }, ], "attributes": [ - FeatureSchemaAttribute( - attributeName="requires-connection", - attributeValue="true", - ) + { + "attributeName": "requires-connection", + "attributeValue": "true", + } ], }, { From 44e05c4d465b323020c173397029084f61f412ae Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 13:06:22 -0700 Subject: [PATCH 15/18] Added from_dict method to the FeatureSchemaAttribute data class --- libs/labelbox/src/labelbox/schema/ontology.py | 5 +---- .../src/labelbox/schema/tool_building/classification.py | 5 +---- libs/labelbox/src/labelbox/schema/tool_building/types.py | 9 +++++++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/ontology.py b/libs/labelbox/src/labelbox/schema/ontology.py index da178f238..e451f00bb 100644 --- a/libs/labelbox/src/labelbox/schema/ontology.py +++ b/libs/labelbox/src/labelbox/schema/ontology.py @@ -123,10 +123,7 @@ def from_dict(cls, dictionary: Dict[str, Any]) -> Dict[str, Any]: ], color=dictionary["color"], attributes=[ - FeatureSchemaAttribute( - attributeName=attr["attributeName"], - attributeValue=attr["attributeValue"], - ) + FeatureSchemaAttribute.from_dict(attr) for attr in dictionary.get("attributes", []) or [] ] if dictionary.get("attributes") diff --git a/libs/labelbox/src/labelbox/schema/tool_building/classification.py b/libs/labelbox/src/labelbox/schema/tool_building/classification.py index 7b2ba9132..5f1d26335 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/classification.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/classification.py @@ -114,10 +114,7 @@ def from_dict(cls, dictionary: Dict[str, Any]) -> "Classification": feature_schema_id=dictionary.get("featureSchemaId", None), scope=cls.Scope(dictionary.get("scope", cls.Scope.GLOBAL)), attributes=[ - FeatureSchemaAttribute( - attributeName=attr["attributeName"], - attributeValue=attr["attributeValue"], - ) + FeatureSchemaAttribute.from_dict(attr) for attr in dictionary.get("attributes", []) or [] ] if dictionary.get("attributes") diff --git a/libs/labelbox/src/labelbox/schema/tool_building/types.py b/libs/labelbox/src/labelbox/schema/tool_building/types.py index 59082641e..c765ebc3e 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/types.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/types.py @@ -5,6 +5,8 @@ from dataclasses import dataclass +from typing import Any, Dict, List + @dataclass class FeatureSchemaAttribute: @@ -17,6 +19,13 @@ def asdict(self): "attributeValue": self.attributeValue, } + @classmethod + def from_dict(cls, dictionary: Dict[str, Any]) -> "FeatureSchemaAttribute": + return cls( + attributeName=dictionary["attributeName"], + attributeValue=dictionary["attributeValue"], + ) + FeatureSchemaAttribute = Annotated[FeatureSchemaAttribute, Field()] From 8769293cdf01d613f329a9802f854c05d4f9a0e3 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 13:08:51 -0700 Subject: [PATCH 16/18] Fixed linting errors --- libs/labelbox/src/labelbox/schema/tool_building/types.py | 1 - libs/labelbox/tests/unit/test_unit_ontology.py | 1 - 2 files changed, 2 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/tool_building/types.py b/libs/labelbox/src/labelbox/schema/tool_building/types.py index c765ebc3e..8e589c623 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/types.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/types.py @@ -1,6 +1,5 @@ from typing import Annotated, List from pydantic import Field -from typing import TypedDict from dataclasses import dataclass diff --git a/libs/labelbox/tests/unit/test_unit_ontology.py b/libs/labelbox/tests/unit/test_unit_ontology.py index 94b97609c..137e4fd1f 100644 --- a/libs/labelbox/tests/unit/test_unit_ontology.py +++ b/libs/labelbox/tests/unit/test_unit_ontology.py @@ -4,7 +4,6 @@ from lbox.exceptions import InconsistentOntologyException from labelbox import Classification, OntologyBuilder, Option, Tool -from labelbox.schema.tool_building.types import FeatureSchemaAttribute _SAMPLE_ONTOLOGY = { "tools": [ From 6824e0fb4bfd2a925d8115aaa7595d223e7cc5f3 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 13:21:52 -0700 Subject: [PATCH 17/18] Fix lint error --- libs/labelbox/src/labelbox/schema/tool_building/types.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/tool_building/types.py b/libs/labelbox/src/labelbox/schema/tool_building/types.py index 8e589c623..38c789837 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/types.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/types.py @@ -26,8 +26,6 @@ def from_dict(cls, dictionary: Dict[str, Any]) -> "FeatureSchemaAttribute": ) -FeatureSchemaAttribute = Annotated[FeatureSchemaAttribute, Field()] - FeatureSchemaId = Annotated[str, Field(min_length=25, max_length=25)] SchemaId = Annotated[str, Field(min_length=25, max_length=25)] FeatureSchemaAttributes = Annotated[ From 5ed769cd81347d368f6595c05f8b08523bacd601 Mon Sep 17 00:00:00 2001 From: Tim-Kerr Date: Tue, 17 Dec 2024 16:22:32 -0700 Subject: [PATCH 18/18] Updated the warning message to the standard message for feature schema attributes --- libs/labelbox/src/labelbox/schema/ontology.py | 2 +- .../src/labelbox/schema/tool_building/classification.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/ontology.py b/libs/labelbox/src/labelbox/schema/ontology.py index e451f00bb..3238dc4a5 100644 --- a/libs/labelbox/src/labelbox/schema/ontology.py +++ b/libs/labelbox/src/labelbox/schema/ontology.py @@ -106,7 +106,7 @@ class Type(Enum): def __post_init__(self): if self.attributes is not None: warnings.warn( - "Attributes are an experimental feature and may change in the future." + "The attributes for Tools are in beta. The attribute name and signature may change in the future." ) @classmethod diff --git a/libs/labelbox/src/labelbox/schema/tool_building/classification.py b/libs/labelbox/src/labelbox/schema/tool_building/classification.py index 5f1d26335..62fee2dda 100644 --- a/libs/labelbox/src/labelbox/schema/tool_building/classification.py +++ b/libs/labelbox/src/labelbox/schema/tool_building/classification.py @@ -96,7 +96,7 @@ def __post_init__(self): self.instructions = self.name if self.attributes is not None: warnings.warn( - "Attributes are an experimental feature and may change in the future." + "The attributes for Classifications are in beta. The attribute name and signature may change in the future." ) @classmethod