Skip to content

Commit 0e0aff9

Browse files
Vaghinak BasentsyanVaghinak Basentsyan
authored andcommitted
Merge remote-tracking branch 'origin/validations' into createion_type
# Conflicts: # src/superannotate/lib/core/entities/utils.py
2 parents 2283fab + 71207e0 commit 0e0aff9

File tree

9 files changed

+61
-42
lines changed

9 files changed

+61
-42
lines changed

src/superannotate/lib/app/annotation_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def add_annotation_bbox_to_json(
7474
annotation_class_name,
7575
annotation_class_attributes=None,
7676
error=None,
77-
image_name: str = ""
77+
image_name: str = "",
7878
):
7979
"""Add a bounding box annotation to SuperAnnotate format annotation JSON
8080

src/superannotate/lib/app/interface/sdk_interface.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2476,6 +2476,7 @@ def upload_annotations_from_folder_to_project(
24762476
folder_name=folder_name,
24772477
annotation_paths=annotation_paths, # noqa: E203
24782478
client_s3_bucket=from_s3_bucket,
2479+
folder_path=folder_path,
24792480
)
24802481
if response.errors:
24812482
raise AppException(response.errors)
@@ -3069,7 +3070,12 @@ def add_annotation_bbox_to_image(
30693070
"""
30703071
annotations = get_image_annotations(project, image_name)["annotation_json"]
30713072
annotations = add_annotation_bbox_to_json(
3072-
annotations, bbox, annotation_class_name, annotation_class_attributes, error, image_name
3073+
annotations,
3074+
bbox,
3075+
annotation_class_name,
3076+
annotation_class_attributes,
3077+
error,
3078+
image_name,
30733079
)
30743080
upload_image_annotations(project, image_name, annotations, verbose=False)
30753081

src/superannotate/lib/core/entities/pixel.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,24 @@
44
from lib.core.entities.utils import BaseImageInstance
55
from lib.core.entities.utils import BaseModel
66
from lib.core.entities.utils import MetadataBase
7-
from lib.core.entities.utils import PixelColor
87
from lib.core.entities.utils import Tag
98
from pydantic import Field
9+
from pydantic import validator
10+
from pydantic.color import Color
11+
from pydantic.color import ColorType
1012

1113

1214
class PixelMetaData(MetadataBase):
1315
is_segmented: Optional[bool] = Field(None, alias="isSegmented")
1416

1517

1618
class PixelAnnotationPart(BaseModel):
17-
color: PixelColor
19+
color: ColorType
20+
21+
@validator("color")
22+
def validate_color(cls, v):
23+
color = Color(v)
24+
return color.as_hex()
1825

1926

2027
class PixelAnnotationInstance(BaseImageInstance):

src/superannotate/lib/core/entities/utils.py

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010
from pydantic import EmailStr
1111
from pydantic import Field
1212
from pydantic import validator
13-
from pydantic.color import Color
14-
from pydantic.color import ColorType
15-
from pydantic.datetime_parse import parse_datetime
1613
from pydantic.errors import EnumMemberError
1714

1815

@@ -37,17 +34,6 @@ class Config:
3734
}
3835

3936

40-
class StringDate(datetime):
41-
@classmethod
42-
def __get_validators__(cls):
43-
yield parse_datetime
44-
yield cls.validate
45-
46-
@classmethod
47-
def validate(cls, v: datetime):
48-
return f'{v.replace(tzinfo=None).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]}Z'
49-
50-
5137
class VectorAnnotationTypeEnum(str, Enum):
5238
BBOX = "bbox"
5339
ELLIPSE = "ellipse"
@@ -112,8 +98,8 @@ class BboxPoints(BaseModel):
11298

11399

114100
class TimedBaseModel(BaseModel):
115-
created_at: StringDate = Field(None, alias="createdAt")
116-
updated_at: StringDate = Field(None, alias="updatedAt")
101+
created_at: constr(regex=r'\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d{3})Z') = Field(None, alias="createdAt")
102+
updated_at: constr(regex=r'\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d{3})Z') = Field(None, alias="updatedAt")
117103

118104

119105
class UserAction(BaseModel):
@@ -193,12 +179,3 @@ class Metadata(MetadataBase):
193179
status: Optional[AnnotationStatusEnum]
194180
pinned: Optional[bool]
195181
is_predicted: Optional[bool] = Field(None, alias="isPredicted")
196-
197-
198-
class PixelColor(BaseModel):
199-
__root__: ColorType
200-
201-
@validator("__root__")
202-
def validate_color(cls, v):
203-
color = Color(v)
204-
return color.as_hex()

