Skip to content

Commit e1add0e

Browse files
authored
Merge pull request #400 from superannotateai/friday
Friday
2 parents 4846785 + 83e98d1 commit e1add0e

File tree

15 files changed

+299
-3180
lines changed

15 files changed

+299
-3180
lines changed

src/superannotate/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
from superannotate.lib.app.interface.sdk_interface import download_image_annotations
5252
from superannotate.lib.app.interface.sdk_interface import download_model
5353
from superannotate.lib.app.interface.sdk_interface import get_annotations
54+
from superannotate.lib.app.interface.sdk_interface import get_annotations_per_frame
5455
from superannotate.lib.app.interface.sdk_interface import get_exports
5556
from superannotate.lib.app.interface.sdk_interface import get_folder_metadata
5657
from superannotate.lib.app.interface.sdk_interface import get_image_annotations
@@ -130,6 +131,7 @@
130131
"get_exports",
131132
# annotations
132133
"get_annotations",
134+
"get_annotations_per_frame",
133135
# converters
134136
"convert_json_version",
135137
"import_annotation",

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1561,7 +1561,7 @@ def create_annotation_class(
15611561
attribute_groups=attribute_groups,
15621562
class_type=type,
15631563
)
1564-
return response.data.dict()
1564+
return BaseSerializers(response.data).serialize()
15651565

15661566

15671567
@Trackable

src/superannotate/lib/core/data_handlers.py

Lines changed: 19 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from typing import Dict
99
from typing import List
1010

11-
import lib.core as constances
12-
from lib.core.enums import ClassTypeEnum
13-
from lib.core.reporter import Reporter
1411
from superannotate_schemas.schemas.classes import AnnotationClass
1512
from superannotate_schemas.schemas.classes import Attribute
1613
from superannotate_schemas.schemas.classes import AttributeGroup
1714

15+
import lib.core as constances
16+
from lib.core.enums import ClassTypeEnum
17+
from lib.core.reporter import Reporter
18+
1819

1920
class BaseDataHandler(metaclass=ABCMeta):
2021
@abstractmethod
@@ -40,17 +41,15 @@ def __init__(self, annotation_classes: List[AnnotationClass]):
4041

4142
@lru_cache()
4243
def get_annotation_class(
43-
self, name: str, class_type: ClassTypeEnum
44+
self, name: str
4445
) -> AnnotationClass:
4546
for annotation_class in self._annotation_classes:
46-
if annotation_class.name == name and class_type.equals(
47-
annotation_class.type
48-
):
47+
if annotation_class.name == name:
4948
return annotation_class
5049

5150
@lru_cache()
5251
def get_attribute_group(
53-
self, annotation_class: AnnotationClass, attr_group_name: str
52+
self, annotation_class: AnnotationClass, attr_group_name: str
5453
) -> AttributeGroup:
5554
for attr_group in annotation_class.attribute_groups:
5655
if attr_group.name == attr_group_name:
@@ -108,9 +107,7 @@ class DocumentTagHandler(BaseAnnotationDateHandler):
108107
def handle(self, annotation: dict):
109108
new_tags = []
110109
for tag in annotation["tags"]:
111-
annotation_class = self.get_annotation_class(
112-
tag, class_type=ClassTypeEnum.OBJECT
113-
)
110+
annotation_class = self.get_annotation_class(tag)
114111
if annotation_class:
115112
new_tags.append(annotation_class.id)
116113
annotation["tags"] = new_tags
@@ -119,10 +116,10 @@ def handle(self, annotation: dict):
119116

