Skip to content

Commit 8cc64db

Browse files
committed
merge friday
2 parents 6a1a6ce + e8510ae commit 8cc64db

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2097
-388
lines changed

src/superannotate/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,4 +304,3 @@
304304
logging.config.fileConfig(
305305
os.path.join(WORKING_DIR, "logging.conf"), disable_existing_loggers=False
306306
)
307-
sys.tracebacklimit = 0

src/superannotate/lib/app/analytics/common.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ def aggregate_annotations_as_df(
212212
"updatorRole": [],
213213
"updatorEmail": [],
214214
"folderName": [],
215+
"imageAnnotator": [],
216+
"imageQA": [],
215217
}
216218

217219
if include_comments:
@@ -255,6 +257,8 @@ def __get_image_metadata(image_name, annotations):
255257
image_metadata["imageWidth"] = annotations["metadata"].get("width")
256258
image_metadata["imageStatus"] = annotations["metadata"].get("status")
257259
image_metadata["imagePinned"] = annotations["metadata"].get("pinned")
260+
image_metadata["imageAnnotator"] = annotations["metadata"].get("annotatorEmail")
261+
image_metadata["imageQA"] = annotations["metadata"].get("qaEmail")
258262
return image_metadata
259263

260264
def __get_user_metadata(annotation):

src/superannotate/lib/app/helpers.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import boto3
1010
import pandas as pd
1111
from superannotate.lib.app.exceptions import PathError
12+
from superannotate.lib.core import ATTACHED_VIDEO_ANNOTATION_POSTFIX
1213
from superannotate.lib.core import PIXEL_ANNOTATION_POSTFIX
1314
from superannotate.lib.core import VECTOR_ANNOTATION_POSTFIX
1415

