Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions labelbox/data/annotation_types/data/audio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from labelbox.typing_imports import Literal
from labelbox.utils import _NoCoercionMixin
from .base_data import BaseData


class AudioData(BaseData):
...
class AudioData(BaseData, _NoCoercionMixin):
class_name: Literal["AudioData"] = "AudioData"
4 changes: 3 additions & 1 deletion labelbox/data/annotation_types/data/conversation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from labelbox.typing_imports import Literal
from labelbox.utils import _NoCoercionMixin
from .base_data import BaseData


class ConversationData(BaseData):
...
class_name: Literal["ConversationData"] = "ConversationData"
6 changes: 4 additions & 2 deletions labelbox/data/annotation_types/data/dicom.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from labelbox.typing_imports import Literal
from labelbox.utils import _NoCoercionMixin
from .base_data import BaseData


class DicomData(BaseData):
...
class DicomData(BaseData, _NoCoercionMixin):
class_name: Literal["DicomData"] = "DicomData"
6 changes: 4 additions & 2 deletions labelbox/data/annotation_types/data/document.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from labelbox.typing_imports import Literal
from labelbox.utils import _NoCoercionMixin
from .base_data import BaseData


class DocumentData(BaseData):
...
class DocumentData(BaseData, _NoCoercionMixin):
class_name: Literal["DocumentData"] = "DocumentData"
6 changes: 4 additions & 2 deletions labelbox/data/annotation_types/data/html.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from labelbox.typing_imports import Literal
from labelbox.utils import _NoCoercionMixin
from .base_data import BaseData


class HTMLData(BaseData):
...
class HTMLData(BaseData, _NoCoercionMixin):
class_name: Literal["HTMLData"] = "HTMLData"
5 changes: 4 additions & 1 deletion labelbox/data/annotation_types/data/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
from pydantic import root_validator

from labelbox.exceptions import InternalServerError
from labelbox.typing_imports import Literal
from labelbox.utils import _NoCoercionMixin
from .base_data import BaseData


class TextData(BaseData):
class TextData(BaseData, _NoCoercionMixin):
"""
Represents text data. Requires arg file_path, text, or url

Expand All @@ -20,6 +22,7 @@ class TextData(BaseData):
text (str)
url (str)
"""
class_name: Literal["TextData"] = "TextData"
file_path: Optional[str] = None
text: Optional[str] = None
url: Optional[str] = None
Expand Down
7 changes: 5 additions & 2 deletions labelbox/data/annotation_types/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@
VideoClassificationAnnotation, VideoObjectAnnotation,
DICOMObjectAnnotation)
from .classification import ClassificationAnswer
from .data import DicomData, VideoData, TextData, ImageData
from .data import AudioData, ConversationData, DicomData, DocumentData, HTMLData, ImageData, MaskData, TextData, VideoData
from .geometry import Mask
from .metrics import ScalarMetric, ConfusionMatrixMetric
from .types import Cuid
from ..ontology import get_feature_schema_lookup

DataType = Union[VideoData, ImageData, TextData, TiledImageData, AudioData,
ConversationData, DicomData, DocumentData, HTMLData]


class Label(BaseModel):
"""Container for holding data and annotations
Expand All @@ -38,7 +41,7 @@ class Label(BaseModel):
extra: additional context
"""
uid: Optional[Cuid] = None
data: Union[VideoData, ImageData, TextData, TiledImageData]
data: DataType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a generic pydantic type? sorry pydantic documentation links do not work for me for some reason

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've defined this type on line 19 above since the list was getting too long.

annotations: List[Union[ClassificationAnnotation, ObjectAnnotation,
ScalarMetric, ConfusionMatrixMetric]] = []
extra: Dict[str, Any] = {}
Expand Down
10 changes: 10 additions & 0 deletions labelbox/typing_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
This module imports types that differ across python versions, so other modules
don't have to worry about where they should be imported from.
"""

import sys
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
23 changes: 23 additions & 0 deletions labelbox/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,26 @@ class _CamelCaseMixin(BaseModel):
class Config:
allow_population_by_field_name = True
alias_generator = camel_case


class _NoCoercionMixin:
"""
When using Unions in type annotations, pydantic will try to coerce the type
of the object to the type of the first Union member. Which results in
uninteded behavior.

This mixin uses a class_name discriminator field to prevent pydantic from
corecing the type of the object. Add a class_name field to the class you
want to discrimniate and use this mixin class to remove the discriminator
when serializing the object.

Example:
class ConversationData(BaseData, _NoCoercionMixin):
class_name: Literal["ConversationData"] = "ConversationData"

"""

def dict(self, *args, **kwargs):
res = super().dict(*args, **kwargs)
res.pop('class_name')
return res
12 changes: 12 additions & 0 deletions tests/data/annotation_types/test_label.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numpy as np

import labelbox.types as lb_types
from labelbox import OntologyBuilder, Tool, Classification as OClassification, Option
from labelbox.data.annotation_types import (ClassificationAnswer, Radio, Text,
ClassificationAnnotation,
Expand Down Expand Up @@ -181,3 +182,14 @@ def test_schema_assignment_confidence():
])

assert label.annotations[0].confidence == 0.914


def test_initialize_label_no_coercion():
global_key = 'global-key'
ner_annotation = lb_types.ObjectAnnotation(
name="ner",
value=lb_types.ConversationEntity(start=0, end=8, message_id="4"))
label = Label(data=lb_types.ConversationData(global_key=global_key),
annotations=[ner_annotation])
assert isinstance(label.data, lb_types.ConversationData)
assert label.data.global_key == global_key