Skip to content

Commit 1a7a06a

Browse files
committed
Start work on annotation folders
1 parent 9831d8c commit 1a7a06a

File tree

4 files changed

+141
-45
lines changed

4 files changed

+141
-45
lines changed

superannotate/db/folders.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import logging
2+
13
from ..api import API
24
from ..exceptions import SABaseException
35
from .project_api import get_project_metadata_bare
46

7+
logger = logging.getLogger("superannotate-python-sdk")
8+
59
_api = API.get_instance()
610

711

@@ -27,36 +31,35 @@ def search_folders(project, folder_name=None, return_metadata=False):
2731
project = get_project_metadata_bare(project)
2832
team_id, project_id = project["team_id"], project["id"]
2933
result_list = []
30-
params = {'team_id': team_id, 'project_id': project_id, 'offset': 0}
31-
total_got = 0
34+
params = {
35+
'team_id': team_id,
36+
'project_id': project_id,
37+
'offset': 0,
38+
'name': folder_name,
39+
'is_root': 0
40+
}
3241
total_folders = 0
3342
while True:
3443
response = _api.send_request(
35-
req_type='GET', path='/images', params=params
44+
req_type='GET', path='/folders', params=params
3645
)
3746
if not response.ok:
3847
raise SABaseException(
39-
response.status_code, "Couldn't search images " + response.text
48+
response.status_code, "Couldn't search folders " + response.text
4049
)
4150
response = response.json()
42-
images = response["images"]
43-
folders = response["folders"]
44-
45-
results_folders = folders["data"]
51+
results_folders = response["data"]
4652
for r in results_folders:
47-
if folder_name is not None and r["name"] != folder_name:
48-
continue
4953
if return_metadata:
5054
result_list.append(r)
5155
else:
5256
result_list.append(r["name"])
5357

5458
total_folders += len(results_folders)
55-
if folders["count"] <= total_folders:
59+
if response["count"] <= total_folders:
5660
break
5761

58-
total_got += len(images["data"]) + len(results_folders)
59-
params["offset"] = total_got
62+
params["offset"] = total_folders
6063

6164
return result_list
6265

@@ -84,4 +87,9 @@ def create_folder(project, folder_name):
8487
response.status_code, "Couldn't create project " + response.text
8588
)
8689
res = response.json()
90+
if res["name"] != folder_name:
91+
logger.warning(
92+
"Created folder has name %s, since folder with name %s already existed.",
93+
res["name"], folder_name
94+
)
8795
return res

superannotate/db/images.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,19 @@ def search_images(
6464
)
6565

6666
if project_folder is not None:
67-
folder = get_folder_metadata(project, project_folder)
68-
folder = folder["id"]
67+
if not isinstance(project_folder, dict):
68+
project_folder = get_folder_metadata(project, project_folder)
69+
project_folder_id = project_folder["id"]
70+
else:
71+
project_folder_id = None
6972

