Skip to content

Commit 8055370

Browse files
committed
Version update
1 parent 7bed5a5 commit 8055370

File tree

10 files changed

+162
-124
lines changed

10 files changed

+162
-124
lines changed

src/superannotate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
import typing
55

6-
__version__ = "4.4.10dev7"
6+
__version__ = "4.4.10dev8"
77

88
sys.path.append(os.path.split(os.path.realpath(__file__))[0])
99

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,22 @@ class Config:
161161
extra = Extra.ignore
162162

163163

164+
class UserEntity(BaseModel):
165+
id: Optional[str]
166+
first_name: Optional[str]
167+
last_name: Optional[str]
168+
email: Optional[str]
169+
picture: Optional[str]
170+
user_role: Optional[int]
171+
172+
164173
class TeamEntity(BaseModel):
165174
id: Optional[int]
166175
name: Optional[str]
167176
description: Optional[str]
168177
type: Optional[str]
169178
user_role: Optional[str]
170179
is_default: Optional[bool]
171-
users: Optional[List[Any]]
180+
users: Optional[List[UserEntity]]
172181
pending_invitations: Optional[List[Any]]
173182
creator_id: Optional[str]

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

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,6 @@ def validate_project_type(self):
10371037

10381038
def execute(self):
10391039
if self.is_valid():
1040-
self.reporter.disable_info()
10411040
response = GetAnnotations(
10421041
config=self._config,
10431042
reporter=Reporter(log_info=False),
@@ -1046,10 +1045,9 @@ def execute(self):
10461045
item_names=[self._video_name],
10471046
service_provider=self._service_provider,
10481047
).execute()
1049-
self.reporter.enable_info()
10501048
if response.data:
10511049
generator = VideoFrameGenerator(response.data[0], fps=self._fps)
1052-
self.reporter.log_info(
1050+
logger.info(
10531051
f"Getting annotations for {generator.frames_count} frames from {self._video_name}."
10541052
)
10551053
if response.errors:
@@ -1412,21 +1410,10 @@ def validate_item_names(self):
14121410
self._item_names = [
14131411
i for i in self._item_names if not (i in seen or seen.add(i))
14141412
]
1415-
elif self._item_names is None:
1416-
self._item_names_provided = False
1417-
condition = Condition("project_id", self._project.id, EQ) & Condition(
1418-
"folder_id", self._folder.id, EQ
1419-
)
1420-
1421-
self._item_names = [
1422-
item.name for item in self._service_provider.items.list(condition).data
1423-
]
1424-
else:
1425-
self._item_names = []
14261413

14271414
def _prettify_annotations(self, annotations: List[dict]):
14281415
re_struct = {}
1429-
if self._item_names_provided:
1416+
if self._item_names:
14301417
for annotation in annotations:
14311418
re_struct[annotation["metadata"]["name"]] = annotation
14321419
try:
@@ -1499,12 +1486,12 @@ async def run_workers(
14991486
)
15001487
if small_annotations:
15011488
self._small_annotations_queue = asyncio.Queue()
1502-
small_chunks = divide_to_chunks(
1503-
small_annotations, size=self._config.ANNOTATION_CHUNK_SIZE
1504-
)
1505-
for chunk in small_chunks:
1506-
self._small_annotations_queue.put_nowait(chunk)
1507-
self._small_annotations_queue.put_nowait(None)
1489+
small_chunks = divide_to_chunks(
1490+
small_annotations, size=self._config.ANNOTATION_CHUNK_SIZE
1491+
)
1492+
for chunk in small_chunks:
1493+
self._small_annotations_queue.put_nowait(chunk)
1494+
self._small_annotations_queue.put_nowait(None)
15081495

15091496
annotations.extend(
15101497
list(
@@ -1528,19 +1515,25 @@ def execute(self):
15281515
self._project, self._folder, self._item_names
15291516
)
15301517
)
1531-
else:
1518+
len_items, len_provided_items = len(items), len(self._item_names)
1519+
if len_items != len_provided_items:
1520+
self.reporter.log_warning(
1521+
f"Could not find annotations for {len_provided_items - len_items}/{len_provided_items} items."
1522+
)
1523+
elif self._item_names is None:
15321524
condition = Condition("project_id", self._project.id, EQ) & Condition(
15331525
"folder_id", self._folder.id, EQ
15341526
)
15351527
items = get_or_raise(self._service_provider.items.list(condition))
1528+
else:
1529+
items = []
15361530
id_item_map = {i.id: i for i in items}
1537-
15381531
if not items:
15391532
logger.info("No annotations to download.")
15401533
self._response.data = []
15411534
return self._response
15421535
items_count = len(items)
1543-
logger.info(
1536+
self.reporter.log_info(
15441537
f"Getting {items_count} annotations from "
15451538
f"{self._project.name}{f'/{self._folder.name}' if self._folder.name != 'root' else ''}."
15461539
)
@@ -1563,12 +1556,8 @@ def execute(self):
15631556
logger.error(e)
15641557
self._response.errors = AppException("Can't get annotations.")
15651558
return self._response
1566-
received_items_count = len(annotations)
15671559
self.reporter.finish_progress()
1568-
if items_count > received_items_count:
1569-
self.reporter.log_warning(
1570-
f"Could not find annotations for {items_count - received_items_count}/{items_count} items."
1571-
)
1560+
15721561
self._response.data = self._prettify_annotations(annotations)
15731562
return self._response
15741563

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -704,8 +704,8 @@ def execute(self):
704704
team_users = set()
705705
project_users = {user.user_id for user in self._project.users}
706706
for user in self._team.users:
707-
if user["user_role"] > constances.UserRole.ADMIN.value:
708-
team_users.add(user["email"])
707+
if user.user_role > constances.UserRole.ADMIN.value:
708+
team_users.add(user.email)
709709
# collecting pending team users which is not admin
710710
for user in self._team.pending_invitations:
711711
if user["user_role"] > constances.UserRole.ADMIN.value:
@@ -772,7 +772,7 @@ def __init__(
772772

773773
def execute(self):
774774
if self.is_valid():
775-
team_users = {user["email"] for user in self._team.users}
775+
team_users = {user.email for user in self._team.users}
776776
# collecting pending team users
777777
team_users.update(
778778
{user["email"] for user in self._team.pending_invitations}

src/superannotate/lib/infrastructure/services/annotation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ async def upload_small_annotations(
301301
response = UploadAnnotationsResponse()
302302
response.status = _response.status
303303
response._content = await _response.text()
304+
# TODO add error handling
304305
response.data = parse_obj_as(UploadAnnotations, data_json)
305306
return response
306307

tests/integration/annotations/test_get_annotations.py

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,8 @@ def folder_path(self):
2424

2525
# @pytest.mark.flaky(reruns=3)
2626
def test_get_annotations(self):
27-
sa.upload_images_from_folder_to_project(
28-
self.PROJECT_NAME, self.folder_path, annotation_status="InProgress"
29-
)
27+
self._attach_items(count=4)
28+
3029
sa.create_annotation_classes_from_classes_json(
3130
self.PROJECT_NAME, f"{self.folder_path}/classes/classes.json"
3231
)
@@ -126,24 +125,23 @@ def test_get_annotations10000(self):
126125
a = sa.get_annotations(self.PROJECT_NAME)
127126
assert len(a) == count
128127

129-
def test_get_annotation(self): # to_delete
130-
count = 2
131-
sa.attach_items(
132-
self.PROJECT_NAME,
133-
[
134-
{"name": f"example_image_{i}.jpg", "url": f"url_{i}"}
135-
for i in range(count)
136-
], # noqa
137-
)
138-
assert len(sa.search_items(self.PROJECT_NAME)) == count
139-
a = sa.get_annotations(self.PROJECT_NAME)
140-
assert len(a) == count
141-
142-
def test_get_annotations_duplicated_names(self):
128+
def test_get_annotations_logs(self):
143129
self._attach_items(count=4)
130+
items_names = [self.IMAGE_NAME] * 4
131+
items_names.append("Non-existent item")
144132
with self.assertLogs("sa", level="INFO") as cm:
145-
sa.get_annotations(self.PROJECT_NAME, [self.IMAGE_NAME] * 4)
146-
assert "INFO:sa:Dropping duplicates. Found 1/4 unique items." in cm.output
133+
assert len(sa.get_annotations(self.PROJECT_NAME, items_names)) == 1
134+
assert (
135+
"INFO:sa:Dropping duplicates. Found 2/5 unique items." == cm.output[0]
136+
)
137+
assert (
138+
"WARNING:sa:Could not find annotations for 1/2 items." == cm.output[1]
139+
)
140+
assert (
141+
f"INFO:sa:Getting 1 annotations from {self.PROJECT_NAME}."
142+
== cm.output[2]
143+
)
144+
assert len(cm.output) == 3
147145

148146

149147
class TestGetAnnotationsVideo(BaseTestCase):
@@ -172,7 +170,7 @@ def folder_path(self):
172170
def annotations_path(self):
173171
return os.path.join(self.folder_path, self.ANNOTATIONS_PATH)
174172

175-
def test_video_annotation_upload_root(self):
173+
def test_video_get_annotations_root(self):
176174
sa.create_annotation_classes_from_classes_json(
177175
self.PROJECT_NAME, self.classes_path
178176
)
@@ -187,7 +185,7 @@ def test_video_annotation_upload_root(self):
187185
annotations = sa.get_annotations(self.PROJECT_NAME)
188186
self.assertEqual(len(annotations), 2)
189187

190-
def test_video_annotation_upload_folder(self):
188+
def test_video_get_annotations_from_folder(self):
191189
sa.create_annotation_classes_from_classes_json(
192190
self.PROJECT_NAME, self.classes_path
193191
)
@@ -208,5 +206,3 @@ def test_empty_list_get(self):
208206
)
209207
annotations = sa.get_annotations(self.PROJECT_NAME, items=[])
210208
assert len(annotations) == 0
211-
annotations = sa.get_annotations(self.PROJECT_NAME, items=None)
212-
assert len(annotations) == 2
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import json
2+
import os
3+
import tempfile
4+
from pathlib import Path
5+
6+
from src.superannotate import SAClient
7+
from src.superannotate.lib.app.helpers import get_annotation_paths
8+
from tests import DATA_SET_PATH
9+
from tests.integration.base import BaseTestCase
10+
11+
sa = SAClient()
12+
13+
14+
class TestAnnotationUploadVector(BaseTestCase):
15+
PROJECT_NAME = "Test-upload_annotations"
16+
PROJECT_DESCRIPTION = "Desc"
17+
PROJECT_TYPE = "Vector"
18+
TEST_FOLDER_PATH = "data_set/sample_vector_annotations_with_tag_classes"
19+
TEST_4_FOLDER_PATH = "data_set/sample_project_vector"
20+
TEST_BIG_FOLDER_PATH = "sample_big_json_vector"
21+
22+
IMAGE_NAME = "example_image_1.jpg"
23+
24+
@property
25+
def data_set(self):
26+
return Path(__file__).parent.parent.parent
27+
28+
@property
29+
def big_annotations_folder_path(self):
30+
return os.path.join(DATA_SET_PATH, self.TEST_BIG_FOLDER_PATH)
31+
32+
@staticmethod
33+
def _get_annotations_from_folder(path):
34+
paths = get_annotation_paths(path)
35+
annotations = []
36+
for i in paths:
37+
annotations.append(json.load(open(i)))
38+
return annotations
39+
40+
def test_large_annotations_upload_get_download(self):
41+
items_to_attach = [
42+
{"name": f"aearth_mov_00{i}.jpg", "url": f"url_{i}"} for i in range(1, 6)
43+
]
44+
sa.attach_items(
45+
self.PROJECT_NAME,
46+
items_to_attach,
47+
)
48+
sa.create_annotation_classes_from_classes_json(
49+
self.PROJECT_NAME,
50+
f"{self.big_annotations_folder_path}/classes/classes.json",
51+
)
52+
annotations = self._get_annotations_from_folder(
53+
self.big_annotations_folder_path
54+
)
55+
with self.assertLogs("sa", level="INFO") as cm:
56+
uploaded, _, _ = sa.upload_annotations(
57+
self.PROJECT_NAME, annotations
58+
).values()
59+
assert (
60+
"INFO:sa:Uploading 5/5 annotations to the project Test-upload_annotations."
61+
== cm.output[0]
62+
)
63+
assert len(uploaded) == 5
64+
65+
with self.assertLogs("sa", level="INFO") as cm:
66+
annotations = sa.get_annotations(self.PROJECT_NAME)
67+
assert (
68+
"INFO:sa:Getting 5 annotations from Test-upload_annotations."
69+
== cm.output[0]
70+
)
71+
assert len(annotations) == 5
72+
assert [
73+
len(annotation["instances"]) > 1 for annotation in annotations
74+
].count(True) == 4
75+
76+
with tempfile.TemporaryDirectory() as tmpdir:
77+
with self.assertLogs("sa", level="INFO") as cm:
78+
sa.download_annotations(self.PROJECT_NAME, tmpdir)
79+
assert cm.output[0].startswith(
80+
"INFO:sa:Downloading the annotations of the requested items to /var/"
81+
)
82+
assert cm.output[0].endswith("This might take a while…")
83+
84+
for item_name in items_to_attach:
85+
annotation = self._get_annotations_from_folder(
86+
f"{tmpdir}/{os.listdir(tmpdir)[0]}/{items_to_attach[0]['name']}___objects.json"
87+
)
88+
for i in annotations:
89+
if i["metadata"]["name"] == item_name:
90+
assert i == annotation

tests/integration/annotations/test_upload_annotations.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class TestAnnotationUploadVector(BaseTestCase):
1616
PROJECT_TYPE = "Vector"
1717
TEST_FOLDER_PATH = "data_set/sample_vector_annotations_with_tag_classes"
1818
TEST_4_FOLDER_PATH = "data_set/sample_project_vector"
19-
TEST_BIG_FOLDER_PATH = "sample_big_json_vector"
2019
TEST_LARGE_FOLDER_PATH = "sample_large_json_vector"
2120
IMAGE_NAME = "example_image_1.jpg"
2221

@@ -28,10 +27,6 @@ def data_set(self):
2827
def folder_path(self):
2928
return os.path.join(Path(__file__).parent.parent.parent, self.TEST_FOLDER_PATH)
3029

31-
@property
32-
def big_annotations_folder_path(self):
33-
return os.path.join(DATA_SET_PATH, self.TEST_BIG_FOLDER_PATH)
34-
3530
@property
3631
def large_annotations_folder_path(self):
3732
return os.path.join(DATA_SET_PATH, self.TEST_LARGE_FOLDER_PATH)
@@ -115,25 +110,3 @@ def test_upload_large_annotations(self):
115110
assert [len(annotation["instances"]) > 1 for annotation in annotations].count(
116111
True
117112
) == 5
118-
119-
def test_upload_big_annotations(self):
120-
sa.attach_items(
121-
self.PROJECT_NAME,
122-
[
123-
{"name": f"aearth_mov_00{i}.jpg", "url": f"url_{i}"}
124-
for i in range(1, 6)
125-
], # noqa
126-
)
127-
sa.create_annotation_classes_from_classes_json(
128-
self.PROJECT_NAME,
129-
f"{self.big_annotations_folder_path}/classes/classes.json",
130-
)
131-
annotations = self._get_annotations_from_folder(
132-
self.big_annotations_folder_path
133-
)
134-
uploaded, _, _ = sa.upload_annotations(self.PROJECT_NAME, annotations).values()
135-
assert len(uploaded) == 5
136-
annotations = sa.get_annotations(self.PROJECT_NAME)
137-
assert [len(annotation["instances"]) > 1 for annotation in annotations].count(
138-
True
139-
) == 4

0 commit comments

Comments
 (0)