Skip to content

Commit 39d7677

Browse files
authored
Merge pull request #201 from superannotateai/FRIDAY-274
Friday 274
2 parents 1d273b9 + d5e2928 commit 39d7677

22 files changed

+7414
-45
lines changed

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
from lib.app.helpers import get_annotation_paths
3434
from lib.app.helpers import reformat_metrics_json
3535
from lib.app.interface.types import AnnotationType
36-
from lib.app.interface.types import ClassesJson
3736
from lib.app.interface.types import NotEmptyStr
3837
from lib.app.interface.types import Status
3938
from lib.app.interface.types import validate_arguments
@@ -46,9 +45,11 @@
4645
from lib.core.enums import ImageQuality
4746
from lib.core.exceptions import AppException
4847
from lib.core.exceptions import AppValidationException
48+
from lib.core.types import ClassesJson
4949
from lib.infrastructure.controller import Controller
5050
from plotly.subplots import make_subplots
5151
from pydantic import EmailStr
52+
from pydantic import parse_obj_as
5253
from pydantic import StrictBool
5354
from tqdm import tqdm
5455

@@ -1969,12 +1970,18 @@ def create_annotation_classes_from_classes_json(
19691970
from_s3_object = from_s3.Object(from_s3_bucket, classes_json)
19701971
from_s3_object.download_fileobj(file)
19711972
file.seek(0)
1972-
annotation_classes = json.load(file)
1973+
annotation_classes = parse_obj_as(List[ClassesJson], json.load(file))
19731974
else:
1974-
annotation_classes = json.load(open(classes_json))
1975+
annotation_classes = parse_obj_as(
1976+
List[ClassesJson], json.load(open(classes_json))
1977+
)
1978+
19751979
else:
19761980
annotation_classes = classes_json
19771981

1982+
annotation_classes = [
1983+
annotation_class.dict() for annotation_class in annotation_classes
1984+
]
19781985
response = controller.create_annotation_classes(
19791986
project_name=project, annotation_classes=annotation_classes,
19801987
)

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

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
from functools import wraps
2-
from typing import List
3-
from typing import Optional
42
from typing import Union
53

64
from lib.core.enums import AnnotationStatus
7-
from pydantic import BaseModel
85
from pydantic import constr
96
from pydantic import StrictStr
107
from pydantic import validate_arguments as pydantic_validate_arguments
118
from pydantic import ValidationError
129

13-
1410
NotEmptyStr = constr(strict=True, min_length=1)
1511

1612

@@ -36,17 +32,6 @@ def validate(cls, value: Union[str]) -> Union[str]:
3632
return value
3733

3834

39-
class AttributeGroup(BaseModel):
40-
name: StrictStr
41-
is_multiselect: Optional[bool]
42-
43-
44-
class ClassesJson(BaseModel):
45-
name: StrictStr
46-
color: StrictStr
47-
attribute_groups: List[AttributeGroup]
48-
49-
5035
def validate_arguments(func):
5136
@wraps(func)
5237
def wrapped(*args, **kwargs):
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from typing import List
2+
from typing import Optional
3+
from typing import Union
4+
5+
from pydantic import BaseModel
6+
from pydantic import constr
7+
from pydantic import StrictStr
8+
9+
10+
NotEmptyStr = constr(strict=True, min_length=1)
11+
12+
13+
class Attribute(BaseModel):
14+
name: NotEmptyStr
15+
16+
17+
class AttributeGroup(BaseModel):
18+
name: StrictStr
19+
attributes: List[Attribute]
20+
21+
22+
class ClassesJson(BaseModel):
23+
name: StrictStr
24+
color: StrictStr
25+
attribute_groups: List[AttributeGroup]
26+
27+
28+
class Metadata(BaseModel):
29+
name: Optional[NotEmptyStr]
30+
31+
32+
class BaseInstance(BaseModel):
33+
type: NotEmptyStr
34+
classId: int
35+
groupId: Optional[int]
36+
attributes: List[Attribute]
37+
38+
39+
class Point(BaseInstance):
40+
x: float
41+
y: float
42+
43+
44+
class PolyLine(BaseInstance):
45+
points: List[float]
46+
47+
48+
class Polygon(BaseInstance):
49+
points: List[float]
50+
51+
52+
class BboxPoints(BaseModel):
53+
x1: float
54+
x2: float
55+
y1: float
56+
y2: float
57+
58+
59+
class Bbox(BaseInstance):
60+
points: BboxPoints
61+
62+
63+
class Ellipse(BaseInstance):
64+
cx: float
65+
cy: float
66+
rx: float
67+
ry: float
68+
69+
70+
class TemplatePoint(BaseModel):
71+
id: int
72+
x: float
73+
y: float
74+
75+
76+
class TemplateConnection(BaseModel):
77+
id: int
78+
to: int
79+
80+
81+
class Template(BaseInstance):
82+
points: List[TemplatePoint]
83+
connections: List[Optional[TemplateConnection]]
84+
templateId: int
85+
86+
87+
class EmptyPoint(BaseModel):
88+
x: float
89+
y: float
90+
91+
92+
class CuboidPoint(BaseModel):
93+
f1: EmptyPoint
94+
f2: EmptyPoint
95+
r1: EmptyPoint
96+
r2: EmptyPoint
97+
98+
99+
class Cuboid(BaseInstance):
100+
points: CuboidPoint
101+
102+
103+
class VectorAnnotation(BaseModel):
104+
metadata: Metadata
105+
instances: List[Union[Template, Cuboid, Point, PolyLine, Polygon, Bbox, Ellipse]]
106+
107+
108+
class PixelAnnotationPart(BaseModel):
109+
color: NotEmptyStr
110+
111+
112+
class PixelAnnotationInstance(BaseModel):
113+
classId: int
114+
groupId: int
115+
parts: List[PixelAnnotationPart]
116+
attributes: List[Attribute]
117+
118+
119+
class PixelAnnotation(BaseModel):
120+
metadata: Metadata
121+
instances: List[PixelAnnotationInstance]

src/superannotate/lib/core/usecases.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@
5151
from lib.core.repositories import BaseReadOnlyRepository
5252
from lib.core.response import Response
5353
from lib.core.serviceproviders import SuerannotateServiceProvider
54+
from lib.core.types import PixelAnnotation
55+
from lib.core.types import VectorAnnotation
5456
from PIL import UnidentifiedImageError
57+
from pydantic import ValidationError
5558

5659
logger = logging.getLogger("root")
5760

@@ -3448,13 +3451,23 @@ def annotations_to_upload(self):
34483451
self._annotations_to_upload = annotations_to_upload
34493452
return self._annotations_to_upload
34503453

3454+
def _is_valid_json(self, json_data: dict):
3455+
try:
3456+
if self._project.project_type == constances.ProjectType.PIXEL.value:
3457+
PixelAnnotation(**json_data)
3458+
else:
3459+
VectorAnnotation(**json_data)
3460+
return True
3461+
except ValidationError as _:
3462+
return False
3463+
34513464
def execute(self):
34523465
uploaded_annotations = []
34533466
missing_annotations = []
34543467
failed_annotations = []
34553468
for _ in range(0, len(self.annotations_to_upload), self.AUTH_DATA_CHUNK_SIZE):
34563469
annotations_to_upload = self.annotations_to_upload[
3457-
_ : _ + self.CHUNK_SIZE # noqa: E203
3470+
_ : _ + self.AUTH_DATA_CHUNK_SIZE # noqa: E203
34583471
]
34593472

34603473
if self._pre_annotation:
@@ -3504,22 +3517,21 @@ def execute(self):
35043517
for image_id, image_info in response.data.images.items()
35053518
]
35063519
for future in concurrent.futures.as_completed(results):
3507-
future.result()
3520+
annotation, uploaded = future.result()
3521+
if uploaded:
3522+
uploaded_annotations.append(annotation)
3523+
else:
3524+
failed_annotations.append(annotation)
35083525
yield
35093526

