Skip to content

Commit cd7cd0c

Browse files
authored
Merge pull request #250 from superannotateai/develop
Develop
2 parents c02617d + 502225d commit cd7cd0c

File tree

14 files changed

+339
-267
lines changed

14 files changed

+339
-267
lines changed

src/superannotate/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,4 +302,3 @@
302302
logging.config.fileConfig(
303303
os.path.join(WORKING_DIR, "logging.conf"), disable_existing_loggers=False
304304
)
305-
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/interface/sdk_interface.py

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
from lib.app.interface.types import AnnotationType
3131
from lib.app.interface.types import ImageQualityChoices
3232
from lib.app.interface.types import NotEmptyStr
33-
from lib.app.interface.types import Status
3433
from lib.app.interface.types import validate_arguments
3534
from lib.app.mixp.decorators import Trackable
3635
from lib.app.serializers import BaseSerializers
@@ -280,7 +279,7 @@ def clone_project(
280279
def search_images(
281280
project: Union[NotEmptyStr, dict],
282281
image_name_prefix: Optional[NotEmptyStr] = None,
283-
annotation_status: Optional[Status] = None,
282+
annotation_status: Optional[AnnotationStatuses] = None,
284283
return_metadata: Optional[StrictBool] = False,
285284
):
286285
"""Search images by name_prefix (case-insensitive) and annotation status
@@ -333,10 +332,6 @@ def create_folder(project: NotEmptyStr, folder_name: NotEmptyStr):
333332
res = controller.create_folder(project=project, folder_name=folder_name)
334333
if res.data:
335334
folder = res.data
336-
if folder and folder.name != folder_name:
337-
logger.warning(
338-
f"Created folder has name {folder.name}, since folder with name {folder_name} already existed.",
339-
)
340335
logger.info(f"Folder {folder.name} created in project {project}")
341336
return folder.to_dict()
342337
if res.errors:
@@ -603,7 +598,7 @@ def upload_images_from_public_urls_to_project(
603598
project: Union[NotEmptyStr, dict],
604599
img_urls: List[NotEmptyStr],
605600
img_names: Optional[List[NotEmptyStr]] = None,
606-
annotation_status: Optional[Status] = "NotStarted",
601+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
607602
image_quality_in_editor: Optional[NotEmptyStr] = None,
608603
):
609604
"""Uploads all images given in the list of URL strings in img_urls to the project.
@@ -1641,7 +1636,7 @@ def upload_videos_from_folder_to_project(
16411636
target_fps: Optional[int] = None,
16421637
start_time: Optional[float] = 0.0,
16431638
end_time: Optional[float] = None,
1644-
annotation_status: Optional[Status] = "NotStarted",
1639+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
16451640
image_quality_in_editor: Optional[str] = None,
16461641
):
16471642
"""Uploads image frames from all videos with given extensions from folder_path to the project.
@@ -1705,11 +1700,7 @@ def upload_videos_from_folder_to_project(
17051700
project_folder_name = project_name + (f"/{folder_name}" if folder_name else "")
17061701

17071702
logger.info(
1708-
"Uploading all videos with extensions %s from %s to project %s. Excluded file patterns are: %s.",
1709-
extensions,
1710-
str(folder_path),
1711-
project_name,
1712-
exclude_file_patterns,
1703+
f"Uploading all videos with extensions {extensions} from {str(folder_path)} to project {project_name}. Excluded file patterns are: {[*exclude_file_patterns]}.",
17131704
)
17141705
uploaded_paths = []
17151706
for path in video_paths:
@@ -1778,7 +1769,7 @@ def upload_video_to_project(
17781769
target_fps: Optional[int] = None,
17791770
start_time: Optional[float] = 0.0,
17801771
end_time: Optional[float] = None,
1781-
annotation_status: Optional[Status] = "NotStarted",
1772+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
17821773
image_quality_in_editor: Optional[NotEmptyStr] = None,
17831774
):
17841775
"""Uploads image frames from video to platform. Uploaded images will have
@@ -2235,7 +2226,7 @@ def download_image(
22352226
def attach_image_urls_to_project(
22362227
project: Union[NotEmptyStr, dict],
22372228
attachments: Union[str, Path],
2238-
annotation_status: Optional[Status] = "NotStarted",
2229+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
22392230
):
22402231
"""Link images on external storage to SuperAnnotate.
22412232
@@ -2298,7 +2289,7 @@ def attach_image_urls_to_project(
22982289
def attach_video_urls_to_project(
22992290
project: Union[NotEmptyStr, dict],
23002291
attachments: Union[str, Path],
2301-
annotation_status: Optional[Status] = "NotStarted",
2292+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
23022293
):
23032294
"""Link videos on external storage to SuperAnnotate.
23042295
@@ -3314,7 +3305,7 @@ def upload_image_to_project(
33143305
project: NotEmptyStr,
33153306
img,
33163307
image_name: Optional[NotEmptyStr] = None,
3317-
annotation_status: Optional[Status] = "NotStarted",
3308+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
33183309
from_s3_bucket=None,
33193310
image_quality_in_editor: Optional[NotEmptyStr] = None,
33203311
):
@@ -3389,7 +3380,7 @@ def search_models(
33893380
def upload_images_to_project(
33903381
project: NotEmptyStr,
33913382
img_paths: List[NotEmptyStr],
3392-
annotation_status: Optional[Status] = "NotStarted",
3383+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
33933384
from_s3_bucket=None,
33943385
image_quality_in_editor: Optional[ImageQualityChoices] = None,
33953386
):
@@ -3523,7 +3514,7 @@ def delete_annotations(
35233514
def attach_document_urls_to_project(
35243515
project: Union[NotEmptyStr, dict],
35253516
attachments: Union[Path, NotEmptyStr],
3526-
annotation_status: Optional[Status] = "NotStarted",
3517+
annotation_status: Optional[AnnotationStatuses] = "NotStarted",
35273518
):
35283519
"""Link documents on external storage to SuperAnnotate.
35293520

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def validate(cls, value: Union[str]) -> Union[str]:
1717
if cls.curtail_length and len(value) > cls.curtail_length:
1818
value = value[: cls.curtail_length]
1919
if value.lower() not in AnnotationStatus.values():
20-
raise TypeError(f"Available statuses is {', '.join(AnnotationStatus)}. ")
20+
raise TypeError(f"Available statuses is {', '.join(AnnotationStatus.titles())}. ")
2121
return value
2222

2323

@@ -80,6 +80,5 @@ def wrapped(*args, **kwargs):
8080
field, " " * (48 - len(field)), f"\n {' ' * 48}".join(text)
8181
)
8282
)
83-
raise Exception("\n".join(texts))
84-
83+
raise Exception("\n".join(texts)) from None
8584
return wrapped
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from collections import defaultdict
2+
from typing import List
3+
4+
5+
def map_annotation_classes_name(annotation_classes, logger=None) -> dict:
6+
classes_data = defaultdict(dict)
7+
for annotation_class in annotation_classes:
8+
class_info = {"id": annotation_class.uuid}
9+
if annotation_class.attribute_groups:
10+
for attribute_group in annotation_class.attribute_groups:
11+
attribute_group_data = defaultdict(dict)
12+
for attribute in attribute_group["attributes"]:
13+
if logger and attribute["name"] in attribute_group_data.keys():
14+
logger.warning(
15+
f"Duplicate annotation class attribute name {attribute['name']}"
16+
f" in attribute group {attribute_group['name']}. "
17+
"Only one of the annotation class attributes will be used. "
18+
"This will result in errors in annotation upload."
19+
)
20+
attribute_group_data[attribute["name"]] = attribute["id"]
21+
if logger and attribute_group["name"] in class_info.keys():
22+
logger.warning(
23+
f"Duplicate annotation class attribute group name {attribute_group['name']}."
24+
" Only one of the annotation class attribute groups will be used."
25+
" This will result in errors in annotation upload."
26+
)
27+
class_info["attribute_groups"] = {
28+
attribute_group["name"]: {
29+
"id": attribute_group["id"],
30+
"attributes": attribute_group_data,
31+
}
32+
}
33+
if logger and annotation_class.name in classes_data.keys():
34+
logger.warning(
35+
f"Duplicate annotation class name {annotation_class.name}."
36+
f" Only one of the annotation classes will be used."
37+
" This will result in errors in annotation upload.",
38+
)
39+
classes_data[annotation_class.name] = class_info
40+
return classes_data
41+
42+
43+
def fill_annotation_ids(annotations: dict, annotation_classes_name_maps: dict, templates: List[dict], logger=None):
44+
annotation_classes_name_maps = annotation_classes_name_maps
45+
if "instances" not in annotations:
46+
return
47+
missing_classes = set()
48+
missing_attribute_groups = set()
49+
missing_attributes = set()
50+
unknown_classes = dict()
51+
report = {
52+
"missing_classes": missing_classes,
53+
"missing_attribute_groups": missing_attribute_groups,
54+
"missing_attributes": missing_attributes,
55+
}
56+
for annotation in [i for i in annotations["instances"] if "className" in i]:
57+
if "className" not in annotation:
58+
return
59+
annotation_class_name = annotation["className"]
60+
if annotation_class_name not in annotation_classes_name_maps.keys():
61+
if annotation_class_name not in unknown_classes:
62+
missing_classes.add(annotation_class_name)
63+
unknown_classes[annotation_class_name] = {
64+
"id": -(len(unknown_classes) + 1),
65+
"attribute_groups": {},
66+
}
67+
annotation_classes_name_maps.update(unknown_classes)
68+
template_name_id_map = {template["name"]: template["id"] for template in templates}
69+
for annotation in (
70+
i for i in annotations["instances"] if i.get("type", None) == "template"
71+
):
72+
annotation["templateId"] = template_name_id_map.get(
73+
annotation.get("templateName", ""), -1
74+
)
75+
76+
for annotation in [i for i in annotations["instances"] if "className" in i]:
77+
annotation_class_name = annotation["className"]
78+
if annotation_class_name not in annotation_classes_name_maps.keys():
79+
if logger:
80+
logger.warning(
81+
f"Couldn't find annotation class {annotation_class_name}"
82+
)
83+
continue
84+
annotation["classId"] = annotation_classes_name_maps[annotation_class_name]["id"]
85+
for attribute in annotation["attributes"]:
86+
if (
87+
attribute["groupName"]
88+
not in annotation_classes_name_maps[annotation_class_name]["attribute_groups"]
89+
):
90+
if logger:
91+
logger.warning(
92+
f"Couldn't find annotation group {attribute['groupName']}."
93+
)
94+
missing_attribute_groups.add(attribute["groupName"])
95+
continue
96+
attribute["groupId"] = annotation_classes_name_maps[annotation_class_name][
97+
"attribute_groups"
98+
][attribute["groupName"]]["id"]
99+
if (
100+
attribute["name"]
101+
not in annotation_classes_name_maps[annotation_class_name][
102+
"attribute_groups"
103+
][attribute["groupName"]]["attributes"]
104+
):
105+
del attribute["groupId"]
106+
if logger:
107+
logger.warning(
108+
f"Couldn't find annotation name {attribute['name']} in"
109+
f" annotation group {attribute['groupName']}",
110+
)
111+
missing_attributes.add(attribute["name"])
112+
continue
113+
attribute["id"] = annotation_classes_name_maps[annotation_class_name][
114+
"attribute_groups"
115+
][attribute["groupName"]]["attributes"][attribute["name"]]
116+
return report

src/superannotate/lib/core/usecases/folders.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def __init__(
2727
self._project = project
2828
self._folder = folder
2929
self._folders = folders
30+
self._origin_name = folder.name
3031

3132
def validate_folder(self):
3233
if not self._folder.name:
@@ -53,6 +54,11 @@ def execute(self):
5354
if self.is_valid():
5455
self._folder.project_id = self._project.uuid
5556
self._response.data = self._folders.insert(self._folder)
57+
if self._response.data.name not in (self._origin_name, self._folder.name):
58+
logger.warning(
59+
f"Created folder has name {self._response.data.name},"
60+
f" since folder with name {self._folder.name} already existed."
61+
)
5662
return self._response
5763

5864

0 commit comments

Comments
 (0)