src/superannotate/lib/core/usecases/annotations.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def __init__(
4949
validators: BaseAnnotationValidator,
5050
pre_annotation: bool = False,
5151
client_s3_bucket=None,
52+
folder_path: str = None,
5253
):
5354
super().__init__(reporter)
5455
self._project = project
@@ -66,6 +67,7 @@ def __init__(
6667
self.missing_attribute_groups = set()
6768
self.missing_classes = set()
6869
self.missing_attributes = set()
70+
self._folder_path = folder_path
6971

7072
@property
7173
def annotation_postfix(self):
@@ -173,7 +175,6 @@ def _upload_annotation(
173175
validators=self._validators,
174176
).execute()
175177
if response.errors:
176-
self.reporter.store_message("Invalid jsons", path)
177178
return path, False
178179
return path, True
179180
except Exception as _:
@@ -193,14 +194,23 @@ def get_bucket_to_upload(self, ids: List[int]):
193194

194195
def _log_report(self):
195196
for key, values in self.reporter.custom_messages.items():
196-
template = key + ": {}"
197-
if key == "missing_classes":
198-
template = "Could not find annotation classes matching existing classes on the platform: [{}]"
199-
elif key == "missing_attribute_groups":
200-
template = "Could not find attribute groups matching existing attribute groups on the platform: [{}]"
201-
elif key == "missing_attributes":
202-
template = "Could not find attributes matching existing attributes on the platform: [{}]"
203-
logger.warning(template.format("', '".join(values)))
197+
if key in [
198+
"missing_classes",
199+
"missing_attribute_groups",
200+
"missing_attributes",
201+
]:
202+
template = key + ": {}"
203+
if key == "missing_classes":
204+
template = "Could not find annotation classes matching existing classes on the platform: [{}]"
205+
elif key == "missing_attribute_groups":
206+
template = "Could not find attribute groups matching existing attribute groups on the platform: [{}]"
207+
elif key == "missing_attributes":
208+
template = "Could not find attributes matching existing attributes on the platform: [{}]"
209+
logger.warning(template.format("', '".join(values)))
210+
if self.reporter.custom_messages.get("invalid_jsons"):
211+
logger.warning(
212+
f"Couldn't validate {len(self.reporter.custom_messages['invalid_jsons'])}/{len(self._annotations_to_upload + self._missing_annotations)} annotations from {self._folder_path}."
213+
)
204214

205215
def execute(self):
206216
uploaded_annotations = []
@@ -457,4 +467,5 @@ def execute(self):
457467
)
458468
else:
459469
self._response.errors = "Invalid json"
470+
self.reporter.store_message("invalid_jsons", self._annotation_path)
460471
return self._response

src/superannotate/lib/infrastructure/controller.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,6 +1323,7 @@ def upload_annotations_from_folder(
13231323
annotation_paths: List[str],
13241324
client_s3_bucket=None,
13251325
is_pre_annotations: bool = False,
1326+
folder_path: str = None,
13261327
):
13271328
project = self._get_project(project_name)
13281329
folder = self._get_folder(project, folder_name)
@@ -1342,6 +1343,7 @@ def upload_annotations_from_folder(
13421343
),
13431344
validators=self.annotation_validators,
13441345
reporter=Reporter(log_info=False, log_warning=False),
1346+
folder_path=folder_path,
13451347
)
13461348
return use_case.execute()
13471349

tests/integration/annotations/test_text_annotation_upload.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def test_document_annotation_upload_invalid_json(self):
5252
self.assertEqual(len(uploaded_annotations), 0)
5353
self.assertEqual(len(failed_annotations), 1)
5454
self.assertEqual(len(missing_annotations), 0)
55-
self.assertIn("Invalid json", self._caplog.text)
55+
self.assertIn("Couldn't validate 1/1 annotations", self._caplog.text)
5656

5757
def test_text_annotation_upload(self):
5858
sa.create_annotation_classes_from_classes_json(self.PROJECT_NAME, self.classes_path)

tests/integration/annotations/test_video_annotation_upload.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def test_video_annotation_upload_invalid_json(self):
5959
self.assertEqual(len(uploaded_annotations), 0)
6060
self.assertEqual(len(failed_annotations), 1)
6161
self.assertEqual(len(missing_annotations), 0)
62-
self.assertIn("Invalid json", self._caplog.text)
62+
self.assertIn("Couldn't validate ", self._caplog.text)
6363

6464
def test_video_annotation_upload(self):
6565
sa.create_annotation_classes_from_classes_json(self.PROJECT_NAME, self.classes_path)

tests/unit/test_validators.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import os
33
from os.path import dirname
44
import tempfile
5-
65
import src.superannotate as sa
76
from tests.utils.helpers import catch_prints
8-
7+
from src.superannotate.lib.core.entities.utils import TimedBaseModel
8+
from src.superannotate.lib.core.entities.pixel import PixelAnnotationPart
9+
from pydantic import ValidationError
910
from unittest import TestCase
1011

1112
VECTOR_ANNOTATION_JSON_WITH_BBOX = """
@@ -87,6 +88,21 @@ def test_validate_annotation_without_metadata(self):
8788
sa.validate_annotations("Vector", os.path.join(self.vector_folder_path, f"{tmpdir_name}/vector.json"))
8889
self.assertIn("metadatafieldrequired", out.getvalue().strip().replace(" ", ""))
8990

91+
def test_validate_annotation_invalid_date_time_format(self):
92+
with self.assertRaises(ValidationError):
93+
TimedBaseModel(createdAt="2021-11-02T15:11:50.065000Z")
94+
95+
def test_validate_annotation_valid_date_time_format(self):
96+
self.assertEqual(TimedBaseModel(createdAt="2021-11-02T15:11:50.065Z").created_at, "2021-11-02T15:11:50.065Z")
97+
98+
def test_validate_annotation_invalid_color_format(self):
99+
with self.assertRaisesRegexp(ValidationError, "1 validation error for PixelAnnotationPart"):
100+
PixelAnnotationPart(color="fd435eraewf4rewf")
101+
102+
103+
def test_validate_annotation_valid_color_format(self):
104+
self.assertEqual(PixelAnnotationPart(color="#f1f2f3").color, "#f1f2f3")
105+
90106

91107
class TestTypeHandling(TestCase):
92108
ANNOTATION = """

0 commit comments

Comments
 (0)