3510-
uploaded_annotations.extend(
3511-
[annotation.path for annotation in self.annotations_to_upload]
3512-
)
3527+
uploaded_annotations = [annotation.path for annotation in uploaded_annotations]
35133528
missing_annotations.extend(
35143529
[annotation.path for annotation in self._missing_annotations]
35153530
)
3516-
failed_annotations.extend(
3517-
[
3518-
annotation
3519-
for annotation in self._annotation_paths
3520-
if annotation not in uploaded_annotations + missing_annotations
3521-
]
3522-
)
3531+
failed_annotations = [
3532+
annotation.path for annotation in failed_annotations
3533+
]
3534+
35233535
self._response.data = (
35243536
uploaded_annotations,
35253537
failed_annotations,
@@ -3542,6 +3554,9 @@ def upload_to_s3(
35423554
annotation_json = json.load(open(image_id_name_map[image_id].path))
35433555

35443556
self.fill_classes_data(annotation_json)
3557+
3558+
if not self._is_valid_json(annotation_json):
3559+
return image_id_name_map[image_id], False
35453560
bucket.put_object(
35463561
Key=image_info["annotation_json_path"], Body=json.dumps(annotation_json),
35473562
)
@@ -3561,6 +3576,7 @@ def upload_to_s3(
35613576
file = io.BytesIO(mask_file.read())
35623577

35633578
bucket.put_object(Key=image_info["annotation_bluemap_path"], Body=file)
3579+
return image_id_name_map[image_id], True
35643580

35653581

35663582
class CreateModelUseCase(BaseUseCase):

0 commit comments

Comments
 (0)