120117
class MissingIDsHandler(BaseAnnotationDateHandler):
121118
def __init__(
122-
self,
123-
annotation_classes: List[AnnotationClass],
124-
templates: List[dict],
125-
reporter: Reporter,
119+
self,
120+
annotation_classes: List[AnnotationClass],
121+
templates: List[dict],
122+
reporter: Reporter,
126123
):
127124
super().__init__(annotation_classes)
128125
self.validate_existing_classes(annotation_classes)
@@ -175,9 +172,7 @@ def handle(self, annotation: dict):
175172
annotation_instance["classId"] = -1
176173
else:
177174
class_name = annotation_instance["className"]
178-
annotation_type = annotation_instance.get("type", ClassTypeEnum.OBJECT)
179-
class_type = self._get_class_type(annotation_type)
180-
annotation_class = self.get_annotation_class(class_name, class_type)
175+
annotation_class = self.get_annotation_class(class_name)
181176
if not annotation_class:
182177
self.reporter.log_warning(f"Couldn't find class {class_name}")
183178
self.reporter.store_message("missing_classes", class_name)
@@ -199,7 +194,7 @@ def handle(self, annotation: dict):
199194
template["name"]: template["id"] for template in self._templates
200195
}
201196
for annotation_instance in (
202-
i for i in annotation["instances"] if i.get("type", None) == "template"
197+
i for i in annotation["instances"] if i.get("type", None) == "template"
203198
):
204199
annotation_instance["templateId"] = template_name_id_map.get(
205200
annotation_instance.get("templateName", ""), -1
@@ -209,13 +204,8 @@ def handle(self, annotation: dict):
209204
i for i in annotation["instances"] if "className" in i and i["classId"] > 0
210205
]:
211206
annotation_class_name = annotation_instance["className"]
212-
annotation_class_type = self._get_class_type(
213-
annotation_instance.get("type", ClassTypeEnum.OBJECT)
214-
)
207+
annotation_class = self.get_annotation_class(annotation_class_name)
215208

