Skip to content

Commit efcb3bd

Browse files
committed
Search images in subfolders
1 parent 697ca7a commit efcb3bd

File tree

6 files changed

+128
-9
lines changed

6 files changed

+128
-9
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ ______
7575

7676
.. _ref_search_images:
7777
.. autofunction:: superannotate.search_images
78+
.. autofunction:: superannotate.search_images_all_folders
7879
.. autofunction:: superannotate.get_image_metadata
7980
.. autofunction:: superannotate.get_image_bytes
8081
.. autofunction:: superannotate.download_image

superannotate/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ def consensus(*args, **kwargs):
4949
create_fuse_image, delete_image, download_image, download_image_annotations,
5050
download_image_preannotations, get_image_annotations, get_image_bytes,
5151
get_image_metadata, get_image_preannotations, search_images,
52-
set_image_annotation_status, set_images_annotation_statuses,
53-
upload_image_annotations
52+
search_images_all_folders, set_image_annotation_status,
53+
set_images_annotation_statuses, upload_image_annotations
5454
)
5555
from .db.project_api import (
5656
create_folder, delete_folders, get_folder_metadata,

superannotate/db/images.py

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,32 @@
2424
get_annotation_classes_id_to_name, get_annotation_classes_name_to_id,
2525
search_annotation_classes
2626
)
27-
from .project_api import get_project_and_folder_metadata
27+
from .project_api import get_project_and_folder_metadata, get_project_metadata_bare
2828

2929
logger = logging.getLogger("superannotate-python-sdk")
3030

3131
_api = API.get_instance()
3232

3333