7073
result_list = []
7174
params = {
7275
'team_id': team_id,
7376
'project_id': project_id,
7477
'annotation_status': annotation_status,
7578
'offset': 0,
76-
'folder_id': project_folder
79+
'folder_id': project_folder_id
7780
}
7881
if image_name_prefix is not None:
7982
params['name'] = image_name_prefix
@@ -137,17 +140,30 @@ def get_image_metadata(
137140
if isinstance(image_names, str):
138141
image_names = [image_names]
139142

143+
if project_folder is not None:
144+
if not isinstance(project_folder, dict):
145+
project_folder = get_folder_metadata(project, project_folder)
146+
project_folder_id = project_folder["id"]
147+
else:
148+
project_folder_id = None
149+
140150
json_req = {
141151
'project_id': project['id'],
142152
'team_id': _api.team_id,
143-
'names': image_names
144-
'folder_id': project_folder
153+
'names': image_names,
145154
}
155+
if project_folder_id is not None:
156+
json_req["folder_id"] = project_folder_id
146157
response = _api.send_request(
147158
req_type='POST',
148159
path='/images/getBulk',
149160
json_req=json_req,
150161
)
162+
if not response.ok:
163+
raise SABaseException(
164+
response.status_code,
165+
"Couldn't get image metadata. " + response.text
166+
)
151167

152168
metadata_raw = response.json()
153169
metadata_without_deleted = []
@@ -649,7 +665,7 @@ def get_image_preannotations(project, image_name):
649665
return _get_image_pre_or_annotations(project, image_name, "pre")
650666

651667

652-
def get_image_annotations(project, image_name):
668+
def get_image_annotations(project, image_name, project_folder=None):
653669
"""Get annotations of the image.
654670
655671
:param project: project name or metadata of the project
@@ -664,10 +680,14 @@ def get_image_annotations(project, image_name):
664680
"annotation_mask_filename": mask filename on server
665681
:rtype: dict
666682
"""
667-
return _get_image_pre_or_annotations(project, image_name, "")
683+
return _get_image_pre_or_annotations(
684+
project, image_name, "", project_folder
685+
)
668686

669687

670-
def _get_image_pre_or_annotations(project, image_name, pre, project_type=None):
688+
def _get_image_pre_or_annotations(
689+
project, image_name, pre, project_type=None, project_folder=None
690+
):
671691
image = get_image_metadata(project, image_name)
672692
team_id, project_id, image_id, folder_id = image["team_id"], image[
673693
"project_id"], image["id"], image['folder_id']

superannotate/db/projects.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ def get_image_array_to_upload(
642642
def __upload_images_to_aws_thread(
643643
res, img_paths, project, annotation_status, prefix, thread_id, chunksize,
644644
couldnt_upload, uploaded, tried_upload, image_quality_in_editor,
645-
from_s3_bucket, folder_id
645+
from_s3_bucket, project_folder_id
646646
):
647647
len_img_paths = len(img_paths)
648648
start_index = thread_id * chunksize
@@ -699,7 +699,7 @@ def __upload_images_to_aws_thread(
699699
__create_image(
700700
uploaded_imgs_info[0], uploaded_imgs_info[1], project,
701701
annotation_status, prefix, uploaded_imgs_info[2],
702-
folder_id
702+
project_folder_id
703703
)
704704
except SABaseException as e:
705705
couldnt_upload[thread_id] += uploaded_imgs
@@ -711,7 +711,7 @@ def __upload_images_to_aws_thread(
711711
try:
712712
__create_image(
713713
uploaded_imgs_info[0], uploaded_imgs_info[1], project,
714-
annotation_status, prefix, uploaded_imgs_info[2], folder_id
714+
annotation_status, prefix, uploaded_imgs_info[2], project_folder_id
715715
)
716716
except SABaseException as e:
717717
couldnt_upload[thread_id] += uploaded_imgs
@@ -722,7 +722,7 @@ def __upload_images_to_aws_thread(
722722

723723
def __create_image(
724724
img_names, img_paths, project, annotation_status, remote_dir, sizes,
725-
folder_id
725+
project_folder_id
726726
):
727727
if len(img_paths) == 0:
728728
return
@@ -737,8 +737,8 @@ def __create_image(
737737
"annotation_status": annotation_status,
738738
"meta": {}
739739
}
740-
if folder_id is not None:
741-
data["folder_id"] = folder_id
740+
if project_folder_id is not None:
741+
data["folder_id"] = project_folder_id
742742
for img_name, img_path, size in zip(img_names, img_paths, sizes):
743743
img_name_uuid = Path(img_path).name
744744
remote_path = remote_dir + f"{img_name_uuid}"
@@ -838,10 +838,10 @@ def upload_images_to_project(
838838
raise SABaseException(
839839
response.status_code, "Couldn't get upload token " + response.text
840840
)
841-
if folder is not None:
842-
if not isinstance(folder, dict):
843-
folder = get_folder_metadata(project, folder)
844-
folder = folder["id"]
841+
if project_folder is not None:
842+
if not isinstance(project_folder, dict):
843+
project_folder = get_folder_metadata(project, project_folder)
844+
project_folder = project_folder["id"]
845845
res = response.json()
846846
prefix = res['filePath']
847847
tqdm_thread = threading.Thread(
@@ -858,7 +858,7 @@ def upload_images_to_project(
858858
args=(
859859
res, img_paths, project, annotation_status, prefix, thread_id,
860860
chunksize, couldnt_upload, uploaded, tried_upload,
861-
image_quality_in_editor, from_s3_bucket, folder
861+
image_quality_in_editor, from_s3_bucket, project_folder
862862
),
863863
daemon=True
864864
)
@@ -1172,7 +1172,7 @@ def upload_images_from_azure_blob_to_project(
11721172
def __upload_annotations_thread(
11731173
team_id, project_id, project_type, anns_filenames, folder_path,
11741174
annotation_classes_dict, pre, thread_id, chunksize, missing_images,
1175-
couldnt_upload, uploaded, from_s3_bucket, project_folder
1175+
couldnt_upload, uploaded, from_s3_bucket, project_folder_id
11761176
):
11771177
NUM_TO_SEND = 500
11781178
len_anns = len(anns_filenames)
@@ -1196,7 +1196,9 @@ def __upload_annotations_thread(
11961196
image_name = anns_filenames[j][:-len_postfix_json]
11971197
names.append(image_name)
11981198
try:
1199-
metadatas = get_image_metadata({"id": project_id}, names, False)
1199+
metadatas = get_image_metadata(
1200+
{"id": project_id}, names, False, {"id": project_folder_id}
1201+
)
12001202
except SABaseException:
12011203
metadatas = []
12021204
names_in_metadatas = [metadata["name"] for metadata in metadatas]
@@ -1216,7 +1218,7 @@ def __upload_annotations_thread(
12161218
"project_id": project_id,
12171219
"team_id": team_id,
12181220
"ids": [metadata["id"] for metadata in metadatas],
1219-
"folder_id": project_folder
1221+
"folder_id": project_folder_id
12201222
}
12211223
endpoint = '/images/getAnnotationsPathsAndTokens' if pre == "" else '/images/getPreAnnotationsPathsAndTokens'
12221224
response = _api.send_request(
@@ -1338,9 +1340,16 @@ def _upload_pre_or_annotations_from_folder_to_project(
13381340
if not isinstance(project, dict):
13391341
project = get_project_metadata_bare(project)
13401342

1343+
if project_folder is not None:
1344+
if not isinstance(project_folder, dict):
1345+
project_folder = get_folder_metadata(project, project_folder)
1346+
project_folder_id = project_folder["id"]
1347+
else:
1348+
project_folder_id = None
1349+
13411350
return _upload_annotations_from_folder_to_project(
13421351
project, folder_path, pre, from_s3_bucket, recursive_subfolders,
1343-
project_folder
1352+
project_folder_id
13441353
)
13451354

13461355

@@ -1350,7 +1359,7 @@ def _upload_annotations_from_folder_to_project(
13501359
pre,
13511360
from_s3_bucket=None,
13521361
recursive_subfolders=False,
1353-
project_folder=None
1362+
project_folder_id=None
13541363
):
13551364
return_result = []
13561365
if from_s3_bucket is not None:
@@ -1362,7 +1371,7 @@ def _upload_annotations_from_folder_to_project(
13621371
if path.is_dir():
13631372
return_result += _upload_annotations_from_folder_to_project(
13641373
project, path, pre, from_s3_bucket,
1365-
recursive_subfolders, project_folder
1374+
recursive_subfolders, project_folder_id
13661375
)
13671376
else:
13681377
s3_client = boto3.client('s3')
@@ -1374,7 +1383,7 @@ def _upload_annotations_from_folder_to_project(
13741383
for o in results:
13751384
return_result += _upload_annotations_from_folder_to_project(
13761385
project, o.get('Prefix'), pre, from_s3_bucket,
1377-
recursive_subfolders, project_folder
1386+
recursive_subfolders, project_folder_id
13781387
)
13791388

13801389
team_id, project_id, project_type = project["team_id"], project[
@@ -1449,7 +1458,7 @@ def _upload_annotations_from_folder_to_project(
14491458
team_id, project_id, project_type, annotations_filenames,
14501459
folder_path, annotation_classes_dict, pre, thread_id, chunksize,
14511460
missing_image, couldnt_upload, uploaded, from_s3_bucket,
1452-
project_folder
1461+
project_folder_id
14531462
),
14541463
daemon=True
14551464
)

tests/test_folders.py

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from pathlib import Path
2-
import json
3-
from superannotate.exceptions import SABaseException
42

53
import pytest
6-
74
import superannotate as sa
5+
from superannotate.exceptions import SABaseException
86

97
PROJECT_NAME1 = "test folder simple"
10-
FROM_FOLDER = "./tests/sample_project_vector"
8+
PROJECT_NAME2 = "test folder annotations"
9+
10+
FROM_FOLDER = Path("./tests/sample_project_vector")
1111

1212

1313
def test_basic_folders(tmpdir):
@@ -80,4 +80,63 @@ def test_basic_folders(tmpdir):
8080
annotation_status="InProgress",
8181
project_folder="folder2"
8282
)
83-
assert 'Folder not found' in str(e)
83+
assert 'Folder not found' in str(e)
84+
85+
folder_metadata = sa.create_folder(project, "folder2")
86+
assert folder_metadata["name"] == "folder2"
87+
88+
folders = sa.search_folders(project)
89+
assert len(folders) == 2
90+
91+
folders = sa.search_folders(project, folder_name="folder")
92+
assert len(folders) == 2
93+
94+
folders = sa.search_folders(project, folder_name="folder2")
95+
assert len(folders) == 1
96+
assert folders[0] == "folder2"
97+
98+
folders = sa.search_folders(project, folder_name="folder1")
99+
assert len(folders) == 1
100+
assert folders[0] == "folder1"
101+
102+
folders = sa.search_folders(project, folder_name="old")
103+
assert len(folders) == 2
104+
105+
106+
def test_folder_annotations(tmpdir):
107+
tmpdir = Path(tmpdir)
108+
109+
projects_found = sa.search_projects(PROJECT_NAME2, return_metadata=True)
110+
for pr in projects_found:
111+
sa.delete_project(pr)
112+
113+
project = sa.create_project(PROJECT_NAME2, 'test', 'Vector')
114+
sa.upload_images_from_folder_to_project(
115+
project, FROM_FOLDER, annotation_status="InProgress"
116+
)
117+
sa.create_annotation_classes_from_classes_json(
118+
project, FROM_FOLDER / "classes" / "classes.json"
119+
)
120+
folder_metadata = sa.create_folder(project, "folder1")
121+
assert folder_metadata["name"] == "folder1"
122+
123+
folders = sa.search_folders(project, return_metadata=True)
124+
assert len(folders) == 1
125+
126+
sa.upload_images_from_folder_to_project(
127+
project,
128+
FROM_FOLDER,
129+
annotation_status="InProgress",
130+
project_folder=folders[0]
131+
)
132+
sa.upload_annotations_from_folder_to_project(
133+
project, FROM_FOLDER, project_folder=folders[0]
134+
)
135+
print(folders[0])
136+
annot = sa.get_image_annotations(project, "example_image_1.jpg")
137+
assert len(annot["annotation_json"]["instances"]) == 0
138+
139+
annot = sa.get_image_annotations(
140+
project, "example_image_1.jpg", project_folder="folder1"
141+
)
142+
assert len(annot["annotation_json"]["instances"]) > 0

0 commit comments

Comments
 (0)