Skip to content

Commit 26910a9

Browse files
authored
Merge pull request #18 from superannotateai/folder-limitations
Folder limitations
2 parents 32fba21 + 75221a8 commit 26910a9

File tree

4 files changed

+102
-86
lines changed

4 files changed

+102
-86
lines changed

superannotate/db/images.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -231,30 +231,32 @@ def get_image_metadata(project, image_names, return_dict_on_single_output=True):
231231
else:
232232
project_folder_id = None
233233

234+
chunk_size = 500
235+
chunks = [image_names[i:i + chunk_size] for i in range(0, len(image_names), chunk_size)]
236+
234237
json_req = {
235238
'project_id': project['id'],
236239
'team_id': _api.team_id,
237-
'names': image_names,
238240
}
241+
239242
if project_folder_id is not None:
240243
json_req["folder_id"] = project_folder_id
241-
response = _api.send_request(
242-
req_type='POST',
243-
path='/images/getBulk',
244-
json_req=json_req,
245-
)
246-
if not response.ok:
247-
raise SABaseException(
248-
response.status_code,
249-
"Couldn't get image metadata. " + response.text
250-
)
251244

252-
metadata_raw = response.json()
245+
metadata_raw = []
246+
for chunk in chunks:
247+
json_req['names'] = chunk
248+
response = _api.send_request(
249+
req_type='POST',
250+
path='/images/getBulk',
251+
json_req=json_req,
252+
)
253+
if not response.ok:
254+
raise SABaseException(response.status_code,"Couldn't get image metadata. " + response.text)
255+
metadata_raw += response.json()
256+
253257
metadata_without_deleted = []
254-
for im_metadata in metadata_raw:
255-
if 'delete' in im_metadata and im_metadata['delete'] == 1:
256-
continue
257-
metadata_without_deleted.append(im_metadata)
258+
metadata_without_deleted = [ i for i in metadata_raw if i['delete'] != 1 ]
259+
258260
if len(metadata_without_deleted) == 0:
259261
raise SABaseException(
260262
0,

superannotate/db/project_images.py

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
__create_image, get_image_array_to_upload,
2020
get_project_default_image_quality_in_editor, upload_image_array_to_s3
2121
)
22+
from .utils import _get_upload_auth_token
2223

