Skip to content

Commit d591fd3

Browse files
authored
Merge pull request #51 from superannotateai/release-merge
Release merge
2 parents e72900e + ab972ea commit d591fd3

File tree

17 files changed

+816
-126
lines changed

17 files changed

+816
-126
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,20 @@ _________________
129129

130130
----------
131131

132+
Neural Network
133+
_______________
134+
135+
.. autofunction:: superannotate.delete_model
136+
.. autofunction:: superannotate.download_model
137+
.. autofunction:: superannotate.plot_model_metrics
138+
.. autofunction:: superannotate.run_prediction
139+
.. autofunction:: superannotate.run_segmentation
140+
.. autofunction:: superannotate.run_training
141+
.. autofunction:: superannotate.stop_model_training
142+
.. autofunction:: superannotate.search_models
143+
144+
----------
145+
132146

133147
.. _ref_metadata:
134148

@@ -327,4 +341,4 @@ Utility functions
327341

328342
.. autofunction:: superannotate.dicom_to_rgb_sequence
329343
.. autofunction:: superannotate.consensus
330-
.. autofunction:: superannotate.benchmark
344+
.. autofunction:: superannotate.benchmark

superannotate/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def consensus(*args, **kwargs):
5959
)
6060
from .db.project_images import (
6161
assign_images, copy_image, copy_images, delete_images, move_image,
62-
move_images, pin_image, upload_image_to_project
62+
move_images, pin_image, upload_image_to_project, assign_folder,
63+
unassign_folder, unassign_images
6364
)
6465
from .db.projects import (
6566
clone_project, create_project, create_project_from_metadata, delete_project,

superannotate/db/annotation_classes.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
)
1313
from .project_api import get_project_metadata_bare
1414
from ..mixp.decorators import Trackable
15+
from .utils import get_templates_mapping
1516

1617
logger = logging.getLogger("superannotate-python-sdk")
1718

@@ -344,7 +345,11 @@ def fill_class_and_attribute_ids(annotation_json, annotation_classes_dict):
344345
'attribute_groups': {}
345346
}
346347
annotation_classes_dict = {**annotation_classes_dict, **unknown_classes}
347-
348+
templates_map = get_templates_mapping()
349+
for ann in (
350+
i for i in annotation_json["instances"] if i['type'] == 'template'
351+
):
352+
ann['templateId'] = templates_map.get(ann['templateName'], -1)
348353
for ann in annotation_json["instances"]:
349354
if "className" not in ann:
350355
logger.warning("No className in annotation instance")

superannotate/db/project_images.py

Lines changed: 155 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,26 @@
1616
)
1717
from .project_api import get_project_and_folder_metadata
1818
from .projects import (
19-
get_project_default_image_quality_in_editor, _get_available_image_counts
19+
get_project_default_image_quality_in_editor, _get_available_image_counts,
20+
get_project_metadata
2021
)
22+
from .teams import get_team_metadata
2123
from ..mixp.decorators import Trackable
22-
from .utils import _get_upload_auth_token, _get_boto_session_by_credentials, upload_image_array_to_s3, get_image_array_to_upload, __create_image, __copy_images, __move_images, get_project_folder_string
24+
from .utils import _unassign_images, _assign_images, _get_upload_auth_token, _get_boto_session_by_credentials, upload_image_array_to_s3, \
25+
get_image_array_to_upload, __create_image, __copy_images, __move_images, get_project_folder_string
2326

2427
logger = logging.getLogger("superannotate-python-sdk")
2528
_api = API.get_instance()
2629

2730

