Skip to content

Commit df0879c

Browse files
authored
Merge pull request #419 from superannotateai/friday
Friday
2 parents 8c4cf24 + 968c31e commit df0879c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1528
-611
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ ________
3838
.. autofunction:: superannotate.attach_image_urls_to_project
3939
.. autofunction:: superannotate.upload_images_from_public_urls_to_project
4040
.. autofunction:: superannotate.attach_document_urls_to_project
41+
.. autofunction:: superannotate.attach_items_from_integrated_storage
4142
.. autofunction:: superannotate.upload_image_to_project
4243
.. autofunction:: superannotate.delete_annotations
4344
.. _ref_upload_images_from_folder_to_project:
@@ -92,6 +93,7 @@ ______
9293
.. autofunction:: superannotate.add_annotation_bbox_to_image
9394
.. autofunction:: superannotate.add_annotation_point_to_image
9495
.. autofunction:: superannotate.add_annotation_comment_to_image
96+
.. autofunction:: superannotate.upload_priority_scores
9597

9698
----------
9799

@@ -107,10 +109,11 @@ __________________
107109

108110
----------
109111

110-
Team contributors
112+
Team
111113
_________________
112114

113115
.. autofunction:: superannotate.get_team_metadata
116+
.. autofunction:: superannotate.get_integrations
114117
.. autofunction:: superannotate.invite_contributors_to_team
115118
.. autofunction:: superannotate.search_team_contributors
116119

requirements_dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
superannotate_schemas>=1.0.38.b1
1+
superannotate_schemas>=1.0.40b3

