Skip to content

Commit 8cb8170

Browse files
authored
Merge pull request #175 from superannotateai/re-design-sdk
Re design sdk
2 parents 822be19 + c586e4e commit 8cb8170

File tree

8 files changed

+131
-75
lines changed

8 files changed

+131
-75
lines changed

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
minversion = 3.0
33
log_cli=true
44
python_files = test_*.py
5-
addopts = -n32 --dist=loadscope
5+
;addopts = -n32 --dist=loadscope

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
pydicom>=2.0.0
22
boto3>=1.14.53
3-
requests>=2.25.1
3+
requests=2.26.0
44
requests-toolbelt>=0.9.1
55
tqdm>=4.48.2
66
pillow>=7.2.0

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

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ def delete_folders(project: str, folder_names: List[str]):
394394

395395

396396
@Trackable
397-
@validate_input
397+
# @validate_input
398398
def get_project_and_folder_metadata(project: Union[str, dict]):
399399
"""Returns project and folder metadata tuple. If folder part is empty,
400400
than returned folder part is set to None.
@@ -2454,20 +2454,22 @@ def upload_annotations_from_folder_to_project(
24542454
failed_annotations = []
24552455
missing_annotations = []
24562456
chunk_size = 10
2457-
for i in tqdm(range(0, len(annotation_paths), chunk_size)):
2458-
response = controller.upload_annotations_from_folder(
2459-
project_name=project_name,
2460-
folder_name=folder_name,
2461-
folder_path=folder_path,
2462-
annotation_paths=annotation_paths[i : i + chunk_size], # noqa: E203
2463-
client_s3_bucket=from_s3_bucket,
2464-
)
2465-
if response.errors:
2466-
logger.warning(response.errors)
2467-
if response.data:
2468-
uploaded_annotations.extend(response.data[0])
2469-
missing_annotations.extend(response.data[1])
2470-
failed_annotations.extend(response.data[2])
2457+
with tqdm(total=len(annotation_paths)) as progress_bar:
2458+
for i in range(0, len(annotation_paths), chunk_size):
2459+
response = controller.upload_annotations_from_folder(
2460+
project_name=project_name,
2461+
folder_name=folder_name,
2462+
folder_path=folder_path,
2463+
annotation_paths=annotation_paths[i : i + chunk_size], # noqa: E203
2464+
client_s3_bucket=from_s3_bucket,
2465+
)
2466+
if response.errors:
2467+
logger.warning(response.errors)
2468+
if response.data:
2469+
uploaded_annotations.extend(response.data[0])
2470+
missing_annotations.extend(response.data[1])
2471+
failed_annotations.extend(response.data[2])
2472+
progress_bar.update(chunk_size)
24712473
return uploaded_annotations, failed_annotations, missing_annotations
24722474

24732475

src/superannotate/lib/core/entities.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,10 @@ def __init__(
225225
folder_id: int = None,
226226
annotator_id: int = None,
227227
annotator_name: str = None,
228+
qa_id: str = None,
229+
qa_name: str = None,
230+
entropy_value: int = None,
231+
approval_status: bool = None,
228232
is_pinned: bool = None,
229233
meta: ImageInfoEntity = ImageInfoEntity(),
230234
):
@@ -236,7 +240,11 @@ def __init__(
236240
self.project_id = project_id
237241
self.annotation_status_code = annotation_status_code
238242
self.folder_id = folder_id
243+
self.qa_id = qa_id
244+
self.qa_name = qa_name
245+
self.entropy_value = entropy_value
239246
self.annotator_id = annotator_id
247+
self.approval_status = approval_status
240248
self.annotator_name = annotator_name
241249
self.is_pinned = is_pinned
242250
self.meta = meta
@@ -250,6 +258,10 @@ def to_dict(self):
250258
"project_id": self.project_id,
251259
"annotation_status": self.annotation_status_code,
252260
"folder_id": self.folder_id,
261+
"qa_id": self.qa_id,
262+
"qa_name": self.qa_name,
263+
"entropy_value": self.entropy_value,
264+
"approval_status": self.approval_status,
253265
"annotator_id": self.annotator_id,
254266
"annotator_name": self.annotator_name,
255267
"is_pinned": self.is_pinned,

src/superannotate/lib/core/usecases.py

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ def execute(self):
10201020
headers=annotations["annotation_json_path"]["headers"],
10211021
)
10221022
if not response.ok:
1023-
raise AppException(f"Couldn't load annotations {response.text}")
1023+
raise AppException(f"Couldn't load annotations.")
10241024

10251025
image_annotations = response.json()
10261026
from_project_annotation_classes = (
@@ -1111,7 +1111,7 @@ def execute(self):
11111111
headers=annotations["annotation_bluemap_path"]["headers"],
11121112
)
11131113
if not response.ok:
1114-
raise AppException(f"Couldn't load annotations {response.text}")
1114+
raise AppException(f"Couldn't load annotations.")
11151115
self.to_project_s3_repo.insert(
11161116
S3FileEntity(
11171117
auth_data["annotation_bluemap_path"]["filePath"], response.content
@@ -2036,7 +2036,7 @@ def execute(self):
20362036
headers=credentials["annotation_json_path"]["headers"],
20372037
)
20382038
if not response.ok:
2039-
logger.warning(f"Couldn't load annotations {response.text}")
2039+
logger.warning(f"Couldn't load annotations.")
20402040
self._response.data = data
20412041
return self._response
20422042
data["annotation_json"] = response.json()
@@ -2131,7 +2131,7 @@ def execute(self):
21312131
url=annotation_json_creds["url"], headers=annotation_json_creds["headers"],
21322132
)
21332133
if not response.ok:
2134-
raise AppException(f"Couldn't load annotations {response.text}")
2134+
raise AppException(f"Couldn't load annotations.")
21352135
data["preannotation_json"] = response.json()
21362136
data["preannotation_json_filename"] = f"{self._image_name}{file_postfix}"
21372137
if self._project.project_type == constances.ProjectType.PIXEL.value:
@@ -2208,7 +2208,7 @@ def execute(self):
22082208
headers=annotation_json_creds["headers"],
22092209
)
22102210
if not response.ok:
2211-
logger.warning(f"Couldn't load annotations {response.text}")
2211+
logger.warning(f"Couldn't load annotations.")
22122212
self._response.data = (None, None)
22132213
return self._response
22142214
data["annotation_json"] = response.json()
@@ -2287,7 +2287,7 @@ def execute(self):
22872287
url=annotation_json_creds["url"], headers=annotation_json_creds["headers"],
22882288
)
22892289
if not response.ok:
2290-
raise AppException(f"Couldn't load annotations {response.text}")
2290+
raise AppException(f"Couldn't load annotations.")
22912291
data["preannotation_json"] = response.json()
22922292
data["preannotation_json_filename"] = f"{self._image_name}{file_postfix}"
22932293
mask_path = None
@@ -3147,6 +3147,7 @@ def execute(self):
31473147

31483148

31493149
class UploadAnnotationsUseCase(BaseUseCase):
3150+
MAX_WORKERS = 10
31503151
def __init__(
31513152
self,
31523153
project: ProjectEntity,
@@ -3326,42 +3327,9 @@ def execute(self):
33263327
else:
33273328
from_s3 = None
33283329

3329-
for image_id, image_info in auth_data["images"].items():
3330-
if from_s3:
3331-
file = io.BytesIO()
3332-
s3_object = from_s3.Object(
3333-
self._client_s3_bucket, image_id_name_map[image_id].path
3334-
)
3335-
s3_object.download_fileobj(file)
3336-
file.seek(0)
3337-
annotation_json = json.load(file)
3338-
else:
3339-
annotation_json = json.load(open(image_id_name_map[image_id].path))
3340-
3341-
self.fill_classes_data(annotation_json)
3342-
bucket.put_object(
3343-
Key=image_info["annotation_json_path"],
3344-
Body=json.dumps(annotation_json),
3345-
)
3346-
if self._project.project_type == constances.ProjectType.PIXEL.value:
3347-
mask_filename = (
3348-
image_id_name_map[image_id].name
3349-
+ constances.ANNOTATION_MASK_POSTFIX
3350-
)
3351-
if from_s3:
3352-
file = io.BytesIO()
3353-
s3_object = self._client_s3_bucket.Objcect(
3354-
self._client_s3_bucket, self._folder_path + mask_filename
3355-
)
3356-
s3_object.download_file(file)
3357-
file.seek(0)
3358-
else:
3359-
with open(
3360-
f"{self._folder_path}/{mask_filename}", "rb"
3361-
) as mask_file:
3362-
file = io.BytesIO(mask_file.read())
3363-
3364-
bucket.put_object(Key=image_info["annotation_bluemap_path"], Body=file)
3330+
with concurrent.futures.ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor:
3331+
for image_id, image_info in auth_data["images"].items():
3332+
executor.submit(self.upload_to_s3, image_id, image_info, bucket, from_s3, image_id_name_map)
33653333

33663334
uploaded_annotations = [annotation.path for annotation in annotations_to_upload]
33673335
missing_annotations = [annotation.path for annotation in missing_annotations]
@@ -3377,6 +3345,43 @@ def execute(self):
33773345
)
33783346
return self._response
33793347

3348+
def upload_to_s3(self, image_id: int, image_info, bucket, from_s3, image_id_name_map):
3349+
if from_s3:
3350+
file = io.BytesIO()
3351+
s3_object = from_s3.Object(
3352+
self._client_s3_bucket, image_id_name_map[image_id].path
3353+
)
3354+
s3_object.download_fileobj(file)
3355+
file.seek(0)
3356+
annotation_json = json.load(file)
3357+
else:
3358+
annotation_json = json.load(open(image_id_name_map[image_id].path))
3359+
3360+
self.fill_classes_data(annotation_json)
3361+
bucket.put_object(
3362+
Key=image_info["annotation_json_path"],
3363+
Body=json.dumps(annotation_json),
3364+
)
3365+
if self._project.project_type == constances.ProjectType.PIXEL.value:
3366+
mask_filename = (
3367+
image_id_name_map[image_id].name
3368+
+ constances.ANNOTATION_MASK_POSTFIX
3369+
)
3370+
if from_s3:
3371+
file = io.BytesIO()
3372+
s3_object = self._client_s3_bucket.Objcect(
3373+
self._client_s3_bucket, self._folder_path + mask_filename
3374+
)
3375+
s3_object.download_file(file)
3376+
file.seek(0)
3377+
else:
3378+
with open(
3379+
f"{self._folder_path}/{mask_filename}", "rb"
3380+
) as mask_file:
3381+
file = io.BytesIO(mask_file.read())
3382+
3383+
bucket.put_object(Key=image_info["annotation_bluemap_path"], Body=file)
3384+
33803385

33813386
class CreateModelUseCase(BaseUseCase):
33823387
def __init__(

src/superannotate/lib/infrastructure/services.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import lib.core as constance
1010
import requests.packages.urllib3
11-
import requests.adapters
1211
from lib.core.exceptions import AppException
1312
from lib.core.serviceproviders import SuerannotateServiceProvider
1413
from requests.exceptions import HTTPError
@@ -32,14 +31,12 @@ def __init__(self, api_url: str, auth_token: str, logger, paginate_by=None):
3231
self.logger = logger
3332
self._paginate_by = paginate_by
3433
self.team_id = auth_token.split("=")[-1]
35-
self._session = None
3634

3735
@timed_lru_cache(seconds=360)
3836
def get_session(self):
39-
if not self._session:
40-
self._session = requests.Session()
41-
self._session.headers.update(self.default_headers)
42-
return self._session
37+
session = requests.Session()
38+
session.headers.update(self.default_headers)
39+
return session
4340

4441
@property
4542
def default_headers(self):
@@ -79,14 +76,12 @@ def _request(
7976
self, url, method="get", data=None, headers=None, params=None, retried=0
8077
) -> requests.Response:
8178
kwargs = {"json": data} if data else {}
82-
headers_dict = self.default_headers.copy()
8379
session = self.get_session()
8480
session.headers.update(headers if headers else {})
85-
method = getattr(session, method)
8681
with self.safe_api():
87-
response = method(
88-
url, **kwargs, headers=headers_dict, params=params, timeout=60,
89-
)
82+
req = requests.Request(method=method, url=url, **kwargs, params=params)
83+
prepared = self._session.prepare_request(req)
84+
response = self._session.send(request=prepared)
9085
if response.status_code == 404 and retried < 3:
9186
return self._request(
9287
url,

tests/integration/test_interface.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import os
22
from os.path import dirname
3+
import tempfile
4+
35
import src.superannotate as sa
46
from src.superannotate.lib.app.exceptions import AppException
57
from tests.integration.base import BaseTestCase
68

79

10+
811
class TestInterface(BaseTestCase):
912
PROJECT_NAME = "Interface test"
1013
TEST_FOLDER_PATH = "data_set/sample_project_vector"
@@ -52,3 +55,24 @@ def test_get_project_metadata(self):
5255
self.assertListEqual(metadata.get("contributors", []), [])
5356
metadata_with_users = sa.get_project_metadata(self.PROJECT_NAME, include_contributors=True)
5457
self.assertIsNotNone(metadata_with_users.get("contributors"))
58+
59+
def test_upload_annotations_from_folder_to_project(self):
60+
sa.upload_images_from_folder_to_project(
61+
self.PROJECT_NAME,
62+
self.folder_path,
63+
annotation_status="InProgress",
64+
)
65+
uploaded_annotations, _, _ = sa.upload_annotations_from_folder_to_project(
66+
self.PROJECT_NAME, self.folder_path
67+
)
68+
self.assertEqual(len(uploaded_annotations), 4)
69+
70+
def test_get_images_metadata(self):
71+
sa.upload_images_from_folder_to_project(self.PROJECT_NAME, self.folder_path)
72+
metadata = sa.search_images(self.PROJECT_NAME, self.EXAMPLE_IMAGE_1, return_metadata=True)
73+
self.assertIn("qa_id", metadata[0])
74+
75+
def test_download_image_annotations(self):
76+
sa.upload_images_from_folder_to_project(self.PROJECT_NAME, self.folder_path)
77+
with tempfile.TemporaryDirectory() as temp_dir:
78+
sa.download_image_annotations(self.PROJECT_NAME, self.EXAMPLE_IMAGE_1, temp_dir)

tests/profiling/profiling.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
11
import src.superannotate as sa
2-
32
import time
3+
#
4+
# s = time.time()
5+
#
6+
# sa.get_project_metadata("dev")
7+
#
8+
# e = time.time()
9+
# print(e-s)
10+
# #
11+
import cProfile
12+
import pstats, io
13+
14+
15+
16+
pr = cProfile.Profile()
17+
pr.enable()
18+
19+
sa.delete_images("dev")
20+
sa.upload_images_from_folder_to_project("dev", "/Users/vaghinak.basentsyan/www/superannotate-python-sdk/tests/data_set/sample_project_vector")
421

22+
pr.disable()
23+
s = io.StringIO()
524

6-
stat = time.time()
7-
sa.search_annotation_classes("Vector Project")
8-
end = time.time()
9-
print(11, end-stat)
25+
ps = pstats.Stats(pr, stream=s)
26+
ps.print_stats()
27+
print(s.getvalue())

0 commit comments

Comments
 (0)