34+
def _get_project_root_folder_id(project):
35+
"""Get root folder ID
36+
Returns
37+
-------
38+
int
39+
Root folder ID
40+
"""
41+
params = {'team_id': project['team_id']}
42+
response = _api.send_request(
43+
req_type='GET', path=f'/project/{project["id"]}', params=params
44+
)
45+
if not response.ok:
46+
raise SABaseException(response.status_code, response.text)
47+
48+
response = response.json()
49+
50+
return response['folder_id']
51+
52+
3453
def search_images(
3554
project,
3655
image_name_prefix=None,
@@ -63,7 +82,7 @@ def search_images(
6382
if project_folder is not None:
6483
project_folder_id = project_folder["id"]
6584
else:
66-
project_folder_id = None
85+
project_folder_id = _get_project_root_folder_id(project)
6786

6887
result_list = []
6988
params = {
@@ -79,7 +98,7 @@ def search_images(
7998
total_images = 0
8099
while True:
81100
response = _api.send_request(
82-
req_type='GET', path='/images', params=params
101+
req_type='GET', path='/images-folders', params=params
83102
)
84103
if not response.ok:
85104
raise SABaseException(
@@ -115,6 +134,83 @@ def process_result(x):
115134
return result_list
116135

117136

137+
def search_images_all_folders(
138+
project,
139+
image_name_prefix=None,
140+
annotation_status=None,
141+
return_metadata=False
142+
):
143+
"""Search images by name_prefix (case-insensitive) and annotation status in
144+
project and all of its folders
145+
146+
:param project: project name
147+
:type project: str
148+
:param image_name_prefix: image name prefix for search
149+
:type image_name_prefix: str
150+
:param annotation_status: if not None, annotation statuses of images to filter,
151+
should be one of NotStarted InProgress QualityCheck Returned Completed Skipped
152+
:type annotation_status: str
153+
154+
:param return_metadata: return metadata of images instead of names
155+
:type return_metadata: bool
156+
157+
:return: metadata of found images or image names
158+
:rtype: list of dicts or strs
159+
"""
160+
project = get_project_metadata_bare(project)
161+
team_id, project_id = project["team_id"], project["id"]
162+
if annotation_status is not None:
163+
annotation_status = common.annotation_status_str_to_int(
164+
annotation_status
165+
)
166+
167+
project_folder_id = _get_project_root_folder_id(project)
168+
169+
result_list = []
170+
params = {
171+
'team_id': team_id,
172+
'project_id': project_id,
173+
'annotation_status': annotation_status,
174+
'offset': 0,
175+
'folder_id': project_folder_id
176+
}
177+
if image_name_prefix is not None:
178+
params['name'] = image_name_prefix
179+
total_images = 0
180+
while True:
181+
response = _api.send_request(
182+
req_type='GET', path='/images', params=params
183+
)
184+
if not response.ok:
185+
raise SABaseException(
186+
response.status_code, "Couldn't search images " + response.text
187+
)
188+
response = response.json()
189+
results_images = response["data"]
190+
for r in results_images:
191+
if return_metadata:
192+
result_list.append(r)
193+
else:
194+
result_list.append(r["name"])
195+
196+
total_images += len(results_images)
197+
if response["count"] <= total_images:
198+
break
199+
params["offset"] = total_images
200+
201+
if return_metadata:
202+
203+
def process_result(x):
204+
x["annotation_status"] = common.annotation_status_int_to_str(
205+
x["annotation_status"]
206+
)
207+
return x
208+
209+
return list(map(process_result, result_list))
210+
else:
211+
return result_list
212+
213+
118214
def get_image_metadata(project, image_names, return_dict_on_single_output=True):
119215
"""Returns image metadata
120216

superannotate/db/projects.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
fill_class_and_attribute_ids, get_annotation_classes_name_to_id,
3434
search_annotation_classes
3535
)
36-
from .images import get_image_metadata, search_images
36+
from .images import get_image_metadata, search_images, search_images_all_folders
3737
from .project_api import (
3838
get_project_and_folder_metadata, get_project_metadata_bare,
3939
get_project_metadata_with_users
@@ -186,16 +186,21 @@ def rename_project(project, new_name):
186186
)
187187

188188

189-
def get_project_image_count(project):
189+
def get_project_image_count(project, with_all_subfolders=False):
190190
"""Returns number of images in the project.
191191
192192
:param project: project name or folder path (e.g., "project1/folder1")
193193
:type project: str
194+
:param with_all_subfolders: enables recursive folder counting
195+
:type with_all_subfolders: bool
194196
195197
:return: number of images in the project
196198
:rtype: int
197199
"""
198-
return len(search_images(project))
200+
if not with_all_subfolders:
201+
return len(search_images(project))
202+
else:
203+
return len(search_images_all_folders(project))
199204

200205

201206
def upload_video_to_project(

superannotate/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.1.0b11"
1+
__version__ = "4.1.0b12"

tests/test_folders.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ def test_basic_folders(tmpdir):
4242
images = sa.search_images(project + "/folder1", "example_image_1")
4343
assert len(images) == 0
4444

45+
images = sa.search_images_all_folders(project, "example_image_1")
46+
assert len(images) == 1
47+
4548
folder = sa.get_folder_metadata(project, "folder1")
4649
assert isinstance(folder, dict)
4750
assert folder["name"] == "folder1"
@@ -210,6 +213,9 @@ def test_project_folder_image_count(tmpdir):
210213
num_images = sa.get_project_image_count(project + "/folder1")
211214
assert num_images == 4
212215

216+
num_images = sa.get_project_image_count(project, with_all_subfolders=True)
217+
assert num_images == 8
218+
213219

214220
def test_delete_images(tmpdir):
215221
PROJECT_NAME = "test delete folder images"
@@ -319,6 +325,17 @@ def test_move_images(tmpdir):
319325
num_images = sa.get_project_image_count(project)
320326
assert num_images == 3
321327

328+
num_images = sa.get_project_image_count(
329+
PROJECT_NAME, with_all_subfolders=True
330+
)
331+
assert num_images == 4
332+
333+
images = sa.search_images_all_folders(PROJECT_NAME)
334+
assert images == [
335+
"example_image_1.jpg", "example_image_2.jpg", "example_image_3.jpg",
336+
"example_image_4.jpg"
337+
]
338+
322339

323340
def test_move_images2(tmpdir):
324341
PROJECT_NAME = "test move folder images2"

0 commit comments

Comments
 (0)