2831
@Trackable
2932
def upload_image_to_project(
30-
project,
31-
img,
32-
image_name=None,
33-
annotation_status="NotStarted",
34-
from_s3_bucket=None,
35-
image_quality_in_editor=None
33+
project,
34+
img,
35+
image_name=None,
36+
annotation_status="NotStarted",
37+
from_s3_bucket=None,
38+
image_quality_in_editor=None
3639
):
3740
"""Uploads image (io.BytesIO() or filepath to image) to project.
3841
Sets status of the uploaded image to set_status if it is not None.
@@ -134,8 +137,8 @@ def upload_image_to_project(
134137

135138

136139
def _copy_images(
137-
source_project, destination_project, image_names, include_annotations,
138-
copy_annotation_status, copy_pin
140+
source_project, destination_project, image_names, include_annotations,
141+
copy_annotation_status, copy_pin
139142
):
140143
NUM_TO_SEND = 500
141144
source_project, source_project_folder = source_project
@@ -164,7 +167,7 @@ def _copy_images(
164167
res['completed'] = []
165168
for start_index in range(0, len(image_names), NUM_TO_SEND):
166169
json_req["image_names"] = image_names[start_index:start_index +
167-
NUM_TO_SEND]
170+
NUM_TO_SEND]
168171
response = _api.send_request(
169172
req_type='POST',
170173
path='/image/copy',
@@ -190,12 +193,12 @@ def _copy_images(
190193

191194
@Trackable
192195
def copy_images(
193-
source_project,
194-
image_names,
195-
destination_project,
196-
include_annotations=True,
197-
copy_annotation_status=True,
198-
copy_pin=True
196+
source_project,
197+
image_names,
198+
destination_project,
199+
include_annotations=True,
200+
copy_annotation_status=True,
201+
copy_pin=True
199202
):
200203
"""Copy images in bulk between folders in a project
201204
@@ -305,12 +308,12 @@ def delete_images(project, image_names):
305308