@@ -55,7 +56,13 @@ def get_local_annotation_paths(
5556
[
5657
str(i)
5758
for i in all_not_folders
58-
if i.name.endswith((VECTOR_ANNOTATION_POSTFIX, PIXEL_ANNOTATION_POSTFIX))
59+
if i.name.endswith(
60+
(
61+
VECTOR_ANNOTATION_POSTFIX,
62+
PIXEL_ANNOTATION_POSTFIX,
63+
ATTACHED_VIDEO_ANNOTATION_POSTFIX,
64+
)
65+
)
5966
]
6067
)
6168
if recursive:
@@ -82,8 +89,10 @@ def get_s3_annotation_paths(folder_path, s3_bucket, annotation_paths, recursive)
8289
for data in paginator.paginate(Bucket=s3_bucket, Prefix=folder_path):
8390
for annotation in data["Contents"]:
8491
key = annotation["Key"]
85-
if key.endswith(VECTOR_ANNOTATION_POSTFIX) or key.endswith(
86-
PIXEL_ANNOTATION_POSTFIX
92+
if (
93+
key.endswith(VECTOR_ANNOTATION_POSTFIX)
94+
or key.endswith(PIXEL_ANNOTATION_POSTFIX)
95+
or key.endswith(ATTACHED_VIDEO_ANNOTATION_POSTFIX)
8796
):
8897
if not recursive and "/" in key[len(folder_path) + 1 :]:
8998
continue

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

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
from lib.app.interface.types import ImageQualityChoices
3232
from lib.app.interface.types import NotEmptyStr
3333
from lib.app.interface.types import ProjectTypes
34-
from lib.app.interface.types import Status
3534
from lib.app.interface.types import validate_arguments
3635
from lib.app.mixp.decorators import Trackable
3736
from lib.app.serializers import BaseSerializers
@@ -282,7 +281,7 @@ def clone_project(
282281
def search_images(
283282
project: Union[NotEmptyStr, dict],
284283
image_name_prefix: Optional[NotEmptyStr] = None,
285-
annotation_status: Optional[Status] = None,
284+
annotation_status: Optional[AnnotationStatuses] = None,
286285
return_metadata: Optional[StrictBool] = False,
287286
):
288287
"""Search images by name_prefix (case-insensitive) and annotation status
@@ -335,10 +334,6 @@ def create_folder(project: NotEmptyStr, folder_name: NotEmptyStr):
335334
res = controller.create_folder(project=project, folder_name=folder_name)
336335
if res.data:
337336
folder = res.data
338-
if folder and folder.name != folder_name:
339-
logger.warning(
340-
f"Created folder has name {folder.name}, since folder with name {folder_name} already existed.",
341-
)
342337
logger.info(f"Folder {folder.name} created in project {project}")
343338
return folder.to_dict()
344339
if res.errors:
@@ -605,7 +600,7 @@ def upload_images_from_public_urls_to_project(
605600
project: Union[NotEmptyStr, dict],
606601
img_urls: List[NotEmptyStr],
607602
img_names: Optional[List[NotEmptyStr]] = None,
608-
annotation_status: Optional[Status] = "NotStarted",
603+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
609604
image_quality_in_editor: Optional[NotEmptyStr] = None,
610605
):
611606
"""Uploads all images given in the list of URL strings in img_urls to the project.
@@ -1643,7 +1638,7 @@ def upload_videos_from_folder_to_project(
16431638
target_fps: Optional[int] = None,
16441639
start_time: Optional[float] = 0.0,
16451640
end_time: Optional[float] = None,
1646-
annotation_status: Optional[Status] = "NotStarted",
1641+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
16471642
image_quality_in_editor: Optional[ImageQualityChoices] = None,
16481643
):
16491644
"""Uploads image frames from all videos with given extensions from folder_path to the project.
@@ -1793,7 +1788,7 @@ def upload_video_to_project(
17931788
target_fps: Optional[int] = None,
17941789
start_time: Optional[float] = 0.0,
17951790
end_time: Optional[float] = None,
1796-
annotation_status: Optional[Status] = "NotStarted",
1791+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
17971792
image_quality_in_editor: Optional[ImageQualityChoices] = None,
17981793
):
17991794
"""Uploads image frames from video to platform. Uploaded images will have
@@ -2291,7 +2286,7 @@ def download_image(
22912286
def attach_image_urls_to_project(
22922287
project: Union[NotEmptyStr, dict],
22932288
attachments: Union[str, Path],
2294-
annotation_status: Optional[Status] = "NotStarted",
2289+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
22952290
):
22962291
"""Link images on external storage to SuperAnnotate.
22972292
@@ -2354,7 +2349,7 @@ def attach_image_urls_to_project(
23542349
def attach_video_urls_to_project(
23552350
project: Union[NotEmptyStr, dict],
23562351
attachments: Union[str, Path],
2357-
annotation_status: Optional[Status] = "NotStarted",
2352+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
23582353
):
23592354
"""Link videos on external storage to SuperAnnotate.
23602355
@@ -2443,51 +2438,36 @@ def upload_annotations_from_folder_to_project(
24432438
"""
24442439

24452440
project_name, folder_name = extract_project_folder(project)
2446-
project = controller.get_project_metadata(project_name).data
2447-
if project["project"].project_type in [
2448-
constances.ProjectType.VIDEO.value,
2449-
constances.ProjectType.DOCUMENT.value,
2450-
]:
2451-
raise AppException(LIMITED_FUNCTIONS[project["project"].project_type])
24522441

24532442
if recursive_subfolders:
24542443
logger.info(
2455-
"When using recursive subfolder parsing same name annotations in different subfolders will overwrite each other.",
2444+
"When using recursive subfolder parsing same name annotations in different "
2445+
"subfolders will overwrite each other.",
24562446
)
2457-
2458-
logger.info(
2459-
"The JSON files should follow specific naming convention. For Vector projects they should be named '<image_name>___objects.json', for Pixel projects JSON file should be names '<image_name>___pixel.json' and also second mask image file should be present with the name '<image_name>___save.png'. In both cases image with <image_name> should be already present on the platform."
2460-
)
2461-
logger.info("Existing annotations will be overwritten.",)
2447+
logger.info("The JSON files should follow a specific naming convention, matching file names already present "
2448+
"on the platform. Existing annotations will be overwritten")
24622449
logger.info(
24632450
"Uploading all annotations from %s to project %s.", folder_path, project_name
24642451
)
24652452

24662453
annotation_paths = get_annotation_paths(
24672454
folder_path, from_s3_bucket, recursive_subfolders
24682455
)
2456+
if not annotation_paths:
2457+
raise AppException("Could not find annotations matching existing items on the platform.")
2458+
24692459
logger.info(
24702460
"Uploading %s annotations to project %s.", len(annotation_paths), project_name
24712461
)
2472-
use_case = controller.upload_annotations_from_folder(
2462+
response = controller.upload_annotations_from_folder(
24732463
project_name=project_name,
24742464
folder_name=folder_name,
2475-
folder_path=folder_path,
24762465
annotation_paths=annotation_paths, # noqa: E203
24772466
client_s3_bucket=from_s3_bucket,
24782467
)
2479-
if use_case.is_valid():
2480-
with tqdm(
2481-
total=len(use_case.annotations_to_upload), desc="Uploading annotations"
2482-
) as progress_bar:
2483-
for _ in use_case.execute():
2484-
progress_bar.update(1)
2485-
else:
2486-
raise AppException(use_case.response.errors)
2487-
if use_case.response.report:
2488-
for i in use_case.response.report_messages:
2489-
logger.info(i)
2490-
return use_case.data
2468+
if response.errors:
2469+
raise AppException(response.errors)
2470+
return response.data
24912471

24922472

24932473
@Trackable
@@ -2548,20 +2528,16 @@ def upload_preannotations_from_folder_to_project(
25482528
logger.info(
25492529
"Uploading %s annotations to project %s.", len(annotation_paths), project_name
25502530
)
2551-
use_case = controller.upload_annotations_from_folder(
2531+
response = controller.upload_annotations_from_folder(
25522532
project_name=project_name,
25532533
folder_name=folder_name,
2554-
folder_path=folder_path,
25552534
annotation_paths=annotation_paths, # noqa: E203
25562535
client_s3_bucket=from_s3_bucket,
25572536
is_pre_annotations=True,
25582537
)
2559-
with tqdm(
2560-
total=len(annotation_paths), desc="Uploading annotations"
2561-
) as progress_bar:
2562-
for _ in use_case.execute():
2563-
progress_bar.update(1)
2564-
return use_case.data
2538+
if response.errors:
2539+
raise AppException(response.errors)
2540+
return response.data
25652541

25662542

25672543
@Trackable
@@ -2570,7 +2546,7 @@ def upload_image_annotations(
25702546
project: Union[NotEmptyStr, dict],
25712547
image_name: str,
25722548
annotation_json: Union[str, Path, dict],
2573-
mask: Optional[Union[str, Path, dict]] = None,
2549+
mask: Optional[Union[str, Path, bytes]] = None,
25742550
verbose: Optional[StrictBool] = True,
25752551
):
25762552
"""Upload annotations from JSON (also mask for pixel annotations)
@@ -2585,15 +2561,17 @@ def upload_image_annotations(
25852561
:param mask: BytesIO object or filepath to mask annotation for pixel projects in SuperAnnotate format
25862562
:type mask: BytesIO or Path-like (str or Path)
25872563
"""
2588-
annotation_path = f"{image_name}___save.png"
2589-
if isinstance(annotation_json, str) or isinstance(annotation_json, Path):
2590-
annotation_path = str(annotation_json).replace("___pixel.json", "___save.png")
2591-
if isinstance(annotation_json, list):
2592-
raise AppException(
2593-
"Annotation JSON should be a dict object. You are using list object."
2594-
" If this is an old annotation format you can convert it to new format with superannotate."
2595-
"update_json_format SDK function"
2596-
)
2564+
if not mask:
2565+
if not isinstance(annotation_json, dict):
2566+
mask_path = str(annotation_json).replace("___pixel.json", "___save.png")
2567+
else:
2568+
mask_path = f"{image_name}___save.png"
2569+
if os.path.exists(mask_path):
2570+
mask = open(mask_path, "rb").read()
2571+
elif isinstance(mask, str) or isinstance(mask, Path):
2572+
if os.path.exists(mask):
2573+
mask = open(mask, "rb").read()
2574+
25972575
if not isinstance(annotation_json, dict):
25982576
if verbose:
25992577
logger.info("Uploading annotations from %s.", annotation_json)
@@ -2606,7 +2584,6 @@ def upload_image_annotations(
26062584
annotations=annotation_json,
26072585
mask=mask,
26082586
verbose=verbose,
2609-
annotation_path=annotation_path,
26102587
)
26112588
if response.errors:
26122589
raise AppException(response.errors)
@@ -3370,7 +3347,7 @@ def upload_image_to_project(
33703347
project: NotEmptyStr,
33713348
img,
33723349
image_name: Optional[NotEmptyStr] = None,
3373-
annotation_status: Optional[Status] = "NotStarted",
3350+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
33743351
from_s3_bucket=None,
33753352
image_quality_in_editor: Optional[NotEmptyStr] = None,
33763353
):
@@ -3445,7 +3422,7 @@ def search_models(
34453422
def upload_images_to_project(
34463423
project: NotEmptyStr,
34473424
img_paths: List[NotEmptyStr],
3448-
annotation_status: Optional[Status] = "NotStarted",
3425+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
34493426
from_s3_bucket=None,
34503427
image_quality_in_editor: Optional[ImageQualityChoices] = None,
34513428
):
@@ -3579,7 +3556,7 @@ def delete_annotations(
35793556
def attach_document_urls_to_project(
35803557
project: Union[NotEmptyStr, dict],
35813558
attachments: Union[Path, NotEmptyStr],
3582-
annotation_status: Optional[Status] = "NotStarted",
3559+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
35833560
):
35843561
"""Link documents on external storage to SuperAnnotate.
35853562

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ def validate(cls, value: Union[str]) -> Union[str]:
1919
if cls.curtail_length and len(value) > cls.curtail_length:
2020
value = value[: cls.curtail_length]
2121
if value.lower() not in AnnotationStatus.values():
22-
raise TypeError(f"Available statuses is {', '.join(AnnotationStatus)}. ")
22+
raise TypeError(
23+
f"Available statuses is {', '.join(AnnotationStatus.titles())}. "
24+
)
2325
return value
2426

2527

src/superannotate/lib/core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
VECTOR_ANNOTATION_POSTFIX = "___objects.json"
4646
PIXEL_ANNOTATION_POSTFIX = "___pixel.json"
4747
ANNOTATION_MASK_POSTFIX = "___save.png"
48+
ATTACHED_VIDEO_ANNOTATION_POSTFIX = ".json"
4849

4950
NON_PLOTABLE_KEYS = ["eta_seconds", "iteration", "data_time", "time", "model"]
5051

0 commit comments

Comments
 (0)