Skip to content

Commit 99eec3d

Browse files
authored
Merge pull request #177 from superannotateai/re-design-sdk
Re design sdk
2 parents b50387e + d3cb6be commit 99eec3d

File tree

8 files changed

+335
-102
lines changed

8 files changed

+335
-102
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/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import os
21
import logging.config
2+
import os
33

4-
from superannotate.version import __version__
54
from superannotate.lib.app.analytics.class_analytics import attribute_distribution
65
from superannotate.lib.app.analytics.class_analytics import class_distribution
76
from superannotate.lib.app.annotation_helpers import add_annotation_bbox_to_json
@@ -155,6 +154,7 @@
155154
from superannotate.lib.app.interface.sdk_interface import (
156155
upload_videos_from_folder_to_project,
157156
)
157+
from superannotate.version import __version__
158158

159159

160160
__all__ = [

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

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -179,17 +179,13 @@ def create_project(project_name: str, project_description: str, project_type: st
179179
:return: dict object metadata the new project
180180
:rtype: dict
181181
"""
182-
projects = controller.search_project(name=project_name).data
183-
if projects:
184-
raise AppException(
185-
f"Project with name {project_name} already exists."
186-
f" Please use unique names for projects to use with SDK."
187-
)
188-
189-
result = controller.create_project(
182+
response = controller.create_project(
190183
name=project_name, description=project_description, project_type=project_type
191-
).data
192-
return ProjectSerializer(result).serialize()
184+
)
185+
if response.errors:
186+
raise Exception(response.errors)
187+
188+
return ProjectSerializer(response.data).serialize()
193189

194190

195191
@Trackable
@@ -672,6 +668,8 @@ def _upload_image(image_url, image_path) -> ProcessedImage:
672668
else:
673669
failed_images.append(processed_image)
674670

671+
logger.info("Downloading %s images", len(images_to_upload))
672+
675673
for i in range(0, len(images_to_upload), 500):
676674
controller.upload_images(
677675
project_name=project_name,
@@ -886,7 +884,9 @@ def get_project_settings(project: Union[str, dict]):
886884
"""
887885
project_name, folder_name = extract_project_folder(project)
888886
settings = controller.get_project_settings(project_name=project_name)
889-
settings = [BaseSerializers(attribute).serialize() for attribute in settings.data]
887+
settings = [
888+
SettingsSerializer(attribute).serialize() for attribute in settings.data
889+
]
890890
return settings
891891

892892

@@ -1099,6 +1099,9 @@ def delete_images(project: Union[str, dict], image_names: Optional[List[str]] =
10991099
"""
11001100
project_name, folder_name = extract_project_folder(project)
11011101

1102+
if not isinstance(image_names, list) and image_names is not None:
1103+
raise AppValidationException("Image_names should be a list of strs or None.")
1104+
11021105
response = controller.delete_images(
11031106
project_name=project_name, folder_name=folder_name, image_names=image_names
11041107
)
@@ -1143,9 +1146,6 @@ def assign_images(project: Union[str, dict], image_names: List[str], user: str):
11431146
logger.warning(
11441147
f"Skipping {user}. {user} is not a verified contributor for the {project_name}"
11451148
)
1146-
1147-
1148-
11491149
return
11501150

11511151
controller.assign_images(project_name, folder_name, image_names, user)
@@ -1204,9 +1204,27 @@ def assign_folder(project_name: str, folder_name: str, users: List[str]):
12041204
:param users: list of user emails
12051205
:type users: list of str
12061206
"""
1207+
1208+
contributors = (
1209+
controller.get_project_metadata(
1210+
project_name=project_name, include_contributors=True
1211+
)
1212+
.data["project"]
1213+
.users
1214+
)
1215+
verified_users = [i["user_id"] for i in contributors]
1216+
verified_users = set(users).intersection(set(verified_users))
1217+
unverified_contributor = set(users) - verified_users
1218+
1219+
for user in unverified_contributor:
1220+
logger.warning(
1221+
f"Skipping {user} from assignees. {user} is not a verified contributor for the {project_name}"
1222+
)
1223+
12071224
response = controller.assign_folder(
1208-
project_name=project_name, folder_name=folder_name, users=users
1225+
project_name=project_name, folder_name=folder_name, users=list(verified_users)
12091226
)
1227+
12101228
if response.errors:
12111229
raise AppException(response.errors)
12121230

@@ -1226,10 +1244,15 @@ def share_project(project_name: str, user: Union[str, dict], user_role: str):
12261244
if isinstance(user, dict):
12271245
user_id = user["id"]
12281246
else:
1229-
user_id = controller.search_team_contributors(email=user).data[0]["id"]
1230-
controller.share_project(
1247+
response = controller.search_team_contributors(email=user)
1248+
if not response.data:
1249+
raise AppException(f"User {user} not found.")
1250+
user_id = response.data[0]["id"]
1251+
response = controller.share_project(
12311252
project_name=project_name, user_id=user_id, user_role=user_role
12321253
)
1254+
if response.errors:
1255+
raise AppException(response.errors)
12331256

12341257

12351258
@Trackable
@@ -1814,6 +1837,9 @@ def upload_videos_from_folder_to_project(
18141837
if os.name != "nt":
18151838
video_paths += list(Path(folder_path).glob(f"*.{extension.upper()}"))
18161839
else:
1840+
logger.warning(
1841+
"When using recursive subfolder parsing same name videos in different subfolders will overwrite each other."
1842+
)
18171843
video_paths += list(Path(folder_path).rglob(f"*.{extension.lower()}"))
18181844
if os.name != "nt":
18191845
video_paths += list(Path(folder_path).rglob(f"*.{extension.upper()}"))
@@ -1824,6 +1850,14 @@ def upload_videos_from_folder_to_project(
18241850
if all(not_in_exclude_list):
18251851
filtered_paths.append(path)
18261852

1853+
logger.info(
1854+
"Uploading all videos with extensions %s from %s to project %s. Excluded file patterns are: %s.",
1855+
extensions,
1856+
str(folder_path),
1857+
project_name,
1858+
exclude_file_patterns,
1859+
)
1860+
18271861
uploaded_images, failed_images = [], []
18281862
for path in tqdm(video_paths):
18291863
with tempfile.TemporaryDirectory() as temp_path:
@@ -2173,6 +2207,7 @@ def _upload_file_to_s3(to_s3_bucket, path, s3_key) -> None:
21732207

21742208
for future in concurrent.futures.as_completed(results):
21752209
future.result()
2210+
logger.info("Exported to AWS %s/%s", to_s3_bucket, str(path))
21762211

21772212

21782213
@Trackable
@@ -2569,6 +2604,7 @@ def upload_image_annotations(
25692604
image_name=image_name,
25702605
annotations=annotation_json,
25712606
mask=mask,
2607+
verbose=verbose,
25722608
)
25732609
if response.errors:
25742610
raise AppValidationException(response.errors)
@@ -3485,6 +3521,9 @@ def _upload_s3_image(image_path: str):
34853521
progress_bar.update(1)
34863522
uploaded = []
34873523
duplicates = []
3524+
3525+
logger.info("Uploading %s images to project.", len(images_to_upload))
3526+
34883527
for i in range(0, len(uploaded_image_entities), 500):
34893528
response = controller.upload_images(
34903529
project_name=project_name,
@@ -3496,6 +3535,11 @@ def _upload_s3_image(image_path: str):
34963535
uploaded.extend(attachments)
34973536
duplicates.extend(duplications)
34983537

3538+
if len(duplicates):
3539+
logger.warning(
3540+
"%s already existing images found that won't be uploaded.", len(duplicates)
3541+
)
3542+
34993543
return uploaded, failed_images, duplicates
35003544

35013545

src/superannotate/lib/core/plugin.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import io
2+
import logging
23
from pathlib import Path
34
from typing import List
45
from typing import Tuple
@@ -10,6 +11,8 @@
1011
from PIL import ImageDraw
1112
from PIL import ImageOps
1213

14+
logger = logging.getLogger()
15+
1316

1417
class ImagePlugin:
1518
def __init__(self, image_bytes: io.BytesIO, max_resolution: int = 4096):
@@ -185,8 +188,18 @@ def get_video_rotate_code(video_path):
185188
meta_dict = ffmpeg.probe(str(video_path))
186189
rot = int(meta_dict["streams"][0]["tags"]["rotate"])
187190
if rot:
191+
logger.info(
192+
"Frame rotation of %s found. Output images will be rotated accordingly.",
193+
rot,
194+
)
188195
return cv2_rotations[rot]
189-
except Exception:
196+
except Exception as e:
197+
warning_str = ""
198+
if "ffprobe" in str(e):
199+
warning_str = "This could be because ffmpeg package is not installed. To install it, run: sudo apt install ffmpeg"
200+
logger.warning(
201+
"Couldn't read video metadata to determine rotation. %s", warning_str
202+
)
190203
return
191204

192205
@staticmethod
@@ -202,8 +215,25 @@ def extract_frames(
202215
if not video.isOpened():
203216
return []
204217
frames_count = VideoPlugin.get_frames_count(video_path)
218+
logger.info("Video frame count is %s.", frames_count)
219+
205220
fps = video.get(cv2.CAP_PROP_FPS)
206-
ratio = fps / target_fps if target_fps else 1
221+
if target_fps > fps:
222+
logger.warning(
223+
"Video frame rate %s smaller than target frame rate %s. Cannot change frame rate.",
224+
fps,
225+
target_fps,
226+
)
227+
target_fps = fps
228+
229+
else:
230+
logger.info(
231+
"Changing video frame rate from %s to target frame rate %s.",
232+
fps,
233+
target_fps,
234+
)
235+
236+
ratio = fps / target_fps
207237
extracted_frames_paths = []
208238
zero_fill_count = len(str(frames_count))
209239

@@ -212,6 +242,8 @@ def extract_frames(
212242
frame_number = 0
213243
extracted_frame_number = 0
214244
extracted_frame_ratio = ratio
245+
logger.info("Extracting frames from video to %s.", extracted_frames_paths)
246+
215247
while len(extracted_frames_paths) < limit:
216248
success, frame = video.read()
217249
if success:

0 commit comments

Comments
 (0)