306309
@Trackable
307310
def move_images(
308-
source_project,
309-
image_names,
310-
destination_project,
311-
include_annotations=True,
312-
copy_annotation_status=True,
313-
copy_pin=True,
311+
source_project,
312+
image_names,
313+
destination_project,
314+
include_annotations=True,
315+
copy_annotation_status=True,
316+
copy_pin=True,
314317
):
315318
"""Move images in bulk between folders in a project
316319
@@ -370,12 +373,12 @@ def move_images(
370373

371374
@Trackable
372375
def copy_image(
373-
source_project,
374-
image_name,
375-
destination_project,
376-
include_annotations=False,
377-
copy_annotation_status=False,
378-
copy_pin=False
376+
source_project,
377+
image_name,
378+
destination_project,
379+
include_annotations=False,
380+
copy_annotation_status=False,
381+
copy_pin=False
379382
):
380383
"""Copy image to a project. The image's project is the same as destination
381384
project then the name will be changed to <image_name>_(<num>).<image_ext>,
@@ -415,10 +418,10 @@ def copy_image(
415418
else:
416419
for m in p.finditer(new_name):
417420
if m.start() + len(m.group()
418-
) + len(extension) - 1 == len(new_name):
421+
) + len(extension) - 1 == len(new_name):
419422
num = int(m.group()[2:-2])
420423
new_name = new_name[:m.start() +
421-
2] + str(num + 1) + ")" + extension
424+
2] + str(num + 1) + ")" + extension
422425
break
423426
else:
424427
new_name = Path(new_name).stem + "_(1)" + extension
@@ -438,9 +441,9 @@ def copy_image(
438441

439442

440443
def _copy_annotations_and_metadata(
441-
source_project, source_project_folder, image_name, destination_project,
442-
destination_project_folder, new_name, include_annotations,
443-
copy_annotation_status, copy_pin
444+
source_project, source_project_folder, image_name, destination_project,
445+
destination_project_folder, new_name, include_annotations,
446+
copy_annotation_status, copy_pin
444447
):
445448
if include_annotations:
446449
annotations = get_image_annotations(
@@ -477,12 +480,12 @@ def _copy_annotations_and_metadata(
477480

478481
@Trackable
479482
def move_image(
480-
source_project,
481-
image_name,
482-
destination_project,
483-
include_annotations=True,
484-
copy_annotation_status=True,
485-
copy_pin=True
483+
source_project,
484+
image_name,
485+
destination_project,
486+
include_annotations=True,
487+
copy_annotation_status=True,
488+
copy_pin=True
486489
):
487490
"""Move image from source_project to destination_project. source_project
488491
and destination_project cannot be the same.
@@ -563,30 +566,126 @@ def assign_images(project, image_names, user):
563566
:param user: user email
564567
:type user: str
565568
"""
566-
logger.info("Assign %s images to user %s", len(image_names), user)
567569
if len(image_names) == 0:
568570
return
569-
project, project_folder = get_project_and_folder_metadata(project)
570-
images = search_images((project, project_folder), return_metadata=True)
571-
image_dict = {}
572-
for image in images:
573-
image_dict[image["name"]] = image["id"]
574571

575-
image_ids = []
576-
for image_name in image_names:
577-
image_ids.append(image_dict[image_name])
578-
team_id, project_id = project["team_id"], project["id"]
579-
params = {"team_id": team_id, "project_id": project_id}
580-
if project_folder is not None:
581-
params['folder_id'] = project_folder['id']
582-
json_req = {"user_id": user, "image_ids": image_ids}
572+
project, folder = get_project_and_folder_metadata(project)
573+
project_meta = get_project_metadata(project, include_contributors=True)
574+
verified_users = project_meta["contributors"]
575+
verified_users = [i['user_id'] for i in verified_users]
576+
if user not in verified_users:
577+
logger.warn(
578+
f'Skipping {user}. {user} is not a verified contributor for the {project["name"]}'
579+
)
580+
return
581+
582+
folder_name = 'root'
583+
if folder:
584+
folder_name = folder['name']
585+
586+
logs = _assign_images(folder_name=folder_name, image_names=image_names, user=user, project_id=project['id'],
587+
team_id=project['team_id'])
588+
for log in logs:
589+
logger.warn(log)
590+
logger.info("Assign images to user %s", user)
591+
592+
@Trackable
593+
def assign_folder(project, folder_name, users):
594+
"""Assigns folder to users. With SDK, the user can be
595+
assigned to a role in the project with the share_project function.
596+
597+
:param project: project name or metadata of the project
598+
:type project: str or dict
599+
:param folder_name: folder name to assign
600+
:type folder_name: str
601+
:param users: list of user emails
602+
:type user: list of str
603+
"""
604+
605+
project_meta = get_project_metadata(project, include_contributors=True)
606+
verified_users = project_meta["contributors"]
607+
verified_users = [i['user_id'] for i in verified_users]
608+
project_name = project_meta['name']
609+
verified_users = set(users).intersection(set(verified_users))
610+
unverified_contributor = set(users) - verified_users
611+
612+
for user in unverified_contributor:
613+
logger.warn(
614+
f'Skipping {user} from assignees. {user} is not a verified contributor for the {project_name}'
615+
)
616+
617+
if not verified_users:
618+
return
619+
620+
params = {
621+
"project_id": project_meta['id'],
622+
"team_id": project_meta["team_id"]
623+
}
624+
json_req = {
625+
"assign_user_ids": list(verified_users),
626+
"folder_name": folder_name
627+
}
583628
response = _api.send_request(
584629
req_type='POST',
585-
path='/images/assign',
630+
path='/folder/editAssignment',
586631
params=params,
587632
json_req=json_req
588633
)
634+
589635
if not response.ok:
590636
raise SABaseException(
591-
response.status_code, "Couldn't assign images " + response.text
637+
response.status_code, "Couldn't assign folder " + response.text
592638
)
639+
logger.info(f'Assigned {folder_name} to users: {list(verified_users)}')
640+
641+
@Trackable
642+
def unassign_folder(project, folder_name):
643+
"""Removes assignment of given folder for all assignees.
644+
With SDK, the user can be assigned to a role in the project
645+
with the share_project function.
646+
647+
:param project: project name or folder path (e.g., "project1/folder1")
648+
:type project: str
649+
:param folder_name: folder name to remove assignees
650+
:type folder_name: str
651+
"""
652+
653+
project_meta = get_project_metadata(project)
654+
params = {
655+
"project_id": project_meta['id'],
656+
"team_id": project_meta["team_id"]
657+
}
658+
json_req = {"folder_name": folder_name, "remove_user_ids": ["all"]}
659+
response = _api.send_request(
660+
req_type='POST',
661+
path='/folder/editAssignment',
662+
params=params,
663+
json_req=json_req
664+
)
665+
666+
if not response.ok:
667+
raise SABaseException(
668+
response.status_code, "Couldn't unassign folder " + response.text
669+
)
670+
671+
@Trackable
672+
def unassign_images(project, image_names):
673+
"""Removes assignment of given images for all assignees.With SDK,
674+
the user can be assigned to a role in the project with the share_project
675+
function.
676+
677+
:param project: project name or folder path (e.g., "project1/folder1")
678+
:type project: str
679+
:param image_names: list of image unassign
680+
:type image_names: list of str
681+
"""
682+
if not image_names:
683+
return
684+
project, folder = get_project_and_folder_metadata(project)
685+
686+
folder_name = 'root'
687+
if folder:
688+
folder_name = folder['name']
689+
logs = _unassign_images(folder_name=folder_name,image_names=image_names,project_id=project['id'],team_id=project['team_id'])
690+
for log in logs:
691+
logger.warn(log)

0 commit comments

Comments
 (0)