Skip to content

Commit 6191ddc

Browse files
authored
Merge pull request #162 from superannotateai/use_refactor
Added UploadImagesFromFolderToProject
2 parents 40ec96d + 362f6aa commit 6191ddc

File tree

6 files changed

+317
-196
lines changed

6 files changed

+317
-196
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

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

Lines changed: 50 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,118 +1415,26 @@ def upload_images_from_folder_to_project(
14151415
:return: uploaded, could-not-upload, existing-images filepaths
14161416
:rtype: tuple (3 members) of list of strs
14171417
"""
1418-
uploaded_image_entities = []
1419-
failed_images = []
1420-
project_name, folder_name = extract_project_folder(project)
1421-
ProcessedImage = namedtuple("ProcessedImage", ["uploaded", "path", "entity"])
1422-
1423-
def _upload_local_image(image_path: str):
1424-
with open(image_path, "rb") as image:
1425-
image_bytes = BytesIO(image.read())
1426-
upload_response = controller.upload_image_to_s3(
1427-
project_name=project_name,
1428-
image_path=image_path,
1429-
image_bytes=image_bytes,
1430-
folder_name=folder_name,
1431-
image_quality_in_editor=image_quality_in_editor,
1432-
)
1433-
1434-
if not upload_response.errors and upload_response.data:
1435-
entity = upload_response.data
1436-
return ProcessedImage(uploaded=True, path=entity.path, entity=entity)
1437-
else:
1438-
return ProcessedImage(uploaded=False, path=image_path, entity=None)
1439-
1440-
def _upload_s3_image(image_path: str):
1441-
try:
1442-
image_bytes = controller.get_image_from_s3(
1443-
s3_bucket=from_s3_bucket, image_path=image_path
1444-
).data
1445-
except AppValidationException as e:
1446-
logger.warning(e)
1447-
return image_path
1448-
upload_response = controller.upload_image_to_s3(
1449-
project_name=project_name,
1450-
image_path=image_path,
1451-
image_bytes=image_bytes,
1452-
folder_name=folder_name,
1453-
image_quality_in_editor=image_quality_in_editor,
1454-
)
1455-
if not upload_response.errors and upload_response.data:
1456-
entity = upload_response.data
1457-
return ProcessedImage(uploaded=True, path=entity.path, entity=entity)
1458-
else:
1459-
return ProcessedImage(uploaded=False, path=image_path, entity=None)
1460-
1461-
paths = []
1462-
if from_s3_bucket is None:
1463-
for extension in extensions:
1464-
if recursive_subfolders:
1465-
paths += list(Path(folder_path).rglob(f"*.{extension.lower()}"))
1466-
if os.name != "nt":
1467-
paths += list(Path(folder_path).rglob(f"*.{extension.upper()}"))
1468-
else:
1469-
paths += list(Path(folder_path).glob(f"*.{extension.lower()}"))
1470-
if os.name != "nt":
1471-
paths += list(Path(folder_path).glob(f"*.{extension.upper()}"))
1472-
1473-
else:
1474-
s3_client = boto3.client("s3")
1475-
paginator = s3_client.get_paginator("list_objects_v2")
1476-
response_iterator = paginator.paginate(
1477-
Bucket=from_s3_bucket, Prefix=folder_path
1478-
)
1479-
for response in response_iterator:
1480-
for object_data in response["Contents"]:
1481-
key = object_data["Key"]
1482-
if not recursive_subfolders and "/" in key[len(folder_path) + 1 :]:
1483-
continue
1484-
for extension in extensions:
1485-
if key.endswith(f".{extension.lower()}") or key.endswith(
1486-
f".{extension.upper()}"
1487-
):
1488-
paths.append(key)
1489-
break
1490-
1491-
filtered_paths = []
1492-
for path in paths:
1493-
not_in_exclude_list = [x not in Path(path).name for x in exclude_file_patterns]
1494-
if all(not_in_exclude_list):
1495-
filtered_paths.append(path)
14961418

1497-
duplication_counter = Counter(filtered_paths)
1498-
images_to_upload, duplicated_images = (
1499-
set(filtered_paths),
1500-
[item for item in duplication_counter if duplication_counter[item] > 1],
1419+
project_name, folder_name = extract_project_folder(project)
1420+
use_case = controller.upload_images_from_folder_to_project(
1421+
project_name=project_name,
1422+
folder_name=folder_name,
1423+
folder_path=folder_path,
1424+
extensions=extensions,
1425+
annotation_status=annotation_status,
1426+
from_s3_bucket=from_s3_bucket,
1427+
exclude_file_patterns=exclude_file_patterns,
1428+
recursive_sub_folders=recursive_subfolders,
1429+
image_quality_in_editor=image_quality_in_editor,
15011430
)
1502-
upload_method = _upload_s3_image if from_s3_bucket else _upload_local_image
1503-
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
1504-
results = [
1505-
executor.submit(upload_method, image_path)
1506-
for image_path in images_to_upload
1507-
]
1431+
images_to_upload, _ = use_case.images_to_upload
1432+
if use_case.is_valid():
15081433
with tqdm(total=len(images_to_upload)) as progress_bar:
1509-
for future in concurrent.futures.as_completed(results):
1510-
processed_image = future.result()
1511-
if processed_image.uploaded and processed_image.entity:
1512-
uploaded_image_entities.append(processed_image.entity)
1513-
else:
1514-
failed_images.append(processed_image.path)
1434+
for _ in use_case.execute():
15151435
progress_bar.update(1)
1516-
uploaded = []
1517-
duplicates = []
1518-
for i in range(0, len(uploaded_image_entities), 500):
1519-
response = controller.upload_images(
1520-
project_name=project_name,
1521-
folder_name=folder_name,
1522-
images=uploaded_image_entities[i : i + 500], # noqa: E203
1523-
annotation_status=annotation_status,
1524-
)
1525-
attachments, duplications = response.data
1526-
uploaded.extend(attachments)
1527-
duplicates.extend(duplications)
1528-
1529-
return uploaded, failed_images, duplicates
1436+
return use_case.data
1437+
raise AppValidationException(use_case.response.errors)
15301438

15311439

15321440
@Trackable
@@ -1782,28 +1690,6 @@ def upload_videos_from_folder_to_project(
17821690
"""
17831691

17841692
project_name, folder_name = extract_project_folder(project)
1785-
project = controller.get_project_metadata(project_name).data
1786-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
1787-
raise AppValidationException(
1788-
"The function does not support projects containing videos attached with URLs"
1789-
)
1790-
uploaded_image_entities = []
1791-
failed_images = []
1792-
1793-
def _upload_image(image_path: str) -> str:
1794-
with open(image_path, "rb") as image:
1795-
image_bytes = BytesIO(image.read())
1796-
upload_response = controller.upload_image_to_s3(
1797-
project_name=project_name,
1798-
image_path=image_path,
1799-
image_bytes=image_bytes,
1800-
folder_name=folder_name,
1801-
image_quality_in_editor=image_quality_in_editor,
1802-
)
1803-
if not upload_response.errors:
1804-
uploaded_image_entities.append(upload_response.data)
1805-
else:
1806-
return image_path
18071693

18081694
video_paths = []
18091695
for extension in extensions:
@@ -1821,7 +1707,9 @@ def _upload_image(image_path: str) -> str:
18211707
not_in_exclude_list = [x not in Path(path).name for x in exclude_file_patterns]
18221708
if all(not_in_exclude_list):
18231709
filtered_paths.append(path)
1824-
for path in video_paths:
1710+
1711+
uploaded_images, failed_images = [], []
1712+
for path in tqdm(video_paths):
18251713
with tempfile.TemporaryDirectory() as temp_path:
18261714
res = controller.extract_video_frames(
18271715
project_name=project_name,
@@ -1834,25 +1722,25 @@ def _upload_image(image_path: str) -> str:
18341722
annotation_status=annotation_status,
18351723
image_quality_in_editor=image_quality_in_editor,
18361724
)
1837-
if not res.errors:
1838-
extracted_frame_paths = res.data
1839-
# with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
1840-
# for image_path in extracted_frame_paths:
1841-
# failed_images.append(executor.submit(_upload_image, image_path))
1842-
for image_path in extracted_frame_paths:
1843-
failed_images.append(_upload_image(image_path))
1844-
for i in range(0, len(uploaded_image_entities), 500):
1845-
controller.upload_images(
1846-
project_name=project_name,
1847-
folder_name=folder_name,
1848-
images=uploaded_image_entities[i : i + 500], # noqa: E203
1849-
annotation_status=annotation_status,
1850-
)
1851-
uploaded_images = [
1852-
image.path
1853-
for image in uploaded_image_entities
1854-
if image.name not in failed_images
1855-
]
1725+
if res.errors:
1726+
raise AppException(res.errors)
1727+
use_case = controller.upload_images_from_folder_to_project(
1728+
project_name=project_name,
1729+
folder_name=folder_name,
1730+
folder_path=temp_path,
1731+
annotation_status=annotation_status,
1732+
image_quality_in_editor=image_quality_in_editor,
1733+
)
1734+
images_to_upload, _ = use_case.images_to_upload
1735+
if use_case.is_valid():
1736+
for _ in use_case.execute():
1737+
pass
1738+
uploaded, failed_images, _ = use_case.data
1739+
uploaded_images.append(uploaded)
1740+
failed_images.append(failed_images)
1741+
else:
1742+
raise AppValidationException(use_case.response.errors)
1743+
18561744
return uploaded_images, failed_images
18571745

18581746

@@ -1892,28 +1780,6 @@ def upload_video_to_project(
18921780
"""
18931781

18941782
project_name, folder_name = extract_project_folder(project)
1895-
project = controller.get_project_metadata(project_name).data
1896-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
1897-
raise AppValidationException(
1898-
"The function does not support projects containing videos attached with URLs"
1899-
)
1900-
uploaded_image_entities = []
1901-
failed_images = []
1902-
1903-
def _upload_image(image_path: str) -> str:
1904-
with open(image_path, "rb") as image:
1905-
image_bytes = BytesIO(image.read())
1906-
upload_response = controller.upload_image_to_s3(
1907-
project_name=project_name,
1908-
image_path=image_path,
1909-
image_bytes=image_bytes,
1910-
folder_name=folder_name,
1911-
)
1912-
if not upload_response.errors:
1913-
uploaded_image_entities.append(upload_response.data)
1914-
else:
1915-
return image_path
1916-
19171783
with tempfile.TemporaryDirectory() as temp_path:
19181784
res = controller.extract_video_frames(
19191785
project_name=project_name,
@@ -1926,25 +1792,22 @@ def _upload_image(image_path: str) -> str:
19261792
annotation_status=annotation_status,
19271793
image_quality_in_editor=image_quality_in_editor,
19281794
)
1929-
if not res.errors:
1930-
extracted_frame_paths = res.data
1931-
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
1932-
for image_path in extracted_frame_paths:
1933-
failed_images.append(executor.submit(_upload_image, image_path))
1934-
1935-
for i in range(0, len(uploaded_image_entities), 500):
1936-
controller.upload_images(
1795+
if res.errors:
1796+
raise AppException(res.errors)
1797+
use_case = controller.upload_images_from_folder_to_project(
19371798
project_name=project_name,
19381799
folder_name=folder_name,
1939-
images=uploaded_image_entities[i : i + 500], # noqa: E203
1800+
folder_path=temp_path,
19401801
annotation_status=annotation_status,
1802+
image_quality_in_editor=image_quality_in_editor,
19411803
)
1942-
uploaded_images = [
1943-
image.name
1944-
for image in uploaded_image_entities
1945-
if image.name not in failed_images
1946-
]
1947-
return uploaded_images
1804+
images_to_upload, _ = use_case.images_to_upload
1805+
if use_case.is_valid():
1806+
with tqdm(total=len(images_to_upload)) as progress_bar:
1807+
for _ in use_case.execute():
1808+
progress_bar.update(1)
1809+
return use_case.data[0]
1810+
raise AppValidationException(use_case.response.errors)
19481811

19491812

19501813
@Trackable

0 commit comments

Comments
 (0)