From ee26573b74b5577b84b510a0e30fa96f50681149 Mon Sep 17 00:00:00 2001 From: Val Brodsky Date: Mon, 9 Sep 2024 14:22:32 -0700 Subject: [PATCH 1/3] Delete tests --- .../labelbox/data/serialization/__init__.py | 1 - .../serialization/labelbox_v1/converter.py | 1 - .../labelbox_v1/test_document.py | 46 ----------------- .../serialization/labelbox_v1/test_image.py | 50 ------------------- .../serialization/labelbox_v1/test_text.py | 28 ----------- .../labelbox_v1/test_tiled_image.py | 45 ----------------- .../labelbox_v1/test_unknown_media.py | 47 ----------------- .../serialization/labelbox_v1/test_video.py | 40 --------------- .../data/serialization/ndjson/test_metric.py | 10 ---- 9 files changed, 268 deletions(-) delete mode 100644 libs/labelbox/tests/data/serialization/labelbox_v1/test_document.py delete mode 100644 libs/labelbox/tests/data/serialization/labelbox_v1/test_image.py delete mode 100644 libs/labelbox/tests/data/serialization/labelbox_v1/test_text.py delete mode 100644 libs/labelbox/tests/data/serialization/labelbox_v1/test_tiled_image.py delete mode 100644 libs/labelbox/tests/data/serialization/labelbox_v1/test_unknown_media.py delete mode 100644 libs/labelbox/tests/data/serialization/labelbox_v1/test_video.py diff --git a/libs/labelbox/src/labelbox/data/serialization/__init__.py b/libs/labelbox/src/labelbox/data/serialization/__init__.py index 6ca25b5d5..71a9b3443 100644 --- a/libs/labelbox/src/labelbox/data/serialization/__init__.py +++ b/libs/labelbox/src/labelbox/data/serialization/__init__.py @@ -1,3 +1,2 @@ -from .labelbox_v1 import LBV1Converter from .ndjson import NDJsonConverter from .coco import COCOConverter diff --git a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/converter.py b/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/converter.py index 570a63aa4..2b5437d39 100644 --- a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/converter.py +++ b/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/converter.py @@ -1,4 +1,3 @@ -from labelbox.data.serialization.labelbox_v1.objects import LBV1Mask from typing import Any, Dict, Generator, Iterable, Union import logging diff --git a/libs/labelbox/tests/data/serialization/labelbox_v1/test_document.py b/libs/labelbox/tests/data/serialization/labelbox_v1/test_document.py deleted file mode 100644 index 89b2e6c07..000000000 --- a/libs/labelbox/tests/data/serialization/labelbox_v1/test_document.py +++ /dev/null @@ -1,46 +0,0 @@ -import json -from typing import Dict, Any - -from labelbox.data.serialization.labelbox_v1.converter import LBV1Converter -import pytest - -IGNORE_KEYS = [ - "Data Split", "media_type", "DataRow Metadata", "Media Attributes" -] - - -def round_dict(data: Dict[str, Any]) -> Dict[str, Any]: - for key in data: - if isinstance(data[key], float): - data[key] = int(data[key]) - elif isinstance(data[key], dict): - data[key] = round_dict(data[key]) - return data - -@pytest.mark.skip() -def test_pdf(): - """ - Tests an export from a pdf document with only bounding boxes - """ - payload = json.load( - open('tests/data/assets/labelbox_v1/pdf_export.json', 'r')) - collection = LBV1Converter.deserialize(payload) - serialized = next(LBV1Converter.serialize(collection)) - - payload = payload[0] # only one document in the export - - serialized = {k: v for k, v in serialized.items() if k not in IGNORE_KEYS} - - assert serialized.keys() == payload.keys() - for key in payload.keys(): - if key == 'Label': - serialized_no_classes = [{ - k: v for k, v in dic.items() if k != 'classifications' - } for dic in serialized[key]['objects']] - serialized_round = [ - round_dict(dic) for dic in serialized_no_classes - ] - payload_round = [round_dict(dic) for dic in payload[key]['objects']] - assert payload_round == serialized_round - else: - assert serialized[key] == payload[key] diff --git a/libs/labelbox/tests/data/serialization/labelbox_v1/test_image.py b/libs/labelbox/tests/data/serialization/labelbox_v1/test_image.py deleted file mode 100644 index 8be9d7335..000000000 --- a/libs/labelbox/tests/data/serialization/labelbox_v1/test_image.py +++ /dev/null @@ -1,50 +0,0 @@ -import json - -import pytest - -from labelbox.data.serialization.labelbox_v1.converter import LBV1Converter - - -@pytest.mark.skip() -@pytest.mark.parametrize("file_path", [ - 'tests/data/assets/labelbox_v1/highly_nested_image.json', - 'tests/data/assets/labelbox_v1/image_export.json' -]) -#TODO: some checklists from the export come in as [checklist ans: []] -# while others are checklist ans: []... when we can figure out why we sometimes -# have extra brackets, we can look into testing nested checklist answers -# and ensuring the export's output matches deserialized/serialized output -def test_image(file_path): - with open(file_path, 'r') as file: - payload = json.load(file) - - collection = LBV1Converter.deserialize([payload]) - serialized = next(LBV1Converter.serialize(collection)) - - # We are storing the media types now. - payload['media_type'] = 'image' - payload['Global Key'] = None - - assert serialized.keys() == payload.keys() - - for key in serialized: - if key != 'Label': - assert serialized[key] == payload[key] - elif key == 'Label': - for annotation_a, annotation_b in zip(serialized[key]['objects'], - payload[key]['objects']): - annotation_b['page'] = None - annotation_b['unit'] = None - if not len(annotation_a['classifications']): - # We don't add a classification key to the payload if there is no classifications. - annotation_a.pop('classifications') - - if isinstance(annotation_b.get('classifications'), - list) and len(annotation_b['classifications']): - if isinstance(annotation_b['classifications'][0], list): - annotation_b['classifications'] = annotation_b[ - 'classifications'][0] - assert annotation_a == annotation_b - - -# After check the nd serializer on this shit.. It should work for almost everything (except the other horse shit..) diff --git a/libs/labelbox/tests/data/serialization/labelbox_v1/test_text.py b/libs/labelbox/tests/data/serialization/labelbox_v1/test_text.py deleted file mode 100644 index 446e760af..000000000 --- a/libs/labelbox/tests/data/serialization/labelbox_v1/test_text.py +++ /dev/null @@ -1,28 +0,0 @@ -import json - -from labelbox.data.serialization.labelbox_v1.converter import LBV1Converter -import pytest - -@pytest.mark.skip() -def test_text(): - with open('tests/data/assets/labelbox_v1/text_export.json', 'r') as file: - payload = json.load(file) - collection = LBV1Converter.deserialize([payload]) - serialized = next(LBV1Converter.serialize(collection)) - - payload['media_type'] = 'text' - payload['Global Key'] = None - - assert serialized.keys() == payload.keys() - for key in serialized: - if key != 'Label': - assert serialized[key] == payload[key] - elif key == 'Label': - for annotation_a, annotation_b in zip(serialized[key]['objects'], - payload[key]['objects']): - annotation_b['page'] = None - annotation_b['unit'] = None - if not len(annotation_a['classifications']): - # We don't add a classification key to the payload if there is no classifications. - annotation_a.pop('classifications') - assert annotation_a == annotation_b diff --git a/libs/labelbox/tests/data/serialization/labelbox_v1/test_tiled_image.py b/libs/labelbox/tests/data/serialization/labelbox_v1/test_tiled_image.py deleted file mode 100644 index df7e59405..000000000 --- a/libs/labelbox/tests/data/serialization/labelbox_v1/test_tiled_image.py +++ /dev/null @@ -1,45 +0,0 @@ -import json - -import pytest -from labelbox.data.annotation_types.geometry.polygon import Polygon -from labelbox.data.annotation_types.geometry.line import Line -from labelbox.data.annotation_types.geometry.point import Point -from labelbox.data.annotation_types.geometry.rectangle import Rectangle - -from labelbox.data.serialization.labelbox_v1.converter import LBV1Converter -from labelbox.schema.bulk_import_request import Bbox - -@pytest.mark.skip() -@pytest.mark.parametrize( - "file_path", ['tests/data/assets/labelbox_v1/tiled_image_export.json']) -def test_image(file_path): - """Tests against both Simple and non-Simple tiled image export data. - index-0 is non-Simple, index-1 is Simple - """ - with open(file_path, 'r') as f: - payload = json.load(f) - - collection = LBV1Converter.deserialize(payload) - collection_as_list = list(collection) - - assert len(collection_as_list) == 2 - - non_simple_annotations = collection_as_list[0].annotations - assert len(non_simple_annotations) == 6 - expected_shapes = [Polygon, Point, Point, Point, Line, Rectangle] - for idx in range(len(non_simple_annotations)): - assert isinstance(non_simple_annotations[idx].value, - expected_shapes[idx]) - assert non_simple_annotations[-1].value.start.x == -99.36567524971268 - assert non_simple_annotations[-1].value.start.y == 19.34717117508651 - assert non_simple_annotations[-1].value.end.x == -99.3649886680726 - assert non_simple_annotations[-1].value.end.y == 19.41999425190506 - - simple_annotations = collection_as_list[1].annotations - assert len(simple_annotations) == 8 - expected_shapes = [ - Polygon, Point, Point, Point, Point, Point, Line, Rectangle - ] - for idx in range(len(simple_annotations)): - assert isinstance(simple_annotations[idx].value, - expected_shapes[idx]) diff --git a/libs/labelbox/tests/data/serialization/labelbox_v1/test_unknown_media.py b/libs/labelbox/tests/data/serialization/labelbox_v1/test_unknown_media.py deleted file mode 100644 index c4a32b667..000000000 --- a/libs/labelbox/tests/data/serialization/labelbox_v1/test_unknown_media.py +++ /dev/null @@ -1,47 +0,0 @@ -import json - -import pytest - -from labelbox.data.serialization.labelbox_v1.converter import LBV1Converter - -@pytest.mark.skip() -def test_image(): - file_path = 'tests/data/assets/labelbox_v1/unkown_media_type_export.json' - with open(file_path, 'r') as file: - payload = json.load(file) - - collection = list(LBV1Converter.deserialize(payload)) - # One of the data rows is broken. - assert len(collection) != len(payload) - - for row in payload: - row['media_type'] = 'image' - row['Global Key'] = None - - collection = LBV1Converter.deserialize(payload) - for idx, serialized in enumerate(LBV1Converter.serialize(collection)): - assert serialized.keys() == payload[idx].keys() - for key in serialized: - if key != 'Label': - assert serialized[key] == payload[idx][key] - elif key == 'Label': - for annotation_a, annotation_b in zip( - serialized[key]['objects'], - payload[idx][key]['objects']): - if not len(annotation_a['classifications']): - # We don't add a classification key to the payload if there is no classifications. - annotation_a.pop('classifications') - annotation_b['page'] = None - annotation_b['unit'] = None - - if isinstance(annotation_b.get('classifications'), - list) and len( - annotation_b['classifications']): - if isinstance(annotation_b['classifications'][0], list): - annotation_b['classifications'] = annotation_b[ - 'classifications'][0] - - assert annotation_a == annotation_b - - -# After check the nd serializer on this shit.. It should work for almost everything (except the other horse shit..) diff --git a/libs/labelbox/tests/data/serialization/labelbox_v1/test_video.py b/libs/labelbox/tests/data/serialization/labelbox_v1/test_video.py deleted file mode 100644 index 169bef918..000000000 --- a/libs/labelbox/tests/data/serialization/labelbox_v1/test_video.py +++ /dev/null @@ -1,40 +0,0 @@ -import json - -from labelbox.data.serialization.labelbox_v1.converter import LBV1Converter - - -def round_dict(data): - for key in data: - if isinstance(data[key], float): - data[key] = int(data[key]) - elif isinstance(data[key], dict): - data[key] = round_dict(data[key]) - return data - - -def test_video(): - payload = json.load( - open('tests/data/assets/labelbox_v1/video_export.json', 'r')) - collection = LBV1Converter.deserialize([payload]) - serialized = next(LBV1Converter.serialize(collection)) - payload['media_type'] = 'video' - payload['Global Key'] = None - assert serialized.keys() == payload.keys() - for key in serialized: - if key != 'Label': - assert serialized[key] == payload[key] - elif key == 'Label': - for annotation_a, annotation_b in zip(serialized[key], - payload[key]): - assert annotation_a['frameNumber'] == annotation_b[ - 'frameNumber'] - assert annotation_a['classifications'] == annotation_b[ - 'classifications'] - - for obj_a, obj_b in zip(annotation_a['objects'], - annotation_b['objects']): - obj_b['page'] = None - obj_b['unit'] = None - obj_a = round_dict(obj_a) - obj_b = round_dict(obj_b) - assert obj_a == obj_b diff --git a/libs/labelbox/tests/data/serialization/ndjson/test_metric.py b/libs/labelbox/tests/data/serialization/ndjson/test_metric.py index 057603963..6508b73af 100644 --- a/libs/labelbox/tests/data/serialization/ndjson/test_metric.py +++ b/libs/labelbox/tests/data/serialization/ndjson/test_metric.py @@ -1,6 +1,5 @@ import json -from labelbox.data.serialization.labelbox_v1.converter import LBV1Converter from labelbox.data.serialization.ndjson.converter import NDJsonConverter @@ -12,9 +11,6 @@ def test_metric(): reserialized = list(NDJsonConverter.serialize(label_list)) assert reserialized == data - # Just make sure that this doesn't break - list(LBV1Converter.serialize(label_list)) - def test_custom_scalar_metric(): with open('tests/data/assets/ndjson/custom_scalar_import.json', @@ -26,9 +22,6 @@ def test_custom_scalar_metric(): assert json.dumps(reserialized, sort_keys=True) == json.dumps(data, sort_keys=True) - # Just make sure that this doesn't break - list(LBV1Converter.serialize(label_list)) - def test_custom_confusion_matrix_metric(): with open('tests/data/assets/ndjson/custom_confusion_matrix_import.json', @@ -39,6 +32,3 @@ def test_custom_confusion_matrix_metric(): reserialized = list(NDJsonConverter.serialize(label_list)) assert json.dumps(reserialized, sort_keys=True) == json.dumps(data, sort_keys=True) - - # Just make sure that this doesn't break - list(LBV1Converter.serialize(label_list)) From 23fb5d1356c1f08459a7d8375ea423c9b96ef2b3 Mon Sep 17 00:00:00 2001 From: Val Brodsky Date: Tue, 10 Sep 2024 16:47:09 -0700 Subject: [PATCH 2/3] Remove LBV1Converter classes --- .../serialization/labelbox_v1/__init__.py | 1 - .../labelbox_v1/classification.py | 117 ------ .../serialization/labelbox_v1/converter.py | 104 ------ .../data/serialization/labelbox_v1/feature.py | 28 -- .../data/serialization/labelbox_v1/label.py | 244 ------------- .../data/serialization/labelbox_v1/objects.py | 343 ------------------ libs/labelbox/src/labelbox/schema/project.py | 57 --- 7 files changed, 894 deletions(-) delete mode 100644 libs/labelbox/src/labelbox/data/serialization/labelbox_v1/__init__.py delete mode 100644 libs/labelbox/src/labelbox/data/serialization/labelbox_v1/classification.py delete mode 100644 libs/labelbox/src/labelbox/data/serialization/labelbox_v1/converter.py delete mode 100644 libs/labelbox/src/labelbox/data/serialization/labelbox_v1/feature.py delete mode 100644 libs/labelbox/src/labelbox/data/serialization/labelbox_v1/label.py delete mode 100644 libs/labelbox/src/labelbox/data/serialization/labelbox_v1/objects.py diff --git a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/__init__.py b/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/__init__.py deleted file mode 100644 index 60814be58..000000000 --- a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .converter import LBV1Converter diff --git a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/classification.py b/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/classification.py deleted file mode 100644 index c87a04f14..000000000 --- a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/classification.py +++ /dev/null @@ -1,117 +0,0 @@ -from typing import List, Union - - -from .feature import LBV1Feature -from ...annotation_types.annotation import ClassificationAnnotation -from ...annotation_types.classification import Checklist, ClassificationAnswer, Radio, Text -from ...annotation_types.types import Cuid -from pydantic import BaseModel - - -class LBV1ClassificationAnswer(LBV1Feature): - - def to_common(self) -> ClassificationAnswer: - return ClassificationAnswer(feature_schema_id=self.schema_id, - name=self.title, - keyframe=self.keyframe, - extra={ - 'feature_id': self.feature_id, - 'value': self.value - }) - - @classmethod - def from_common( - cls, - answer: ClassificationAnnotation) -> "LBV1ClassificationAnswer": - return cls(schema_id=answer.feature_schema_id, - title=answer.name, - value=answer.extra.get('value'), - feature_id=answer.extra.get('feature_id'), - keyframe=answer.keyframe) - - -class LBV1Radio(LBV1Feature): - answer: LBV1ClassificationAnswer - - def to_common(self) -> Radio: - return Radio(answer=self.answer.to_common()) - - @classmethod - def from_common(cls, radio: Radio, feature_schema_id: Cuid, - **extra) -> "LBV1Radio": - return cls(schema_id=feature_schema_id, - answer=LBV1ClassificationAnswer.from_common(radio.answer), - **extra) - - -class LBV1Checklist(LBV1Feature): - answers: List[LBV1ClassificationAnswer] - - def to_common(self) -> Checklist: - return Checklist(answer=[answer.to_common() for answer in self.answers]) - - @classmethod - def from_common(cls, checklist: Checklist, feature_schema_id: Cuid, - **extra) -> "LBV1Checklist": - return cls(schema_id=feature_schema_id, - answers=[ - LBV1ClassificationAnswer.from_common(answer) - for answer in checklist.answer - ], - **extra) - - -class LBV1Text(LBV1Feature): - answer: str - - def to_common(self) -> Text: - return Text(answer=self.answer) - - @classmethod - def from_common(cls, text: Text, feature_schema_id: Cuid, - **extra) -> "LBV1Text": - return cls(schema_id=feature_schema_id, answer=text.answer, **extra) - - -class LBV1Classifications(BaseModel): - classifications: List[Union[LBV1Text, LBV1Radio, - LBV1Checklist]] = [] - - def to_common(self) -> List[ClassificationAnnotation]: - classifications = [ - ClassificationAnnotation(value=classification.to_common(), - name=classification.title, - feature_schema_id=classification.schema_id, - extra={ - 'value': classification.value, - 'feature_id': classification.feature_id - }) - for classification in self.classifications - ] - return classifications - - @classmethod - def from_common( - cls, annotations: List[ClassificationAnnotation] - ) -> "LBV1Classifications": - classifications = [] - for annotation in annotations: - classification = cls.lookup_classification(annotation) - if classification is not None: - classifications.append( - classification.from_common(annotation.value, - annotation.feature_schema_id, - **annotation.extra)) - else: - raise TypeError(f"Unexpected type {type(annotation.value)}") - return cls(classifications=classifications) - - @staticmethod - def lookup_classification( - annotation: ClassificationAnnotation - ) -> Union[LBV1Text, LBV1Checklist, LBV1Radio, LBV1Checklist]: - return { - Text: LBV1Text, - Checklist: LBV1Checklist, - Radio: LBV1Radio - }.get(type(annotation.value)) diff --git a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/converter.py b/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/converter.py deleted file mode 100644 index 2b5437d39..000000000 --- a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/converter.py +++ /dev/null @@ -1,104 +0,0 @@ -from typing import Any, Dict, Generator, Iterable, Union -import logging - -from labelbox import parser -import requests -from copy import deepcopy -from requests.exceptions import HTTPError -from google.api_core import retry - -import labelbox -from .label import LBV1Label -from ...annotation_types.collection import (LabelCollection, LabelGenerator, - PrefetchGenerator) - -logger = logging.getLogger(__name__) - - -class LBV1Converter: - - @staticmethod - def deserialize_video(json_data: Union[str, Iterable[Dict[str, Any]]], - client: "labelbox.Client") -> LabelGenerator: - """ - Converts a labelbox video export into the common labelbox format. - - Args: - json_data: An iterable representing the labelbox video export. - client: The labelbox client for downloading video annotations - Returns: - LabelGenerator containing the video data. - """ - label_generator = (LBV1Label(**example).to_common() - for example in LBV1VideoIterator(json_data, client) - if example['Label']) - return LabelGenerator(data=label_generator) - - @staticmethod - def deserialize( - json_data: Union[str, Iterable[Dict[str, Any]]]) -> LabelGenerator: - """ - Converts a labelbox export (non-video) into the common labelbox format. - - Args: - json_data: An iterable representing the labelbox export. - Returns: - LabelGenerator containing the export data. - """ - - def label_generator(): - for example in json_data: - if 'frames' in example['Label']: - raise ValueError( - "Use `LBV1Converter.deserialize_video` to process video" - ) - - if example['Label']: - # Don't construct empty dict - yield LBV1Label(**example).to_common() - - return LabelGenerator(data=label_generator()) - - @staticmethod - def serialize( - labels: LabelCollection) -> Generator[Dict[str, Any], None, None]: - """ - Converts a labelbox common object to the labelbox json export format - - Note that any metric annotations will not be written since they are not defined in the LBV1 format. - - Args: - labels: Either a list of Label objects or a LabelGenerator (LabelCollection) - Returns: - A generator for accessing the labelbox json export representation of the data - """ - for label in labels: - res = LBV1Label.from_common(label) - yield res.model_dump(by_alias=True) - - -class LBV1VideoIterator(PrefetchGenerator): - """ - Generator that fetches video annotations in the background to be faster. - """ - - def __init__(self, examples, client): - self.client = client - super().__init__(examples) - - def _process(self, value): - value = deepcopy(value) - if 'frames' in value['Label']: - req = self._request(value) - value['Label'] = parser.loads(req) - return value - - @retry.Retry(predicate=retry.if_exception_type(HTTPError)) - def _request(self, value): - req = requests.get( - value['Label']['frames'], - headers={"Authorization": f"Bearer {self.client.api_key}"}) - if req.status_code == 401: - raise labelbox.exceptions.AuthenticationError("Invalid API key") - req.raise_for_status() - return req.text diff --git a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/feature.py b/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/feature.py deleted file mode 100644 index ed931dd77..000000000 --- a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/feature.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Optional - -from ...annotation_types.types import Cuid -from pydantic import BaseModel, ConfigDict, model_validator, model_serializer -from pydantic.alias_generators import to_camel - - -class LBV1Feature(BaseModel): - keyframe: Optional[bool] = None - title: str = None - value: Optional[str] = None - schema_id: Optional[Cuid] = None - feature_id: Optional[Cuid] = None - model_config = ConfigDict(populate_by_name = True, alias_generator = to_camel) - - @model_validator(mode = "after") - def check_ids(self, values): - if self.value is None: - self.value = self.title - return self - - @model_serializer(mode = "wrap") - def serialize_model(self, handler): - res = handler(self) - # This means these are no video frames .. - if self.keyframe is None: - res.pop('keyframe') - return res diff --git a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/label.py b/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/label.py deleted file mode 100644 index 4035871a2..000000000 --- a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/label.py +++ /dev/null @@ -1,244 +0,0 @@ -from labelbox.data.annotation_types.data.tiled_image import TiledImageData -from labelbox.utils import camel_case -from typing import List, Optional, Union, Dict, Any - -from ...annotation_types.annotation import (ClassificationAnnotation, - ObjectAnnotation) -from ...annotation_types.video import VideoClassificationAnnotation, VideoObjectAnnotation -from ...annotation_types.data import ImageData, TextData, VideoData -from ...annotation_types.label import Label -from .classification import LBV1Classifications -from .objects import LBV1Objects, LBV1TextEntity -from pydantic import Field, BaseModel, ConfigDict, model_serializer -from pydantic.alias_generators import to_camel - - -class LBV1LabelAnnotations(LBV1Classifications, LBV1Objects): - - def to_common( - self) -> List[Union[ObjectAnnotation, ClassificationAnnotation]]: - classifications = LBV1Classifications.to_common(self) - objects = LBV1Objects.to_common(self) - return [*objects, *classifications] - - @classmethod - def from_common( - cls, annotations: List[Union[ClassificationAnnotation, - ObjectAnnotation]] - ) -> "LBV1LabelAnnotations": - - objects = LBV1Objects.from_common( - [x for x in annotations if isinstance(x, ObjectAnnotation)]) - classifications = LBV1Classifications.from_common( - [x for x in annotations if isinstance(x, ClassificationAnnotation)]) - return cls(**objects.model_dump(), **classifications.model_dump()) - - -class LBV1LabelAnnotationsVideo(LBV1LabelAnnotations): - frame_number: int = Field(..., alias='frameNumber') - model_config = ConfigDict(populate_by_name = True) - - def to_common( - self - ) -> List[Union[VideoClassificationAnnotation, VideoObjectAnnotation]]: - classifications = [ - VideoClassificationAnnotation( - value=classification.to_common(), - frame=self.frame_number, - name=classification.title, - feature_schema_id=classification.schema_id) - for classification in self.classifications - ] - - objects = [ - VideoObjectAnnotation(value=obj.to_common(), - keyframe=obj.keyframe, - classifications=[ - ClassificationAnnotation( - value=cls.to_common(), - feature_schema_id=cls.schema_id, - name=cls.title, - extra={ - 'feature_id': cls.feature_id, - 'title': cls.title, - 'value': cls.value, - }) for cls in obj.classifications - ], - name=obj.title, - frame=self.frame_number, - alternative_name=obj.value, - feature_schema_id=obj.schema_id, - extra={ - 'value': obj.value, - 'instanceURI': obj.instanceURI, - 'color': obj.color, - 'feature_id': obj.feature_id, - }) for obj in self.objects - ] - return [*classifications, *objects] - - @classmethod - def from_common( - cls, annotations: List[Union[VideoObjectAnnotation, - VideoClassificationAnnotation]] - ) -> "LBV1LabelAnnotationsVideo": - by_frames = {} - for annotation in annotations: - if annotation.frame in by_frames: - by_frames[annotation.frame].append(annotation) - else: - by_frames[annotation.frame] = [annotation] - - result = [] - for frame in by_frames: - converted = LBV1LabelAnnotations.from_common( - annotations=by_frames[frame]) - result.append( - LBV1LabelAnnotationsVideo( - frame_number=frame, - objects=converted.objects, - classifications=converted.classifications)) - - return result - - -class Review(BaseModel): - score: int - id: str - created_at: str - created_by: str - label_id: Optional[str] = None - model_config = ConfigDict(alias_generator = to_camel) - - -Extra = lambda name: Field(None, alias=name, extra_field=True) - - -class LBV1Label(BaseModel): - label: Union[LBV1LabelAnnotations, - List[LBV1LabelAnnotationsVideo]] = Field( - ..., alias='Label') - data_row_id: str = Field(..., alias="DataRow ID") - row_data: Optional[str] = Field(None, alias="Labeled Data") - id: Optional[str] = Field(None, alias='ID') - external_id: Optional[str] = Field(None, alias="External ID") - data_row_media_attributes: Optional[Dict[str, Any]] = Field( - {}, alias="Media Attributes") - data_row_metadata: Optional[List[Dict[str, Any]]] = Field( - [], alias="DataRow Metadata") - - created_by: Optional[str] = Extra('Created By') - project_name: Optional[str] = Extra('Project Name') - created_at: Optional[str] = Extra('Created At') - updated_at: Optional[str] = Extra('Updated At') - seconds_to_label: Optional[float] = Extra('Seconds to Label') - agreement: Optional[float] = Extra('Agreement') - benchmark_agreement: Optional[float] = Extra('Benchmark Agreement') - benchmark_id: Optional[str] = Extra('Benchmark ID') - dataset_name: Optional[str] = Extra('Dataset Name') - reviews: Optional[List[Review]] = Extra('Reviews') - label_url: Optional[str] = Extra('View Label') - has_open_issues: Optional[float] = Extra('Has Open Issues') - skipped: Optional[bool] = Extra('Skipped') - media_type: Optional[str] = Extra('media_type') - data_split: Optional[str] = Extra('Data Split') - global_key: Optional[str] = Extra('Global Key') - model_config = ConfigDict(populate_by_name = True) - - def to_common(self) -> Label: - if isinstance(self.label, list): - annotations = [] - for lbl in self.label: - annotations.extend(lbl.to_common()) - else: - annotations = self.label.to_common() - - return Label(data=self._data_row_to_common(), - uid=self.id, - annotations=annotations, - extra={ - field.alias: getattr(self, field_name) - for field_name, field in self.model_fields.items() - if isinstance(field.json_schema_extra, Dict) and field.json_schema_extra["extra_field"] - }) - - @classmethod - def from_common(cls, label: Label): - if isinstance(label.annotations[0], - (VideoObjectAnnotation, VideoClassificationAnnotation)): - label_ = LBV1LabelAnnotationsVideo.from_common(label.annotations) - else: - label_ = LBV1LabelAnnotations.from_common(label.annotations) - return LBV1Label(label=label_, - id=label.uid, - data_row_id=label.data.uid, - row_data=label.data.url, - external_id=label.data.external_id, - data_row_media_attributes=label.data.media_attributes, - data_row_metadata=label.data.metadata, - **label.extra) - - def _data_row_to_common( - self) -> Union[ImageData, TextData, VideoData, TiledImageData]: - # Use data row information to construct the appropriate annotation type - data_row_info = { - 'url' if self._is_url() else 'text': self.row_data, - 'external_id': self.external_id, - 'uid': self.data_row_id, - 'media_attributes': self.data_row_media_attributes, - 'metadata': self.data_row_metadata - } - - self.media_type = self.media_type or self._infer_media_type() - media_mapping = { - 'text': TextData, - 'image': ImageData, - 'video': VideoData - } - if self.media_type not in media_mapping: - raise ValueError( - f"Annotation types are only supported for {list(media_mapping)} media types." - f" Found {self.media_type}.") - return media_mapping[self.media_type](**data_row_info) - - def _infer_media_type(self) -> str: - # Determines the data row type based on the label content - if isinstance(self.label, list): - return 'video' - if self._has_text_annotations(): - return 'text' - elif self._has_object_annotations(): - return 'image' - else: - if self._row_contains((".jpg", ".png", ".jpeg")) and self._is_url(): - return 'image' - elif (self._row_contains((".txt", ".text", ".html")) and - self._is_url()) or not self._is_url(): - return 'text' - else: - # This condition will occur when a data row url does not contain a file extension - # and the label does not contain object annotations that indicate the media type. - # As a temporary workaround you can explicitly set the media_type - # in each label json payload before converting. - # We will eventually provide the media type in the export. - raise TypeError( - f"Can't infer data type from row data. row_data: {self.row_data[:200]}" - ) - - def _has_object_annotations(self) -> bool: - return len(self.label.objects) > 0 - - def _has_text_annotations(self) -> bool: - return len([ - annotation for annotation in self.label.objects - if isinstance(annotation, LBV1TextEntity) - ]) > 0 - - def _row_contains(self, substrs) -> bool: - lower_row_data = self.row_data.lower() - return any([substr in lower_row_data for substr in substrs]) - - def _is_url(self) -> bool: - return self.row_data.startswith( - ("http://", "https://", "gs://", - "s3://")) or "tileLayerUrl" in self.row_data diff --git a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/objects.py b/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/objects.py deleted file mode 100644 index 21c72e18d..000000000 --- a/libs/labelbox/src/labelbox/data/serialization/labelbox_v1/objects.py +++ /dev/null @@ -1,343 +0,0 @@ -from typing import Any, Dict, List, Optional, Union, Type -try: - from typing import Literal -except: - from typing_extensions import Literal - -import numpy as np - -from .classification import LBV1Checklist, LBV1Classifications, LBV1Radio, LBV1Text -from .feature import LBV1Feature -from ...annotation_types.annotation import (ClassificationAnnotation, - ObjectAnnotation) -from ...annotation_types.data import MaskData -from ...annotation_types.geometry import Line, Mask, Point, Polygon, Rectangle -from ...annotation_types.ner import TextEntity -from ...annotation_types.types import Cuid -from pydantic import BaseModel, Field, model_serializer, field_validator - - -class LBV1ObjectBase(LBV1Feature): - color: Optional[str] = None - instanceURI: Optional[str] = Field(default=None, serialization_alias="instanceURI") - classifications: List[Union[LBV1Text, LBV1Radio, - LBV1Checklist]] = [] - page: Optional[int] = None - unit: Optional[str] = None - - @model_serializer(mode="wrap") - def serialize_model(self, handler): - res = handler(self) - # This means these are not video frames .. - if self.instanceURI is None: - if "instanceURI" in res: - res.pop('instanceURI') - if "instanceuri" in res: - res.pop("instanceuri") - return res - - @field_validator('classifications', mode="before") - def validate_subclasses(cls, value): - # checklist subclasses create extra unessesary nesting. So we just remove it. - if isinstance(value, list) and len(value): - subclasses = [] - for v in value: - # this is due to Checklists providing extra brackets []. We grab every item - # in the brackets if this is the case - if isinstance(v, list): - for inner_v in v: - subclasses.append(inner_v) - else: - subclasses.append(v) - return subclasses - return value - - -class TIPointCoordinate(BaseModel): - coordinates: List[float] - - -class TILineCoordinate(BaseModel): - coordinates: List[List[float]] - - -class TIPolygonCoordinate(BaseModel): - coordinates: List[List[List[float]]] - - -class TIRectangleCoordinate(BaseModel): - coordinates: List[List[List[float]]] - - -class LBV1TIPoint(LBV1ObjectBase): - object_type: Literal['point'] = Field(..., alias='type') - geometry: TIPointCoordinate - - def to_common(self) -> Point: - lng, lat = self.geometry.coordinates - return Point(x=lng, y=lat) - - -class LBV1TILine(LBV1ObjectBase): - object_type: Literal['polyline'] = Field(..., alias='type') - geometry: TILineCoordinate - - def to_common(self) -> Line: - return Line(points=[ - Point(x=coord[0], y=coord[1]) for coord in self.geometry.coordinates - ]) - - -class LBV1TIPolygon(LBV1ObjectBase): - object_type: Literal['polygon'] = Field(..., alias='type') - geometry: TIPolygonCoordinate - - def to_common(self) -> Polygon: - for coord_list in self.geometry.coordinates: - return Polygon( - points=[Point(x=coord[0], y=coord[1]) for coord in coord_list]) - - -class LBV1TIRectangle(LBV1ObjectBase): - object_type: Literal['rectangle'] = Field(..., alias='type') - geometry: TIRectangleCoordinate - - def to_common(self) -> Rectangle: - coord_list = np.array(self.geometry.coordinates[0]) - - min_x, max_x = np.min(coord_list[:, 0]), np.max(coord_list[:, 0]) - min_y, max_y = np.min(coord_list[:, 1]), np.max(coord_list[:, 1]) - - start = [min_x, min_y] - end = [max_x, max_y] - - return Rectangle(start=Point(x=start[0], y=start[1]), - end=Point(x=end[0], y=end[1])) - - -class _Point(BaseModel): - x: float - y: float - - -class _Box(BaseModel): - top: float - left: float - height: float - width: float - - -class LBV1Rectangle(LBV1ObjectBase): - bbox: _Box - - def to_common(self) -> Rectangle: - return Rectangle(start=Point(x=self.bbox.left, y=self.bbox.top), - end=Point(x=self.bbox.left + self.bbox.width, - y=self.bbox.top + self.bbox.height)) - - @classmethod - def from_common(cls, rectangle: Rectangle, - classifications: List[ClassificationAnnotation], - feature_schema_id: Cuid, title: str, - extra: Dict[str, Any]) -> "LBV1Rectangle": - return cls(bbox=_Box( - top=rectangle.start.y, - left=rectangle.start.x, - height=rectangle.end.y - rectangle.start.y, - width=rectangle.end.x - rectangle.start.x, - ), - schema_id=feature_schema_id, - title=title, - classifications=classifications, - **extra) - - -class LBV1Polygon(LBV1ObjectBase): - polygon: List[_Point] - - def to_common(self) -> Polygon: - return Polygon(points=[Point(x=p.x, y=p.y) for p in self.polygon]) - - @classmethod - def from_common(cls, polygon: Polygon, - classifications: List[ClassificationAnnotation], - feature_schema_id: Cuid, title: str, - extra: Dict[str, Any]) -> "LBV1Polygon": - return cls( - polygon=[ - _Point(x=point.x, y=point.y) for point in polygon.points[:-1] - ], # drop closing point - classifications=classifications, - schema_id=feature_schema_id, - title=title, - **extra) - - -class LBV1Point(LBV1ObjectBase): - point: _Point - - def to_common(self) -> Point: - return Point(x=self.point.x, y=self.point.y) - - @classmethod - def from_common(cls, point: Point, - classifications: List[ClassificationAnnotation], - feature_schema_id: Cuid, title: str, - extra: Dict[str, Any]) -> "LBV1Point": - return cls(point=_Point(x=point.x, y=point.y), - classifications=classifications, - schema_id=feature_schema_id, - title=title, - **extra) - - -class LBV1Line(LBV1ObjectBase): - line: List[_Point] - - def to_common(self) -> Line: - return Line(points=[Point(x=p.x, y=p.y) for p in self.line]) - - @classmethod - def from_common(cls, polygon: Line, - classifications: List[ClassificationAnnotation], - feature_schema_id: Cuid, title: str, - extra: Dict[str, Any]) -> "LBV1Line": - return cls( - line=[_Point(x=point.x, y=point.y) for point in polygon.points], - classifications=classifications, - schema_id=feature_schema_id, - title=title, - **extra) - - -class LBV1Mask(LBV1ObjectBase): - instanceURI: str - - def to_common(self) -> Mask: - return Mask(mask=MaskData(url=self.instanceURI), color=(255, 255, 255)) - - @classmethod - def from_common(cls, mask: Mask, - classifications: List[ClassificationAnnotation], - feature_schema_id: Cuid, title: str, - extra: Dict[str, Any]) -> "LBV1Mask": - if mask.mask.url is None: - raise ValueError( - "Mask does not have a url. Use `LabelGenerator.add_url_to_masks`, or `Label.add_url_to_masks`." - ) - return cls(instanceURI=mask.mask.url, - classifications=classifications, - schema_id=feature_schema_id, - title=title, - **{ - k: v for k, v in extra.items() if k != 'instanceURI' - }) - - -class _TextPoint(BaseModel): - start: int - end: int - - -class _Location(BaseModel): - location: _TextPoint - - -class LBV1TextEntity(LBV1ObjectBase): - data: _Location - format: str = "text.location" - version: int = 1 - - def to_common(self) -> TextEntity: - return TextEntity( - start=self.data.location.start, - end=self.data.location.end, - ) - - @classmethod - def from_common(cls, text_entity: TextEntity, - classifications: List[ClassificationAnnotation], - feature_schema_id: Cuid, title: str, - extra: Dict[str, Any]) -> "LBV1TextEntity": - return cls(data=_Location( - location=_TextPoint(start=text_entity.start, end=text_entity.end)), - classifications=classifications, - schema_id=feature_schema_id, - title=title, - **extra) - - -class LBV1Objects(BaseModel): - objects: List[Union[ - LBV1Line, - LBV1Point, - LBV1Polygon, - LBV1Rectangle, - LBV1TextEntity, - LBV1Mask, - LBV1TIPoint, - LBV1TILine, - LBV1TIPolygon, - LBV1TIRectangle, - ]] - - def to_common(self) -> List[ObjectAnnotation]: - objects = [ - ObjectAnnotation(value=obj.to_common(), - classifications=[ - ClassificationAnnotation( - value=cls.to_common(), - feature_schema_id=cls.schema_id, - name=cls.title, - extra={ - 'feature_id': cls.feature_id, - 'title': cls.title, - 'value': cls.value - }) for cls in obj.classifications - ], - name=obj.title, - feature_schema_id=obj.schema_id, - extra={ - 'instanceURI': obj.instanceURI, - 'color': obj.color, - 'feature_id': obj.feature_id, - 'value': obj.value, - 'page': obj.page, - 'unit': obj.unit, - }) for obj in self.objects - ] - return objects - - @classmethod - def from_common(cls, annotations: List[ObjectAnnotation]) -> "LBV1Objects": - objects = [] - for annotation in annotations: - obj = cls.lookup_object(annotation) - subclasses = LBV1Classifications.from_common( - annotation.classifications).classifications - - objects.append( - obj.from_common( - annotation.value, subclasses, annotation.feature_schema_id, - annotation.name, { - 'keyframe': getattr(annotation, 'keyframe', None), - **annotation.extra - })) - return cls(objects=objects) - - @staticmethod - def lookup_object( - annotation: ObjectAnnotation - ) -> Type[Union[LBV1Line, LBV1Point, LBV1Polygon, LBV1Rectangle, LBV1Mask, - LBV1TextEntity]]: - result = { - Line: LBV1Line, - Point: LBV1Point, - Polygon: LBV1Polygon, - Rectangle: LBV1Rectangle, - Mask: LBV1Mask, - TextEntity: LBV1TextEntity - }.get(type(annotation.value)) - if result is None: - raise TypeError(f"Unexpected type {type(annotation.value)}") - return result diff --git a/libs/labelbox/src/labelbox/schema/project.py b/libs/labelbox/src/labelbox/schema/project.py index 5d9458468..a30ff856b 100644 --- a/libs/labelbox/src/labelbox/schema/project.py +++ b/libs/labelbox/src/labelbox/schema/project.py @@ -46,10 +46,6 @@ if TYPE_CHECKING: from labelbox import BulkImportRequest -try: - from labelbox.data.serialization import LBV1Converter -except ImportError: - pass DataRowPriority = int LabelingParameterOverrideInput = Tuple[Union[DataRow, DataRowIdentifier], @@ -366,52 +362,6 @@ def export_queued_data_rows( self.uid) time.sleep(sleep_time) - def label_generator(self, timeout_seconds=600, **kwargs): - """ - Download text and image annotations, or video annotations. - - For a mixture of text/image and video, use project.export_labels() - - Returns: - LabelGenerator for accessing labels - """ - _check_converter_import() - json_data = self.export_labels(download=True, - timeout_seconds=timeout_seconds, - **kwargs) - - # assert that the instance this would fail is only if timeout runs out - assert isinstance( - json_data, - List), "Unable to successfully get labels. Please try again" - - if json_data is None: - raise TimeoutError( - f"Unable to download labels in {timeout_seconds} seconds." - "Please try again or contact support if the issue persists.") - - is_video = [ - "frames" in row["Label"] - for row in json_data - if row["Label"] and not row["Skipped"] - ] - - if len(is_video) and not all(is_video) and any(is_video): - raise ValueError( - "Found mixed data types of video and text/image. " - "Use project.export_labels() to export projects with mixed data types. " - ) - if len(is_video) and all(is_video): - # Filter skipped labels to avoid inference errors - json_data = [ - label for label in self.export_labels(download=True) - if not label["Skipped"] - ] - - return LBV1Converter.deserialize_video(json_data, self.client) - - return LBV1Converter.deserialize(json_data) - def export_labels(self, download=False, timeout_seconds=1800, @@ -1995,10 +1945,3 @@ class LabelingParameterOverride(DbObject): LabelerPerformance.__doc__ = ( "Named tuple containing info about a labeler's performance.") - -def _check_converter_import(): - if 'LBV1Converter' not in globals(): - raise ImportError( - "Missing dependencies to import converter. " - "Use `pip install labelbox[data] --upgrade` to add missing dependencies. " - "or download raw json with project.export_labels()") From badbaac4e6d1ef8cf071fc307284642de7043b0e Mon Sep 17 00:00:00 2001 From: Val Brodsky Date: Tue, 10 Sep 2024 16:47:35 -0700 Subject: [PATCH 3/3] Remove test relying on LBV1Converter --- .../metrics/iou/data_row/test_data_row_iou.py | 143 ------------------ 1 file changed, 143 deletions(-) delete mode 100644 libs/labelbox/tests/data/metrics/iou/data_row/test_data_row_iou.py diff --git a/libs/labelbox/tests/data/metrics/iou/data_row/test_data_row_iou.py b/libs/labelbox/tests/data/metrics/iou/data_row/test_data_row_iou.py deleted file mode 100644 index e4f161e3e..000000000 --- a/libs/labelbox/tests/data/metrics/iou/data_row/test_data_row_iou.py +++ /dev/null @@ -1,143 +0,0 @@ -from labelbox.data.metrics.iou.iou import miou_metric -from pytest_cases import parametrize, fixture_ref -from unittest.mock import patch -import math -import numpy as np -import base64 - -from labelbox.data.metrics.iou import data_row_miou, feature_miou_metric -from labelbox.data.serialization import NDJsonConverter, LBV1Converter -from labelbox.data.annotation_types import Label, ImageData, Mask - - -def check_iou(pair, mask=None): - default = Label(data=ImageData( - uid="ckppihxc10005aeyjen11h7jh", media_attributes=None, metadata=None)) - prediction = next(NDJsonConverter.deserialize(pair.predictions), default) - label = next(LBV1Converter.deserialize([pair.labels])) - if mask: - for annotation in [*prediction.annotations, *label.annotations]: - if isinstance(annotation.value, Mask): - annotation.value.mask.arr = np.frombuffer( - base64.b64decode(annotation.value.mask.url.encode('utf-8')), - dtype=np.uint8).reshape((32, 32, 3)) - - for include_subclasses, expected_attr_name in [[ - True, 'expected' - ], [False, 'expected_without_subclasses']]: - assert math.isclose( - data_row_miou(label, - prediction, - include_subclasses=include_subclasses), - getattr(pair, expected_attr_name)) - assert math.isclose( - miou_metric(label.annotations, - prediction.annotations, - include_subclasses=include_subclasses)[0].value, - getattr(pair, expected_attr_name)) - feature_ious = feature_miou_metric( - label.annotations, - prediction.annotations, - include_subclasses=include_subclasses) - assert len( - feature_ious - ) == 1 # The tests run here should only have one class present. - assert math.isclose(feature_ious[0].value, - getattr(pair, expected_attr_name)) - - -def check_iou_checklist(pair, mask=None): - """specialized test since checklists have more than one feature ious """ - default = Label(data=ImageData(uid="ckppihxc10005aeyjen11h7jh")) - prediction = next(NDJsonConverter.deserialize(pair.predictions), default) - label = next(LBV1Converter.deserialize([pair.labels])) - if mask: - for annotation in [*prediction.annotations, *label.annotations]: - if isinstance(annotation.value, Mask): - annotation.value.mask.arr = np.frombuffer( - base64.b64decode(annotation.value.mask.url.encode('utf-8')), - dtype=np.uint8).reshape((32, 32, 3)) - assert math.isclose(data_row_miou(label, prediction), - pair.data_row_expected) - assert math.isclose( - miou_metric(label.annotations, prediction.annotations)[0].value, - pair.data_row_expected) - feature_ious = feature_miou_metric(label.annotations, - prediction.annotations) - mapping = {} - for iou in feature_ious: - if not mapping.get(iou.value, None): - mapping[iou.value] = 0 - mapping[iou.value] += 1 - assert mapping == pair.expected - - -def strings_to_fixtures(strings): - return [fixture_ref(x) for x in strings] - - -def test_overlapping(polygon_pair, box_pair, mask_pair): - check_iou(polygon_pair) - check_iou(box_pair) - check_iou(mask_pair, True) - - -@parametrize("pair", - strings_to_fixtures([ - "unmatched_label", - "unmatched_prediction", - ])) -def test_unmatched(pair): - check_iou(pair) - - -@parametrize( - "pair", - strings_to_fixtures([ - "empty_radio_label", - "matching_radio", - "empty_radio_prediction", - ])) -def test_radio(pair): - check_iou(pair) - - -@parametrize( - "pair", - strings_to_fixtures([ - "matching_checklist", - "partially_matching_checklist_1", - "partially_matching_checklist_2", - "partially_matching_checklist_3", - "empty_checklist_label", - "empty_checklist_prediction", - ])) -def test_checklist(pair): - check_iou_checklist(pair) - - -@parametrize("pair", strings_to_fixtures(["matching_text", - "not_matching_text"])) -def test_text(pair): - check_iou(pair) - - -@parametrize( - "pair", - strings_to_fixtures( - ["test_box_with_wrong_subclass", "test_box_with_subclass"])) -def test_vector_with_subclass(pair): - check_iou(pair) - - -@parametrize("pair", strings_to_fixtures(["point_pair", "line_pair"])) -def test_others(pair): - check_iou(pair) - - -@parametrize( - "pair", - strings_to_fixtures( - ["matching_ner", "no_matching_ner", "partial_matching_ner"])) -def test_ner(pair): - check_iou(pair)