Skip to content

Commit 7f2f554

Browse files
committed
2 parents 7b662c1 + ff70b00 commit 7f2f554

File tree

17 files changed

+304
-99
lines changed

17 files changed

+304
-99
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,6 @@ sample_tif_files
133133

134134
# Vscode settings folder
135135
.vscode/
136+
137+
# python virtual env for SDK testing
138+
venv_sa_conv

docs/source/cli.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ To initialize CLI (and SDK) with team token:
3636
3737
----------
3838

39+
.. _ref_create_project:
40+
41+
Creating a project
42+
~~~~~~~~~~~~~~~~~~
43+
44+
To create a new project:
45+
46+
.. code-block:: bash
47+
48+
superannotate create-project --name <project_name> --description <project_description> --type <project_type Vector or Pixel>
49+
50+
----------
51+
3952
.. _ref_upload_images:
4053

4154
Uploading images

docs/source/superannotate.sdk.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ _________________
110110

111111
.. autofunction:: superannotate.get_team_metadata
112112
.. autofunction:: superannotate.invite_contributor_to_team
113+
.. autofunction:: superannotate.delete_contributor_to_team_invitation
113114

114115
----------
115116

superannotate/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@
6161
upload_preannotations_from_folder_to_project, upload_video_to_project,
6262
upload_videos_from_folder_to_project
6363
)
64-
from .db.teams import get_team_metadata, invite_contributor_to_team
64+
from .db.teams import (
65+
delete_contributor_to_team_invitation, get_team_metadata,
66+
invite_contributor_to_team
67+
)
6568
from .db.users import search_team_contributors
6669
from .dicom_converter import dicom_to_rgb_sequence
6770
from .exceptions import (

superannotate/__main__.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ def main():
4444
command = sys.argv[1]
4545
further_args = sys.argv[2:]
4646

47-
if command == "upload-images":
47+
if command == "create-project":
48+
create_project(further_args)
49+
elif command == "upload-images":
4850
image_upload(further_args)
4951
elif command == "upload-videos":
5052
video_upload(further_args)
@@ -122,7 +124,8 @@ def preannotations_upload(args, annotations=False):
122124
)
123125
args.folder = tempdir_path
124126
sa.create_annotation_classes_from_classes_json(
125-
args.project, Path(args.folder) / "classes" / "classes.json"
127+
args.project,
128+
Path(args.folder) / "classes" / "classes.json"
126129
)
127130

128131
if annotations:
@@ -135,6 +138,20 @@ def preannotations_upload(args, annotations=False):
135138
)
136139

137140