2324
logger = logging.getLogger("superannotate-python-sdk")
2425
_api = API.get_instance()
@@ -50,7 +51,7 @@ def upload_image_to_project(
5051
Can be either "compressed" or "original". If None then the default value in project settings will be used.
5152
:type image_quality_in_editor: str
5253
"""
53-
project, project_folder = get_project_and_folder_metadata(project)
54+
project, folder = get_project_and_folder_metadata(project)
5455
upload_state = common.upload_state_int_to_str(project.get("upload_state"))
5556
if upload_state == "External":
5657
raise SABaseException(
@@ -92,21 +93,18 @@ def upload_image_to_project(
9293
0, "Image name img_name should be set if img is not Pathlike"
9394
)
9495

96+
if folder:
97+
folder_id = folder["id"]
98+
else:
99+
folder_id = get_project_root_folder_id(project)
100+
95101
team_id, project_id = project["team_id"], project["id"]
96102
params = {
97103
'team_id': team_id,
104+
'folder_id' : folder_id
98105
}
99-
response = _api.send_request(
100-
req_type='GET',
101-
path=f'/project/{project_id}/sdkImageUploadToken',
102-
params=params
103-
)
104-
if not response.ok:
105-
raise SABaseException(
106-
response.status_code, "Couldn't get upload token " + response.text
107-
)
108-
res = response.json()
109-
prefix = res['filePath']
106+
res = _get_upload_auth_token(params=params,project_id=project_id)
107+
prefix = res['filePath']
110108
s3_session = boto3.Session(
111109
aws_access_key_id=res['accessKeyId'],
112110
aws_secret_access_key=res['secretAccessKey'],
@@ -122,16 +120,12 @@ def upload_image_to_project(
122120
except Exception as e:
123121
raise SABaseException(0, "Couldn't upload to data server.") from e
124122

125-
if project_folder is not None:
126-
project_folder_id = project_folder["id"]
127-
else:
128-
project_folder_id = None
129123
__create_image(
130124
[img_name], [key],
131125
project,
132126
annotation_status,
133127
prefix, [images_info_and_array[2]],
134-
project_folder_id,
128+
folder_id,
135129
upload_state="Basic"
136130
)
137131

@@ -171,7 +165,7 @@ def _copy_images(
171165
destination_folder_id = get_project_root_folder_id(destination_project)
172166
json_req["destination_folder_id"] = destination_folder_id
173167
res = {}
174-
res['skipped'] = 0
168+
res['skipped'] = []
175169
for start_index in range(0, len(image_names), NUM_TO_SEND):
176170
json_req["image_names"] = image_names[start_index:start_index +
177171
NUM_TO_SEND]
@@ -239,7 +233,7 @@ def copy_images(
239233
source_project["name"] + "" if source_project_folder is None else "/" +
240234
source_project_folder["name"], destination_project["name"] +
241235
"" if destination_project_folder is None else "/" +
242-
destination_project_folder["name"], res["skipped"]
236+
destination_project_folder["name"], len(res["skipped"])
243237
)
244238
return res["skipped"]
245239

superannotate/db/projects.py

Lines changed: 52 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@
3434
fill_class_and_attribute_ids, get_annotation_classes_name_to_id,
3535
search_annotation_classes
3636
)
37-
from .images import get_image_metadata, search_images, search_images_all_folders
37+
from .images import get_image_metadata, search_images, search_images_all_folders, get_project_root_folder_id
3838
from .project_api import (
3939
get_project_and_folder_metadata, get_project_metadata_bare,
4040
get_project_metadata_with_users
4141
)
4242
from .users import get_team_contributor_metadata
43+
from .utils import _get_upload_auth_token
4344

4445
logger = logging.getLogger("superannotate-python-sdk")
4546

@@ -825,9 +826,9 @@ def upload_images_to_project(
825826
:return: uploaded, could-not-upload, existing-images filepaths
826827
:rtype: tuple (3 members) of list of strs
827828
"""
828-
project, project_folder = get_project_and_folder_metadata(project)
829-
project_folder_name = project["name"] + (
830-
f'/{project_folder["name"]}' if project_folder else ""
829+
project, folder = get_project_and_folder_metadata(project)
830+
folder_name = project["name"] + (
831+
f'/{folder["name"]}' if folder else ""
831832
)
832833
upload_state = common.upload_state_int_to_str(project.get("upload_state"))
833834
if upload_state == "External":
@@ -845,7 +846,7 @@ def upload_images_to_project(
845846
project
846847
)
847848
team_id, project_id = project["team_id"], project["id"]
848-
existing_images = search_images((project, project_folder))
849+
existing_images = search_images((project, folder))
849850
duplicate_images = []
850851
for existing_image in existing_images:
851852
i = -1
@@ -863,31 +864,31 @@ def upload_images_to_project(
863864
)
864865
len_img_paths = len(img_paths)
865866
logger.info(
866-
"Uploading %s images to project %s.", len_img_paths, project_folder_name
867+
"Uploading %s images to project %s.", len_img_paths, folder_name
867868
)
868869
if len_img_paths == 0:
869870
return ([], [], duplicate_images)
870-
params = {'team_id': team_id}
871+
872+
873+
if folder:
874+
folder_id = folder["id"]
875+
else:
876+
folder_id = get_project_root_folder_id(project)
877+
878+
params = {'team_id': team_id , 'folder_id' : folder_id }
871879
uploaded = [[] for _ in range(_NUM_THREADS)]
872880
tried_upload = [[] for _ in range(_NUM_THREADS)]
873881
couldnt_upload = [[] for _ in range(_NUM_THREADS)]
874882
finish_event = threading.Event()
875-
chunksize = int(math.ceil(len(img_paths) / _NUM_THREADS))
876-
response = _api.send_request(
877-
req_type='GET',
878-
path=f'/project/{project_id}/sdkImageUploadToken',
879-
params=params
880-
)
881-
if not response.ok:
882-
raise SABaseException(
883-
response.status_code, "Couldn't get upload token " + response.text
884-
)
885-
if project_folder is not None:
886-
project_folder_id = project_folder["id"]
887-
else:
888-
project_folder_id = None
889-
res = response.json()
883+
884+
res = _get_upload_auth_token(params=params,project_id=project_id)
885+
890886
prefix = res['filePath']
887+
limit = res['availableImageCount']
888+
images_to_upload = img_paths[:limit]
889+
images_to_skip = img_paths[limit:]
890+
chunksize = int(math.ceil(len(images_to_upload) / _NUM_THREADS))
891+
891892
tqdm_thread = threading.Thread(
892893
target=__tqdm_thread_image_upload,
893894
args=(len_img_paths, tried_upload, finish_event),
@@ -900,9 +901,9 @@ def upload_images_to_project(
900901
t = threading.Thread(
901902
target=__upload_images_to_aws_thread,
902903
args=(
903-
res, img_paths, project, annotation_status, prefix, thread_id,
904+
res, images_to_upload, project, annotation_status, prefix, thread_id,
904905
chunksize, couldnt_upload, uploaded, tried_upload,
905-
image_quality_in_editor, from_s3_bucket, project_folder_id
906+
image_quality_in_editor, from_s3_bucket, folder_id
906907
),
907908
daemon=True
908909
)
@@ -921,6 +922,7 @@ def upload_images_to_project(
921922
for f in upload_thread:
922923
list_of_uploaded.append(str(f))
923924

925+
list_of_not_uploaded += images_to_skip
924926
return (list_of_uploaded, list_of_not_uploaded, duplicate_images)
925927

926928

@@ -958,9 +960,9 @@ def attach_image_urls_to_project(
958960
:rtype: tuple
959961
"""
960962

961-
project, project_folder = get_project_and_folder_metadata(project)
962-
project_folder_name = project["name"] + (
963-
f'/{project_folder["name"]}' if project_folder else ""
963+
project, folder = get_project_and_folder_metadata(project)
964+
folder_name = project["name"] + (
965+
f'/{folder["name"]}' if folder else ""
964966
)
965967
upload_state = common.upload_state_int_to_str(project.get("upload_state"))
966968
if upload_state == "Basic":
@@ -976,7 +978,7 @@ def attach_image_urls_to_project(
976978
duplicate_idx_csv = existing_names.duplicated(subset="name", keep="first")
977979
duplicate_images = existing_names[duplicate_idx_csv]["name"].tolist()
978980
existing_names = existing_names[~duplicate_idx_csv]
979-
existing_images = search_images((project, project_folder))
981+
existing_images = search_images((project, folder))
980982
duplicate_idx = []
981983
for ind, _ in image_data[image_data["name"].isnull()].iterrows():
982984
while True:
@@ -998,37 +1000,35 @@ def attach_image_urls_to_project(
9981000
)
9991001
image_data = pd.DataFrame(image_data, columns=["name", "url"])
10001002
img_names_urls = image_data.values.tolist()
1001-
len_img_names_urls = len(img_names_urls)
10021003
logger.info(
1003-
"Attaching %s images to project %s.", len_img_names_urls,
1004-
project_folder_name
1004+
"Uploading %s images to project %s.", len(img_names_urls),
1005+
folder_name
10051006
)
1006-
if len_img_names_urls == 0:
1007+
if len(img_names_urls) == 0:
10071008
return ([], [], duplicate_images)
1008-
params = {'team_id': team_id}
1009+
1010+
if folder:
1011+
folder_id = folder["id"]
1012+
else:
1013+
folder_id = get_project_root_folder_id(project)
1014+
1015+
params = {'team_id': team_id , 'folder_id' : folder_id }
10091016
uploaded = [[] for _ in range(_NUM_THREADS)]
10101017
tried_upload = [[] for _ in range(_NUM_THREADS)]
10111018
couldnt_upload = [[] for _ in range(_NUM_THREADS)]
10121019
finish_event = threading.Event()
1013-
chunksize = int(math.ceil(len_img_names_urls / _NUM_THREADS))
1014-
response = _api.send_request(
1015-
req_type='GET',
1016-
path=f'/project/{project_id}/sdkImageUploadToken',
1017-
params=params
1018-
)
1019-
if not response.ok:
1020-
raise SABaseException(
1021-
response.status_code, "Couldn't get upload token " + response.text
1022-
)
1023-
if project_folder is not None:
1024-
project_folder_id = project_folder["id"]
1025-
else:
1026-
project_folder_id = None
1027-
res = response.json()
1020+
1021+
res = _get_upload_auth_token(params=params,project_id=project_id)
1022+
10281023
prefix = res['filePath']
1024+
limit = res['availableImageCount']
1025+
images_to_upload = img_names_urls[:limit]
1026+
images_to_skip = img_names_urls[limit:]
1027+
chunksize = int(math.ceil(len(images_to_upload) / _NUM_THREADS))
1028+
10291029
tqdm_thread = threading.Thread(
10301030
target=__tqdm_thread_image_upload,
1031-
args=(len_img_names_urls, tried_upload, finish_event),
1031+
args=(len(images_to_upload), tried_upload, finish_event),
10321032
daemon=True
10331033
)
10341034
tqdm_thread.start()
@@ -1037,9 +1037,9 @@ def attach_image_urls_to_project(
10371037
t = threading.Thread(
10381038
target=__attach_image_urls_to_project_thread,
10391039
args=(
1040-
res, img_names_urls, project, annotation_status, prefix,
1040+
res, images_to_upload, project, annotation_status, prefix,
10411041
thread_id, chunksize, couldnt_upload, uploaded, tried_upload,
1042-
project_folder_id
1042+
folder_id
10431043
),
10441044
daemon=True
10451045
)
@@ -1058,6 +1058,7 @@ def attach_image_urls_to_project(
10581058
for f in upload_thread:
10591059
list_of_uploaded.append(str(f))
10601060

1061+
list_of_not_uploaded += [i[0] for i in images_to_skip ]
10611062
return (list_of_uploaded, list_of_not_uploaded, duplicate_images)
10621063

10631064

superannotate/db/utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from ..api import API
2+
from ..exceptions import SABaseException, SAImageSizeTooLarge
3+
_api = API.get_instance()
4+
5+
6+
def _get_upload_auth_token(params,project_id):
7+
response = _api.send_request(
8+
req_type='GET',
9+
path=f'/project/{project_id}/sdkImageUploadToken',
10+
params=params
11+
)
12+
if not response.ok:
13+
raise SABaseException(
14+
response.status_code, "Couldn't get upload token " + response.text
15+
)
16+
17+
res = response.json()
18+
return res
19+

0 commit comments

Comments
 (0)