216-
annotation_class = self.get_annotation_class(
217-
annotation_class_name, annotation_class_type
218-
)
219209
if not annotation_class:
220210
self.reporter.log_warning(
221211
f"Couldn't find annotation class {annotation_class_name}"
@@ -291,9 +281,7 @@ def convert_timestamp(timestamp):
291281
"locked": False,
292282
}
293283
if class_name:
294-
annotation_class = self.get_annotation_class(
295-
class_name, ClassTypeEnum.OBJECT
296-
)
284+
annotation_class = self.get_annotation_class(class_name)
297285
if annotation_class:
298286
editor_instance["classId"] = annotation_class.id
299287
else:
@@ -328,9 +316,7 @@ def convert_timestamp(timestamp):
328316
] = timestamp_data["points"]
329317
if not class_name:
330318
continue
331-
annotation_class = self.get_annotation_class(
332-
class_name, ClassTypeEnum.OBJECT
333-
)
319+
annotation_class = self.get_annotation_class(class_name)
334320
if not annotation_class:
335321
self.reporter.store_message(
336322
"missing_classes", meta["className"]
@@ -365,10 +351,10 @@ def convert_timestamp(timestamp):
365351
(group_name, attr_name)
366352
)
367353
attributes_to_add = (
368-
existing_attributes_in_current_instance - active_attributes
354+
existing_attributes_in_current_instance - active_attributes
369355
)
370356
attributes_to_delete = (
371-
active_attributes - existing_attributes_in_current_instance
357+
active_attributes - existing_attributes_in_current_instance
372358
)
373359
if attributes_to_add or attributes_to_delete:
374360
editor_instance["timeline"][timestamp][

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,9 @@ def execute(self):
554554
return self._response
555555
if not response.data:
556556
self._response.errors = AppException(f"Video {self._video_name} not found.")
557-
558-
self._response.data = VideoFrameGenerator(response.data[1], fps=self._fps)
557+
annotations = response.data
558+
if annotations:
559+
self._response.data = list(VideoFrameGenerator(response.data[0], fps=self._fps))
560+
else:
561+
self._response.data = []
559562
return self._response

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

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2710,12 +2710,12 @@ def __init__(
27102710
self,
27112711
annotation_classes: BaseManageableRepository,
27122712
annotation_class: AnnotationClassEntity,
2713-
project_name: str,
2713+
project: ProjectEntity,
27142714
):
27152715
super().__init__()
27162716
self._annotation_classes = annotation_classes
27172717
self._annotation_class = annotation_class
2718-
self._project_name = project_name
2718+
self._project = project
27192719

27202720
def validate_uniqueness(self):
27212721
annotation_classes = self._annotation_classes.get_all(
@@ -2730,11 +2730,20 @@ def validate_uniqueness(self):
27302730
):
27312731
raise AppValidationException("Annotation class already exits.")
27322732

2733+
def validate_project_type(self):
2734+
if (
2735+
self._project.project_type != ProjectType.VECTOR.value
2736+
and self._annotation_class.type == "tag"
2737+
):
2738+
raise AppException(
2739+
f"Predefined tagging functionality is not supported for projects of type {ProjectType.get_name(self._project.project_type)}."
2740+
)
2741+
27332742
def execute(self):
27342743
if self.is_valid():
27352744
logger.info(
27362745
"Creating annotation class in project %s with name %s",
2737-
self._project_name,
2746+
self._project.name,
27382747
self._annotation_class.name,
27392748
)
27402749
created = self._annotation_classes.insert(entity=self._annotation_class)
@@ -2813,7 +2822,7 @@ def execute(self):
28132822
str(self._download_path),
28142823
)
28152824
classes = self._annotation_classes_repo.get_all()
2816-
classes = [entity.dict() for entity in classes]
2825+
classes = [entity.dict(by_alias=True) for entity in classes]
28172826
json_path = f"{self._download_path}/classes.json"
28182827
json.dump(classes, open(json_path, "w"), indent=4)
28192828
self._response.data = json_path
@@ -2836,31 +2845,34 @@ def __init__(
28362845
self._annotation_classes = annotation_classes
28372846
self._project = project
28382847

2839-
def validate_annotation_classes(self):
2840-
if "attribute_groups" not in self._annotation_classes:
2841-
raise AppValidationException("Field attribute_groups is required.")
2848+
def validate_project_type(self):
2849+
if self._project.project_type != ProjectType.VECTOR.value and "tag" in [
2850+
i.type for i in self._annotation_classes
2851+
]:
2852+
raise AppException(
2853+
f"Predefined tagging functionality is not supported for projects of type {ProjectType.get_name(self._project.project_type)}."
2854+
)
28422855

28432856
def execute(self):
2844-
existing_annotation_classes = self._annotation_classes_repo.get_all()
2845-
existing_classes_name = [i.name for i in existing_annotation_classes]
2846-
unique_annotation_classes = []
2847-
for annotation_class in self._annotation_classes:
2848-
if annotation_class.name in existing_classes_name:
2849-
logger.warning(
2850-
"Annotation class %s already in project. Skipping.",
2851-
annotation_class.name,
2857+
if self.is_valid():
2858+
existing_annotation_classes = self._annotation_classes_repo.get_all()
2859+
existing_classes_name = [i.name for i in existing_annotation_classes]
2860+
unique_annotation_classes = []
2861+
for annotation_class in self._annotation_classes:
2862+
if annotation_class.name in existing_classes_name:
2863+
logger.warning(
2864+
"Annotation class %s already in project. Skipping.",
2865+
annotation_class.name,
2866+
)
2867+
continue
2868+
else:
2869+
unique_annotation_classes.append(annotation_class)
2870+
created = []
2871+
for i in range(len(unique_annotation_classes) - self.CHUNK_SIZE, 0, self.CHUNK_SIZE):
2872+
created += self._annotation_classes_repo.bulk_insert(
2873+
entities=unique_annotation_classes[i : i + self.CHUNK_SIZE], # noqa: E203
28522874
)
2853-
continue
2854-
else:
2855-
unique_annotation_classes.append(annotation_class)
2856-
2857-
created = []
2858-
2859-
for i in range(0, len(unique_annotation_classes), self.CHUNK_SIZE):
2860-
created += self._annotation_classes_repo.bulk_insert(
2861-
entities=unique_annotation_classes[i : i + self.CHUNK_SIZE],
2862-
)
2863-
self._response.data = created
2875+
self._response.data = created
28642876
return self._response
28652877

28662878

src/superannotate/lib/core/video_convertor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,5 +141,5 @@ def _process(self):
141141
)
142142

143143
def __iter__(self):
144-
for frame_no in range(1, int(self._frames_count)):
144+
for frame_no in range(1, int(self._frames_count) + 1):
145145
yield self.get_frame(frame_no).dict()

src/superannotate/lib/infrastructure/controller.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ def __init__(self, config_path: str = None, token: str = None):
5555
self._user_id = None
5656
self._team_name = None
5757
self._reporter = None
58-
self._ssl_verify = not (os.environ.get("SA_TESTING", "False").lower() == "false")
59-
58+
self._testing = os.getenv("SA_TESTING", 'False').lower() in ('true', '1', 't')
59+
self._ssl_verify = not self._testing
6060
self._backend_url = os.environ.get("SA_URL", constances.BACKEND_URL)
6161

6262
if token:
@@ -114,6 +114,7 @@ def initialize_backend_client(self):
114114
auth_token=self._token,
115115
logger=self._logger,
116116
verify_ssl=self._ssl_verify,
117+
testing=self._testing
117118
)
118119
self._backend_client.get_session.cache_clear()
119120
return self._backend_client
@@ -1142,7 +1143,7 @@ def create_annotation_class(
11421143
use_case = usecases.CreateAnnotationClassUseCase(
11431144
annotation_classes=annotation_classes,
11441145
annotation_class=annotation_class,
1145-
project_name=project_name,
1146+
project=project,
11461147
)
11471148
use_case.execute()
11481149
return use_case.execute()

src/superannotate/lib/infrastructure/services.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,23 @@ class BaseBackendService(SuerannotateServiceProvider):
4343
"""
4444

4545
def __init__(
46-
self, api_url: str, auth_token: str, logger, paginate_by=None, verify_ssl=False
46+
self, api_url: str, auth_token: str, logger, paginate_by=None, verify_ssl=False, testing: bool = False
4747
):
4848
self.api_url = api_url
4949
self._auth_token = auth_token
5050
self.logger = logger
5151
self._paginate_by = paginate_by
52-
self._verify_ssl = False # TODO fix False
52+
self._verify_ssl = verify_ssl
5353
self.team_id = auth_token.split("=")[-1]
54+
self._testing = testing
5455
self.get_session()
5556

57+
@property
58+
def assets_provider_url(self):
59+
if self._testing:
60+
return "https://assets-provider.devsuperannotate.com/api/v1/"
61+
return "https://assets-provider.superannotate.com/api/v1/"
62+
5663
@timed_lru_cache(seconds=360)
5764
def get_session(self):
5865
session = requests.Session()
@@ -166,7 +173,6 @@ class SuperannotateBackendService(BaseBackendService):
166173
Manage projects, images and team in the Superannotate
167174
"""
168175
DEFAULT_CHUNK_SIZE = 1000
169-
STREAMED_DATA_PROVIDER_URL = "https://assets-provider.devsuperannotate.com"
170176

171177
URL_USERS = "users"
172178
URL_LIST_PROJECTS = "projects"
@@ -215,7 +221,7 @@ class SuperannotateBackendService(BaseBackendService):
215221
URL_DELETE_ANNOTATIONS = "annotations/remove"
216222
URL_DELETE_ANNOTATIONS_PROGRESS = "annotations/getRemoveStatus"
217223
URL_GET_LIMITS = "project/{}/limitationDetails"
218-
URL_GET_ANNOTATIONS = "api/v1/images/annotations/stream"
224+
URL_GET_ANNOTATIONS = "images/annotations/stream"
219225

220226
def get_project(self, uuid: int, team_id: int):
221227
get_project_url = urljoin(self.api_url, self.URL_GET_PROJECT.format(uuid))
@@ -1011,7 +1017,7 @@ def get_limitations(
10111017
)
10121018

10131019
def get_annotations(self, project_id: int, team_id: int, folder_id: int, items: List[str]) -> List[dict]:
1014-
get_limits_url = urljoin(self.STREAMED_DATA_PROVIDER_URL, self.URL_GET_ANNOTATIONS)
1020+
get_limits_url = urljoin(self.assets_provider_url, self.URL_GET_ANNOTATIONS)
10151021
query_params = {
10161022
"team_id": team_id,
10171023
"project_id": project_id,

src/superannotate/lib/infrastructure/stream_data_handler.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,22 @@ def map_image_names_to_fetch_streamed_data(data: List[str]):
1313

1414

1515
class StreamedAnnotations:
16-
DELIMITER = b";)"
16+
DELIMITER = b'\n:)\n'
1717

1818
def __init__(self, headers: dict):
1919
self._headers = headers
2020
self._annotations = []
2121

22-
async def fetch(self, method: str, session: aiohttp.ClientSession, url: str, data: dict = None, params: dict = None):
22+
async def fetch(self, method: str, session: aiohttp.ClientSession, url: str, data: dict = None,
23+
params: dict = None):
2324
response = await session._request(method, url, json=data, params=params)
2425
buffer = b""
2526
async for line in response.content:
2627
slices = line.split(self.DELIMITER)
27-
if slices[0]:
28+
if len(slices) == 1:
29+
buffer += slices[0]
30+
continue
31+
elif slices[0]:
2832
self._annotations.append(json.loads(buffer + slices[0]))
2933
for data in slices[1:-1]:
3034
self._annotations.append(json.loads(data))
@@ -43,7 +47,6 @@ async def get_data(
4347
map_function: Callable = lambda x: x,
4448
verify_ssl: bool = False,
4549
):
46-
4750
async with aiohttp.ClientSession(raise_for_status=True, headers=self._headers,
4851
connector=aiohttp.TCPConnector(ssl=verify_ssl)) as session:
4952

src/superannotate/version.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
__version__ = "4.3.0b5"
2+

0 commit comments

Comments
 (0)