141+
def create_project(args):
142+
parser = argparse.ArgumentParser()
143+
parser.add_argument('--name', required=True, help='Project name to create')
144+
parser.add_argument(
145+
'--description', required=True, help='Project description'
146+
)
147+
parser.add_argument(
148+
'--type', required=True, help='Project type Vector or Pixel'
149+
)
150+
args = parser.parse_args(args)
151+
152+
sa.create_project(args.name, args.description, args.type)
153+
154+
138155
def video_upload(args):
139156
parser = argparse.ArgumentParser()
140157
parser.add_argument(

superannotate/analytics/common.py

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,17 @@ def aggregate_annotations_as_df(
183183
)
184184
classes_json = json.load(open(classes_path))
185185
class_name_to_color = {}
186+
class_group_name_to_values = {}
186187
for annotation_class in classes_json:
187188
name = annotation_class["name"]
188189
color = annotation_class["color"]
189190
class_name_to_color[name] = color
191+
class_group_name_to_values[name] = {}
192+
for attribute_group in annotation_class["attribute_groups"]:
193+
class_group_name_to_values[name][attribute_group["name"]] = []
194+
for attribute in attribute_group["attributes"]:
195+
class_group_name_to_values[name][attribute_group["name"]
196+
].append(attribute["name"])
190197

191198
def __append_annotation(annotation_dict):
192199
for annotation_key in annotation_data:
@@ -258,10 +265,12 @@ def __get_image_metadata(image_name, annotations):
258265
continue
259266
annotation_instance_id += 1
260267
annotation_class_name = annotation.get("className")
261-
if annotation_class_name is None:
262-
raise SABaseException(
263-
0, "Annotation class not found in classes.json"
268+
if annotation_class_name is None or annotation_class_name not in class_name_to_color:
269+
logger.warning(
270+
"Annotation class %s not found in classes json. Skipping.",
271+
annotation_class_name
264272
)
273+
continue
265274
annotation_class_color = class_name_to_color[annotation_class_name]
266275
annotation_group_id = annotation.get("groupId")
267276
annotation_locked = annotation.get("locked")
@@ -332,37 +341,50 @@ def __get_image_metadata(image_name, annotations):
332341
}
333342
annotation_dict.update(image_metadata)
334343
__append_annotation(annotation_dict)
335-
336-
for attribute in attributes:
337-
338-
attribute_group = attribute.get("groupName")
339-
attribute_name = attribute.get('name')
340-
annotation_dict = {
341-
"imageName": image_name,
342-
"instanceId": annotation_instance_id,
343-
"className": annotation_class_name,
344-
"attributeGroupName": attribute_group,
345-
"attributeName": attribute_name,
346-
"type": annotation_type,
347-
"locked": annotation_locked,
348-
"visible": annotation_visible,
349-
"trackingId": annotation_tracking_id,
350-
"meta": annotation_meta,
351-
"error": annotation_error,
352-
"probability": annotation_probability,
353-
"pointLabels": annotation_point_labels,
354-
"classColor": annotation_class_color,
355-
"groupId": annotation_group_id,
356-
"createdAt": annotation_created_at,
357-
"creatorRole": annotation_creator_role,
358-
"creatorEmail": annotation_creator_email,
359-
"creationType": annotation_creation_type,
360-
"updatedAt": annotation_updated_at,
361-
"updatorRole": annotation_updator_role,
362-
"updatorEmail": annotation_updator_email
363-
}
364-
annotation_dict.update(image_metadata)
365-
__append_annotation(annotation_dict)
344+
else:
345+
for attribute in attributes:
346+
attribute_group = attribute.get("groupName")
347+
attribute_name = attribute.get('name')
348+
if attribute_group not in class_group_name_to_values[
349+
annotation_class_name]:
350+
logger.warning(
351+
"Annotation class group %s not in classes json. Skipping.",
352+
attribute_group
353+
)
354+
continue
355+
if attribute_name not in class_group_name_to_values[
356+
annotation_class_name][attribute_group]:
357+
logger.warning(
358+
"Annotation class group value %s not in classes json. Skipping.",
359+
attribute_name
360+
)
361+
continue
362+
annotation_dict = {
363+
"imageName": image_name,
364+
"instanceId": annotation_instance_id,
365+
"className": annotation_class_name,
366+
"attributeGroupName": attribute_group,
367+
"attributeName": attribute_name,
368+
"type": annotation_type,
369+
"locked": annotation_locked,
370+
"visible": annotation_visible,
371+
"trackingId": annotation_tracking_id,
372+
"meta": annotation_meta,
373+
"error": annotation_error,
374+
"probability": annotation_probability,
375+
"pointLabels": annotation_point_labels,
376+
"classColor": annotation_class_color,
377+
"groupId": annotation_group_id,
378+
"createdAt": annotation_created_at,
379+
"creatorRole": annotation_creator_role,
380+
"creatorEmail": annotation_creator_email,
381+
"creationType": annotation_creation_type,
382+
"updatedAt": annotation_updated_at,
383+
"updatorRole": annotation_updator_role,
384+
"updatorEmail": annotation_updator_email
385+
}
386+
annotation_dict.update(image_metadata)
387+
__append_annotation(annotation_dict)
366388

367389
df = pd.DataFrame(annotation_data)
368390

superannotate/db/annotation_classes.py

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -160,19 +160,40 @@ def create_annotation_classes_from_classes_json(
160160
else:
161161
new_classes.append(cs)
162162

163-
params = {
164-
'team_id': team_id,
165-
'project_id': project_id,
166-
}
167-
data = {"classes": new_classes}
168-
response = _api.send_request(
169-
req_type='POST', path='/classes', params=params, json_req=data
170-
)
171-
if not response.ok:
172-
raise SABaseException(
173-
response.status_code, "Couldn't create classes " + response.text
163+
res = []
164+
165+
def del_unn(d):
166+
for s in [
167+
"updatedAt", "createdAt", "id", "project_id", "group_id",
168+
"class_id", "count"
169+
]:
170+
if s in d:
171+
del d[s]
172+
173+
for annotation_class in new_classes:
174+
del_unn(annotation_class)
175+
for attribute_group in annotation_class["attribute_groups"]:
176+
del_unn(attribute_group)
177+
for attribute in attribute_group["attributes"]:
178+
del_unn(attribute)
179+
180+
CHUNK_SIZE = 2000
181+
for i in range(0, len(new_classes), CHUNK_SIZE):
182+
params = {
183+
'team_id': team_id,
184+
'project_id': project_id,
185+
}
186+
data = {"classes": new_classes[i:i + CHUNK_SIZE]}
187+
response = _api.send_request(
188+
req_type='POST', path='/classes', params=params, json_req=data
174189
)
175-
res = response.json()
190+
if not response.ok:
191+
raise SABaseException(
192+
response.status_code, "Couldn't create classes " + response.text
193+
)
194+
res += response.json()
195+
196+
assert len(res) == len(new_classes)
176197
return res
177198

178199

@@ -280,12 +301,13 @@ def fill_class_and_attribute_names(annotations_json, annotation_classes_dict):
280301
r["className"] = annotation_classes_dict[r["classId"]]["name"]
281302
if "attributes" in r:
282303
for attribute in r["attributes"]:
283-
attribute["groupName"] = annotation_classes_dict[
284-
r["classId"]]["attribute_groups"][attribute["groupId"]
285-
]["name"]
286-
attribute["name"] = annotation_classes_dict[
287-
r["classId"]]["attribute_groups"][
288-
attribute["groupId"]]["attributes"][attribute["id"]]
304+
if "groupId" in attribute and "id" in "attribute":
305+
attribute["groupName"] = annotation_classes_dict[
306+
r["classId"]]["attribute_groups"][
307+
attribute["groupId"]]["name"]
308+
attribute["name"] = annotation_classes_dict[
309+
r["classId"]]["attribute_groups"][attribute[
310+
"groupId"]]["attributes"][attribute["id"]]
289311

290312

291313
def fill_class_and_attribute_ids(annotation_json, annotation_classes_dict):
@@ -299,15 +321,32 @@ def fill_class_and_attribute_ids(annotation_json, annotation_classes_dict):
299321
logger.warning(
300322
"Couldn't find annotation class %s", annotation_class_name
301323
)
324+
continue
302325
class_id = annotation_classes_dict[annotation_class_name]["id"]
303326
ann["classId"] = class_id
304327
for attribute in ann["attributes"]:
305-
attribute["groupId"] = annotation_classes_dict[
328+
if attribute["groupName"] in annotation_classes_dict[
329+
annotation_class_name]["attribute_groups"]:
330+
attribute["groupId"] = annotation_classes_dict[
331+
annotation_class_name]["attribute_groups"][
332+
attribute["groupName"]]["id"]
333+
else:
334+
logger.warning(
335+
"Couldn't find annotation group %s", attribute["groupName"]
336+
)
337+
continue
338+
if attribute["name"] in annotation_classes_dict[
306339
annotation_class_name]["attribute_groups"][
307-
attribute["groupName"]]["id"]
308-
attribute["id"] = annotation_classes_dict[annotation_class_name][
309-
"attribute_groups"][attribute["groupName"]]["attributes"][
310-
attribute["name"]]
340+
attribute["groupName"]]["attributes"]:
341+
attribute["id"] = annotation_classes_dict[
342+
annotation_class_name]["attribute_groups"][
343+
attribute["groupName"]]["attributes"][attribute["name"]]
344+
else:
345+
logger.warning(
346+
"Couldn't find annotation name %s in annotation group %s",
347+
attribute["name"], attribute["groupName"]
348+
)
349+
del attribute["groupId"]
311350

312351

313352
def get_annotation_classes_id_to_name(annotation_classes):

superannotate/db/teams.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,25 @@ def get_team_metadata(convert_users_role_to_string=False):
5252
return res
5353

5454

55-
# def delete_team_contributor_invitation(invitation):
56-
# """Deletes team contributor invitation
57-
58-
# :param invite: invitation metadata returned from invite_contributor_to_team
59-
# :type project: dict
60-
# """
61-
# data = {'token': invitation["token"], 'e_mail': invitation['email']}
62-
# response = _api.send_request(
63-
# req_type='DELETE', path=f'/team/{_api.team_id}/invite', json_req=data
64-
# )
65-
# if not response.ok:
66-
# raise SABaseException(
67-
# response.status_code,
68-
# "Couldn't delete contributor invite. " + response.text
69-
# )
55+
def delete_contributor_to_team_invitation(email):
56+
"""Deletes team contributor invitation
57+
58+
:param email: invitation email
59+
:type email: str
60+
"""
61+
team_metadata = get_team_metadata()
62+
for invite in team_metadata["pending_invitations"]:
63+
if invite["email"] == email:
64+
break
65+
else:
66+
raise SABaseException(0, "Couldn't find user " + email + " invitation")
67+
68+
data = {'token': invite["token"], 'e_mail': invite['email']} # pylint: disable=undefined-loop-variable
69+
response = _api.send_request(
70+
req_type='DELETE', path=f'/team/{_api.team_id}/invite', json_req=data
71+
)
72+
if not response.ok:
73+
raise SABaseException(
74+
response.status_code,
75+
"Couldn't delete contributor invite. " + response.text
76+
)

superannotate/db/users.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ def get_team_contributor_metadata(email):
6262
if user["email"] == email:
6363
results.append(user)
6464

65-
if len(results) > 1 or len(results) == 0:
65+
if len(results) == 0:
66+
raise SABaseException(0, "No user with email " + email + " found.")
67+
if len(results) > 1:
6668
raise SABaseException(0, "Email " + email + " malformed.")
6769
return results[0]

superannotate/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.3.5"
1+
__version__ = "2.3.9"

0 commit comments

Comments
 (0)