src/superannotate/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
attach_document_urls_to_project,
2626
)
2727
from superannotate.lib.app.interface.sdk_interface import attach_image_urls_to_project
28+
from superannotate.lib.app.interface.sdk_interface import attach_items_from_integrated_storage
2829
from superannotate.lib.app.interface.sdk_interface import attach_video_urls_to_project
2930
from superannotate.lib.app.interface.sdk_interface import benchmark
3031
from superannotate.lib.app.interface.sdk_interface import clone_project
@@ -56,6 +57,7 @@
5657
from superannotate.lib.app.interface.sdk_interface import get_folder_metadata
5758
from superannotate.lib.app.interface.sdk_interface import get_image_annotations
5859
from superannotate.lib.app.interface.sdk_interface import get_image_metadata
60+
from superannotate.lib.app.interface.sdk_interface import get_integrations
5961
from superannotate.lib.app.interface.sdk_interface import (
6062
get_project_and_folder_metadata,
6163
)
@@ -103,6 +105,9 @@
103105
from superannotate.lib.app.interface.sdk_interface import (
104106
upload_preannotations_from_folder_to_project,
105107
)
108+
from superannotate.lib.app.interface.sdk_interface import (
109+
upload_priority_scores,
110+
)
106111
from superannotate.lib.app.interface.sdk_interface import upload_video_to_project
107112
from superannotate.lib.app.interface.sdk_interface import (
108113
upload_videos_from_folder_to_project,
@@ -132,6 +137,9 @@
132137
# annotations
133138
"get_annotations",
134139
"get_annotations_per_frame",
140+
# integrations
141+
"get_integrations",
142+
"attach_items_from_integrated_storage",
135143
# converters
136144
"convert_json_version",
137145
"import_annotation",
@@ -151,6 +159,7 @@
151159
"clone_project",
152160
"share_project",
153161
"delete_project",
162+
"upload_priority_scores",
154163
# Images Section
155164
"search_images",
156165
"copy_image",

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

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@
3434
from lib.app.serializers import SettingsSerializer
3535
from lib.app.serializers import TeamSerializer
3636
from lib.core import LIMITED_FUNCTIONS
37+
from lib.core.entities.integrations import IntegrationEntity
3738
from lib.core.entities.project_entities import AnnotationClassEntity
3839
from lib.core.enums import ImageQuality
3940
from lib.core.exceptions import AppException
4041
from lib.core.types import AttributeGroup
4142
from lib.core.types import MLModel
43+
from lib.core.types import PriorityScore
4244
from lib.core.types import Project
4345
from lib.infrastructure.controller import Controller
4446
from pydantic import conlist
@@ -189,7 +191,7 @@ def create_project_from_metadata(project_metadata: Project):
189191
project_metadata = project_metadata.dict()
190192
response = Controller.get_default().create_project(
191193
name=project_metadata["name"],
192-
description=project_metadata["description"],
194+
description=project_metadata.get("description"),
193195
project_type=project_metadata["type"],
194196
settings=project_metadata.get("settings", []),
195197
annotation_classes=project_metadata.get("classes", []),
@@ -1572,6 +1574,8 @@ def create_annotation_class(
15721574
attribute_groups=attribute_groups,
15731575
class_type=class_type,
15741576
)
1577+
if response.errors:
1578+
raise AppException(response.errors)
15751579
return BaseSerializers(response.data).serialize()
15761580

15771581

@@ -2900,3 +2904,71 @@ def get_annotations_per_frame(project: NotEmptyStr, video: NotEmptyStr, fps: int
29002904
if response.errors:
29012905
raise AppException(response.errors)
29022906
return response.data
2907+
2908+
2909+
@Trackable
2910+
@validate_arguments
2911+
def upload_priority_scores(project: NotEmptyStr, scores: List[PriorityScore]):
2912+
"""Returns per frame annotations for the given video.
2913+
2914+
:param project: project name or folder path (e.g., “project1/folder1”)
2915+
:type project: str
2916+
2917+
:param scores: list of score objects
2918+
:type scores: list of dicts
2919+
2920+
:return: lists of uploaded, skipped items
2921+
:rtype: tuple (2 members) of lists of strs
2922+
"""
2923+
project_name, folder_name = extract_project_folder(project)
2924+
project_folder_name = project
2925+
response = Controller.get_default().upload_priority_scores(project_name, folder_name, scores, project_folder_name)
2926+
if response.errors:
2927+
raise AppException(response.errors)
2928+
return response.data
2929+
2930+
2931+
@Trackable
2932+
@validate_arguments
2933+
def get_integrations():
2934+
"""Get all integrations per team
2935+
2936+
:return: metadata objects of all integrations of the team.
2937+
:rtype: list of dicts
2938+
"""
2939+
response = Controller.get_default().get_integrations()
2940+
if response.errors:
2941+
raise AppException(response.errors)
2942+
integrations = response.data
2943+
return BaseSerializers.serialize_iterable(integrations, ("name", "type", "root"))
2944+
2945+
2946+
@Trackable
2947+
@validate_arguments
2948+
def attach_items_from_integrated_storage(
2949+
project: NotEmptyStr,
2950+
integration: Union[NotEmptyStr, IntegrationEntity],
2951+
folder_path: Optional[NotEmptyStr] = None
2952+
):
2953+
"""Link images from integrated external storage to SuperAnnotate.
2954+
2955+
:param project: project name or folder path where items should be attached (e.g., “project1/folder1”).
2956+
:type project: str
2957+
2958+
:param project: project name or folder path where items should be attached (e.g., “project1/folder1”).
2959+
:type project: str
2960+
2961+
:param integration: existing integration name or metadata dict to pull items from.
2962+
Mandatory keys in integration metadata’s dict is “name”.
2963+
:type integration: str or dict
2964+
2965+
:param folder_path: Points to an exact folder/directory within given storage.
2966+
If None, items are fetched from the root directory.
2967+
:type folder_path: str
2968+
"""
2969+
project_name, folder_name = extract_project_folder(project)
2970+
if isinstance(integration, str):
2971+
integration = IntegrationEntity(name=integration)
2972+
response = Controller.get_default().attach_integrations(project_name, folder_name, integration, folder_path)
2973+
if response.errors:
2974+
raise AppException(response.errors)

src/superannotate/lib/app/mixp/utils/parsers.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import lib.core as constances
22
from lib.app.helpers import extract_project_folder
3+
from lib.core.entities import IntegrationEntity
34
from lib.core.enums import ProjectType
45
from lib.infrastructure.controller import Controller
56

@@ -1230,3 +1231,37 @@ def get_annotations_per_frame(*args, **kwargs):
12301231
"event_name": "get_annotations_per_frame",
12311232
"properties": {"Project": project, "fps": fps},
12321233
}
1234+
1235+
1236+
def upload_priority_scores(*args, **kwargs):
1237+
scores = kwargs.get("scores", args[1])
1238+
return {
1239+
"event_name": "upload_priority_scores",
1240+
"properties": {"Score Count": len(scores)},
1241+
}
1242+
1243+
1244+
def get_integrations(*args, **kwargs):
1245+
return {
1246+
"event_name": "get_integrations",
1247+
"properties": {},
1248+
}
1249+
1250+
1251+
def attach_items_from_integrated_storage(*args, **kwargs):
1252+
project = kwargs.get("project", args[0])
1253+
integration = kwargs.get("integration", args[1])
1254+
folder_path = kwargs.get("folder_path", args[2])
1255+
1256+
project_name, _ = extract_project_folder(project)
1257+
if isinstance(integration, str):
1258+
integration = IntegrationEntity(name=integration)
1259+
project = Controller.get_default().get_project_metadata(project_name)
1260+
return {
1261+
"event_name": "attach_items_from_integrated_storage",
1262+
"properties": {
1263+
"project_type": ProjectType.get_name(project.project_type),
1264+
"integration_name": integration.name,
1265+
"folder_path": bool(folder_path)
1266+
},
1267+
}

src/superannotate/lib/app/serializers.py

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
from abc import ABC
2+
from typing import Any
3+
from typing import List
4+
from typing import Set
5+
from typing import Union
26

37
import superannotate.lib.core as constance
48
from pydantic import BaseModel
@@ -11,12 +15,36 @@ class BaseSerializers(ABC):
1115
def __init__(self, entity: BaseEntity):
1216
self._entity = entity
1317

14-
def serialize(self):
15-
if isinstance(self._entity, dict):
16-
return self._entity
17-
if isinstance(self._entity, BaseModel):
18-
return self._entity.dict(by_alias=True)
19-
return self._entity.to_dict()
18+
def serialize(self, fields: List[str] = None, by_alias: bool = True, flat: bool = False):
19+
return self._serialize(self._entity, fields, by_alias, flat)
20+
21+
@staticmethod
22+
def _serialize(entity: Any, fields: List[str] = None, by_alias: bool = False, flat: bool = False):
23+
if isinstance(entity, dict):
24+
return entity
25+
if isinstance(entity, BaseModel):
26+
if fields:
27+
fields = set(fields)
28+
if len(fields) == 1:
29+
if flat:
30+
return entity.dict(include=fields, by_alias=by_alias)[next(iter(fields))]
31+
return entity.dict(include=fields, by_alias=by_alias)
32+
return entity.dict(include=fields, by_alias=by_alias)
33+
return entity.dict(by_alias=by_alias)
34+
return entity.to_dict()
35+
36+
@classmethod
37+
def serialize_iterable(
38+
cls,
39+
data: List[Any],
40+
fields: Union[List[str], Set[str]] = None,
41+
by_alias: bool = False,
42+
flat: bool = False
43+
) -> List[Any]:
44+
serialized_data = []
45+
for i in data:
46+
serialized_data.append(cls._serialize(i, fields, by_alias, flat))
47+
return serialized_data
2048

2149

2250
class UserSerializer(BaseSerializers):
@@ -43,6 +71,10 @@ class ProjectSerializer(BaseSerializers):
4371
def serialize(self):
4472
data = super().serialize()
4573
data["type"] = constance.ProjectType.get_name(data["type"])
74+
if data.get("status"):
75+
data["status"] = constance.ProjectStatus.get_name(data["status"])
76+
else:
77+
data["status"] = "Undefined"
4678
if data.get("upload_state"):
4779
data["upload_state"] = constance.UploadState(data["upload_state"]).name
4880
if data.get("users"):
@@ -114,4 +146,3 @@ def serialize(self):
114146
if data["attribute"] == "ImageQuality":
115147
data["value"] = constance.ImageQuality.get_name(data["value"])
116148
return data
117-

src/superannotate/lib/core/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from superannotate.lib.core.enums import AnnotationStatus
44
from superannotate.lib.core.enums import ImageQuality
5+
from superannotate.lib.core.enums import ProjectStatus
56
from superannotate.lib.core.enums import ProjectType
67
from superannotate.lib.core.enums import SegmentationStatus
78
from superannotate.lib.core.enums import TrainingStatus
@@ -127,6 +128,7 @@
127128
INVALID_JSON_MESSAGE = "Invalid json"
128129

129130
__alL__ = (
131+
ProjectStatus,
130132
ProjectType,
131133
UserRole,
132134
UploadState,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from lib.core.entities.integrations import IntegrationEntity
12
from lib.core.entities.project_entities import AnnotationClassEntity
23
from lib.core.entities.project_entities import BaseEntity
34
from lib.core.entities.project_entities import ConfigEntity
@@ -33,6 +34,7 @@
3334
"UserEntity",
3435
"TeamEntity",
3536
"MLModelEntity",
37+
"IntegrationEntity",
3638
# annotations
3739
"DocumentAnnotation",
3840
"VideoAnnotation",
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from datetime import datetime
2+
3+
from pydantic import BaseModel
4+
from pydantic import Field
5+
6+
7+
class TimedBaseModel(BaseModel):
8+
created_at: datetime = Field(None, alias="createdAt")
9+
updated_at: datetime = Field(None, alias="updatedAt")
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from lib.core.entities.base import TimedBaseModel
2+
from pydantic import Field
3+
4+
5+
class IntegrationEntity(TimedBaseModel):
6+
id: int = None
7+
user_id: str = None
8+
name: str
9+
type: str = "aws"
10+
root: str = Field(None, alias="bucket_name")
11+
source: int = None
12+
13+
class Config:
14+
arbitrary_types_allowed = True

0 commit comments

Comments
 (0)