diff --git a/libs/labelbox/src/labelbox/annotated_types.py b/libs/labelbox/src/labelbox/annotated_types.py new file mode 100644 index 000000000..d0fe93ef8 --- /dev/null +++ b/libs/labelbox/src/labelbox/annotated_types.py @@ -0,0 +1,6 @@ +from typing_extensions import Annotated + +from pydantic import Field + + +Cuid = Annotated[str, Field(min_length=25, max_length=25)] diff --git a/libs/labelbox/src/labelbox/data/annotation_types/feature.py b/libs/labelbox/src/labelbox/data/annotation_types/feature.py index 836817aeb..e1e3dea45 100644 --- a/libs/labelbox/src/labelbox/data/annotation_types/feature.py +++ b/libs/labelbox/src/labelbox/data/annotation_types/feature.py @@ -1,6 +1,7 @@ from typing import Optional from pydantic import BaseModel, model_validator, model_serializer -from .types import Cuid + +from ...annotated_types import Cuid class FeatureSchema(BaseModel): diff --git a/libs/labelbox/src/labelbox/data/annotation_types/label.py b/libs/labelbox/src/labelbox/data/annotation_types/label.py index 973e9260f..aab5b2fd0 100644 --- a/libs/labelbox/src/labelbox/data/annotation_types/label.py +++ b/libs/labelbox/src/labelbox/data/annotation_types/label.py @@ -6,6 +6,8 @@ from labelbox.data.annotation_types.data.generic_data_row_data import GenericDataRowData from labelbox.data.annotation_types.data.tiled_image import TiledImageData from labelbox.schema import ontology + +from ...annotated_types import Cuid from .annotation import ClassificationAnnotation, ObjectAnnotation from .relationship import RelationshipAnnotation from .llm_prompt_response.prompt import PromptClassificationAnnotation @@ -13,7 +15,6 @@ from .data import AudioData, ConversationData, DicomData, DocumentData, HTMLData, ImageData, TextData, VideoData, LlmPromptCreationData, LlmPromptResponseCreationData, LlmResponseCreationData from .geometry import Mask from .metrics import ScalarMetric, ConfusionMatrixMetric -from .types import Cuid from .video import VideoClassificationAnnotation from .video import VideoObjectAnnotation, VideoMaskAnnotation from .mmc import MessageEvaluationTaskAnnotation diff --git a/libs/labelbox/src/labelbox/data/annotation_types/types.py b/libs/labelbox/src/labelbox/data/annotation_types/types.py index b26789aae..5a19ef792 100644 --- a/libs/labelbox/src/labelbox/data/annotation_types/types.py +++ b/libs/labelbox/src/labelbox/data/annotation_types/types.py @@ -7,8 +7,6 @@ from pydantic import StringConstraints, Field -Cuid = Annotated[str, StringConstraints(min_length=25, max_length=25)] - DType = TypeVar('DType') DShape = TypeVar('DShape') diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/base.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/base.py index 602fa7628..019b92516 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/base.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/base.py @@ -2,11 +2,12 @@ from uuid import uuid4 from labelbox.utils import _CamelCaseMixin, is_exactly_one_set -from ...annotation_types.types import Cuid from pydantic import model_validator, ConfigDict, BaseModel, Field from uuid import uuid4 import threading +from ....annotated_types import Cuid + subclass_registry = {} class _SubclassRegistryBase(BaseModel): diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/classification.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/classification.py index e655e9f36..28889f506 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/classification.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/classification.py @@ -1,14 +1,15 @@ from typing import Any, Dict, List, Union, Optional +from labelbox.data.annotation_types import ImageData, TextData, VideoData from labelbox.data.mixins import ConfidenceMixin, CustomMetric, CustomMetricsMixin from labelbox.data.serialization.ndjson.base import DataRow, NDAnnotation +from ....annotated_types import Cuid + from ...annotation_types.annotation import ClassificationAnnotation from ...annotation_types.video import VideoClassificationAnnotation from ...annotation_types.llm_prompt_response.prompt import PromptClassificationAnnotation, PromptText from ...annotation_types.classification.classification import ClassificationAnswer, Text, Checklist, Radio -from ...annotation_types.types import Cuid -from ...annotation_types.data import TextData, VideoData, ImageData from pydantic import model_validator, Field, BaseModel, ConfigDict, model_serializer from pydantic.alias_generators import to_camel from .base import _SubclassRegistryBase @@ -18,11 +19,12 @@ class NDAnswer(ConfidenceMixin, CustomMetricsMixin): name: Optional[str] = None schema_id: Optional[Cuid] = None classifications: Optional[List['NDSubclassificationType']] = None - model_config = ConfigDict(populate_by_name = True, alias_generator = to_camel) + model_config = ConfigDict(populate_by_name=True, alias_generator=to_camel) @model_validator(mode="after") def must_set_one(self): - if (not hasattr(self, "schema_id") or self.schema_id is None) and (not hasattr(self, "name") or self.name is None): + if (not hasattr(self, "schema_id") or self.schema_id + is None) and (not hasattr(self, "name") or self.name is None): raise ValueError("Schema id or name are not set. Set either one.") return self @@ -102,7 +104,10 @@ def from_common(cls, checklist: Checklist, name: str, NDAnswer(name=answer.name, schema_id=answer.feature_schema_id, confidence=answer.confidence, - classifications=[NDSubclassification.from_common(annot) for annot in answer.classifications] if answer.classifications else None, + classifications=[ + NDSubclassification.from_common(annot) + for annot in answer.classifications + ] if answer.classifications else None, custom_metrics=answer.custom_metrics) for answer in checklist.answer ], @@ -152,8 +157,8 @@ class NDPromptTextSubclass(NDAnswer): def to_common(self) -> PromptText: return PromptText(answer=self.answer, - confidence=self.confidence, - custom_metrics=self.custom_metrics) + confidence=self.confidence, + custom_metrics=self.custom_metrics) @classmethod def from_common(cls, prompt_text: PromptText, name: str, @@ -194,7 +199,8 @@ def from_common(cls, ) -class NDChecklist(NDAnnotation, NDChecklistSubclass, VideoSupported, _SubclassRegistryBase): +class NDChecklist(NDAnnotation, NDChecklistSubclass, VideoSupported, + _SubclassRegistryBase): @model_serializer(mode="wrap") def serialize_model(self, handler): @@ -237,7 +243,8 @@ def from_common( confidence=confidence) -class NDRadio(NDAnnotation, NDRadioSubclass, VideoSupported, _SubclassRegistryBase): +class NDRadio(NDAnnotation, NDRadioSubclass, VideoSupported, + _SubclassRegistryBase): @classmethod def from_common( @@ -266,35 +273,32 @@ def from_common( frames=extra.get('frames'), message_id=message_id, confidence=confidence) - + @model_serializer(mode="wrap") def serialize_model(self, handler): res = handler(self) if "classifications" in res and res["classifications"] == []: del res["classifications"] return res - - + + class NDPromptText(NDAnnotation, NDPromptTextSubclass, _SubclassRegistryBase): - + @classmethod - def from_common( - cls, - uuid: str, - text: PromptText, - name, - data: Dict, - feature_schema_id: Cuid, - confidence: Optional[float] = None - ) -> "NDPromptText": - return cls( - answer=text.answer, - data_row=DataRow(id=data.uid, global_key=data.global_key), - name=name, - schema_id=feature_schema_id, - uuid=uuid, - confidence=text.confidence, - custom_metrics=text.custom_metrics) + def from_common(cls, + uuid: str, + text: PromptText, + name, + data: Dict, + feature_schema_id: Cuid, + confidence: Optional[float] = None) -> "NDPromptText": + return cls(answer=text.answer, + data_row=DataRow(id=data.uid, global_key=data.global_key), + name=name, + schema_id=feature_schema_id, + uuid=uuid, + confidence=text.confidence, + custom_metrics=text.custom_metrics) class NDSubclassification: @@ -350,7 +354,8 @@ def to_common( for frame in annotation.frames: for idx in range(frame.start, frame.end + 1, 1): results.append( - VideoClassificationAnnotation(frame=idx, **common.model_dump(exclude_none=True))) + VideoClassificationAnnotation( + frame=idx, **common.model_dump(exclude_none=True))) return results @classmethod @@ -382,6 +387,7 @@ def lookup_classification( Radio: NDRadio }.get(type(annotation.value)) + class NDPromptClassification: @staticmethod @@ -404,8 +410,7 @@ def from_common( data: Union[VideoData, TextData, ImageData] ) -> Union[NDTextSubclass, NDChecklistSubclass, NDRadioSubclass]: return NDPromptText.from_common(str(annotation._uuid), annotation.value, - annotation.name, - data, + annotation.name, data, annotation.feature_schema_id, annotation.confidence) @@ -427,4 +432,4 @@ def from_common( # Make sure to keep NDChecklist prior to NDRadio in the list, # otherwise list of answers gets parsed by NDRadio whereas NDChecklist must be used NDClassificationType = Union[NDChecklist, NDRadio, NDText] -NDPromptClassificationType = Union[NDPromptText] \ No newline at end of file +NDPromptClassificationType = Union[NDPromptText] diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/mmc.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/mmc.py index 7b1908b76..d94b424e2 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/mmc.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/mmc.py @@ -3,7 +3,6 @@ from labelbox.utils import _CamelCaseMixin from .base import _SubclassRegistryBase, DataRow, NDAnnotation -from ...annotation_types.types import Cuid from ...annotation_types.mmc import MessageSingleSelectionTask, MessageMultiSelectionTask, MessageRankingTask, MessageEvaluationTaskAnnotation diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/objects.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/objects.py index 2b32f1c2b..566a691fc 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/objects.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/objects.py @@ -5,6 +5,7 @@ from labelbox.data.annotation_types.ner.conversation_entity import ConversationEntity from labelbox.data.annotation_types.video import VideoObjectAnnotation, DICOMObjectAnnotation from labelbox.data.mixins import ConfidenceMixin, CustomMetricsMixin, CustomMetric, CustomMetricsNotSupportedMixin +from ....annotated_types import Cuid import numpy as np from PIL import Image @@ -14,7 +15,6 @@ from ...annotation_types.data import ImageData, TextData, MaskData from ...annotation_types.ner import DocumentEntity, DocumentTextSelection, TextEntity -from ...annotation_types.types import Cuid from ...annotation_types.geometry import DocumentRectangle, Rectangle, Polygon, Line, Point, Mask from ...annotation_types.annotation import ClassificationAnnotation, ObjectAnnotation from ...annotation_types.video import VideoMaskAnnotation, DICOMMaskAnnotation, MaskFrame, MaskInstance diff --git a/libs/labelbox/src/labelbox/schema/labeling_service.py b/libs/labelbox/src/labelbox/schema/labeling_service.py index 70376f2e8..a3f2107bb 100644 --- a/libs/labelbox/src/labelbox/schema/labeling_service.py +++ b/libs/labelbox/src/labelbox/schema/labeling_service.py @@ -2,14 +2,14 @@ from typing import Any from typing_extensions import Annotated -from labelbox.exceptions import ResourceNotFoundError - from pydantic import BaseModel, Field + +from labelbox.exceptions import ResourceNotFoundError from labelbox.utils import _CamelCaseMixin from labelbox.schema.labeling_service_dashboard import LabelingServiceDashboard from labelbox.schema.labeling_service_status import LabelingServiceStatus -Cuid = Annotated[str, Field(min_length=25, max_length=25)] +from ..annotated_types import Cuid class LabelingService(_CamelCaseMixin):