Skip to content

Commit 67eece2

Browse files
authored
Merge pull request #148 from superannotateai/develop
Video attach urls
2 parents e026a75 + 3cab063 commit 67eece2

File tree

13 files changed

+434
-259
lines changed

13 files changed

+434
-259
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ ________
4646
.. autofunction:: superannotate.upload_images_from_folder_to_project
4747
.. autofunction:: superannotate.upload_video_to_project
4848
.. autofunction:: superannotate.upload_videos_from_folder_to_project
49+
.. autofunction:: superannotate.attach_video_urls_to_project
4950
.. _ref_upload_annotations_from_folder_to_project:
5051
.. autofunction:: superannotate.upload_annotations_from_folder_to_project
5152
.. autofunction:: superannotate.upload_preannotations_from_folder_to_project

superannotate/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ def consensus(*args, **kwargs):
7575
upload_images_from_public_urls_to_project,
7676
upload_images_from_s3_bucket_to_project, upload_images_to_project,
7777
attach_image_urls_to_project, upload_preannotations_from_folder_to_project,
78-
upload_video_to_project, upload_videos_from_folder_to_project
78+
upload_video_to_project, upload_videos_from_folder_to_project,
79+
attach_video_urls_to_project
7980
)
8081
from .db.search_projects import search_projects
8182
from .db.teams import (

superannotate/__main__.py

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,37 +48,35 @@ def ask_token():
4848

4949

5050
def main():
51-
available_commands = "Available commands to superannotate CLI are: init version create-project create-folder upload-images upload-videos upload-preannotations upload-annotations export-project"
51+
available_commands = {
52+
"create-project": create_project,
53+
"create-folder": create_folder,
54+
"upload-images": image_upload,
55+
"attach-image-urls": attach_video_urls,
56+
"upload-videos": video_upload,
57+
"upload-preannotations": preannotations_upload,
58+
"upload-annotations": preannotations_upload,
59+
"init": lambda *args, **kwargs: ask_token(),
60+
"export-project": export_project,
61+
"attach-video-urls": attach_video_urls,
62+
"version": lambda *args, **kwargs: print(f"SuperAnnotate Python SDK version {sa.__version__}")
63+
}
5264
if len(sys.argv) == 1:
53-
raise SABaseException(
54-
0, "No command given to superannotate CLI. " + available_commands
65+
print(
66+
"No command given to superannotate CLI. Available commands to superannotate CLI are:"
67+
+ ", ".join(available_commands.keys())
5568
)
69+
5670
command = sys.argv[1]
5771
further_args = sys.argv[2:]
58-
59-
if command == "create-project":
60-
create_project(command, further_args)
61-
elif command == "create-folder":
62-
create_folder(command, further_args)
63-
elif command == "upload-images":
64-
image_upload(command, further_args)
65-
elif command == "attach-image-urls":
66-
attach_image_urls(command, further_args)
67-
elif command == "upload-videos":
68-
video_upload(command, further_args)
69-
elif command in ["upload-preannotations", "upload-annotations"]:
70-
preannotations_upload(command, further_args)
71-
elif command == "init":
72-
ask_token()
73-
elif command == "export-project":
74-
export_project(command, further_args)
75-
elif command == "version":
76-
print(f"SuperAnnotate Python SDK version {sa.__version__}")
77-
else:
78-
raise SABaseException(
79-
0, "Wrong command " + command + " to superannotate CLI. " +
80-
available_commands
81-
)
72+
try:
73+
available_commands[command](command, further_args)
74+
except KeyError:
75+
sys.stdout.write("Wrong command " + command + " to superannotate CLI. " + ", ".join(available_commands.keys()))
76+
except SABaseException as e:
77+
sys.stdout.write(e.message)
78+
except BaseException as e:
79+
sys.stdout.write(str(e))
8280

8381

8482
def _list_str(values):
@@ -364,5 +362,30 @@ def export_project(command_name, args):
364362
)
365363

366364

365+
def attach_video_urls(command_name, args):
366+
parser = argparse.ArgumentParser(prog=_CLI_COMMAND + " " + command_name)
367+
parser.add_argument(
368+
'--project', required=True, help='Project name to upload'
369+
)
370+
parser.add_argument(
371+
'--attachments',
372+
required=True,
373+
help='path to csv file on attachments metadata'
374+
)
375+
parser.add_argument(
376+
'--annotation_status',
377+
required=False,
378+
default="NotStarted",
379+
help=
380+
'Set images\' annotation statuses after upload. Default is NotStarted'
381+
)
382+
args = parser.parse_args(args)
383+
sa.attach_video_urls_to_project(
384+
project=args.project,
385+
attachments=args.attachments,
386+
annotation_status=args.annotation_status
387+
)
388+
389+
367390
if __name__ == "__main__":
368391
main()

superannotate/api.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ def init(self, config_location=None):
8989
req_type='GET',
9090
path=f'/team/{self.team_id}',
9191
)
92-
9392
self.user_id = response.json().get('creator_id', None)
9493
self.team_name = response.json().get('name', None)
9594

superannotate/common.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
SPECIAL_CHARACTERS_IN_PROJECT_FOLDER_NAMES = set('/\\:*?"<>|')
2121

22-
_PROJECT_TYPES = {"Vector": 1, "Pixel": 2}
22+
_PROJECT_TYPES = {"Vector": 1, "Pixel": 2, "Video": 3}
2323

2424
_ANNOTATION_STATUSES = {
2525
"NotStarted": 1,
@@ -59,6 +59,46 @@
5959
"Semantic Segmentation for Pixel Projects": "semantic_segmentation_pixel"
6060
}
6161

62+
VIDEO_DEPRICATED_FUNCTIONS = [
63+
"upload_images_from_folder_to_project",
64+
"get_image_metadata",
65+
"search_images",
66+
"upload_images_to_project",
67+
"upload_annotations_from_folder_to_project",
68+
"upload_image_annotations",
69+
"download_image",
70+
"download_image_annotations",
71+
"get_image_annotations",
72+
"set_image_annotation_status",
73+
"aggregate_annotations_as_df",
74+
"attach_image_urls_to_project",
75+
"clone_project",
76+
"copy_image",
77+
"export_annotation",
78+
"upload_image_to_project",
79+
"upload_video_to_project",
80+
"add_annotation_bbox_to_image",
81+
"assign_images",
82+
"delete_images",
83+
"get_project_image_count",
84+
"set_project_workflow",
85+
"upload_preannotations_from_folder_to_project",
86+
"upload_videos_from_folder_to_project",
87+
"add_annotation_comment_to_image",
88+
"add_annotation_point_to_image",
89+
"benchmark",
90+
"class_distribution",
91+
"consensus",
92+
"convert_project_type",
93+
"copy_images",
94+
"get_project_workflow",
95+
"move_image",
96+
"move_images",
97+
"set_images_annotation_statuses",
98+
"set_project_default_image_quality_in_editor",
99+
"upload_images_from_google_cloud_to_project",
100+
]
101+
62102

63103
def prediction_segmentation_status_from_str_to_int(status):
64104
return _PREDICTION_SEGMENTATION_STATUSES[status]
@@ -109,10 +149,11 @@ def project_type_int_to_str(project_type):
109149
:return: 'Vector' or 'Pixel'
110150
:rtype: str
111151
"""
152+
if project_type not in _PROJECT_TYPES.values():
153+
raise RuntimeError("NA Project type")
112154
for k, v in _PROJECT_TYPES.items():
113155
if v == project_type:
114156
return k
115-
raise RuntimeError("NA Project type")
116157

117158

118159
def user_role_str_to_int(user_role):
@@ -323,7 +364,7 @@ def write_to_json(output_path, json_data):
323364

324365

325366
def tqdm_converter(
326-
total_num, images_converted, images_not_converted, finish_event
367+
total_num, images_converted, images_not_converted, finish_event
327368
):
328369
with tqdm(total=total_num) as pbar:
329370
while True:

superannotate/consensus_benchmark/benchmark.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from ..db.exports import prepare_export, download_export
1111
from ..analytics.common import aggregate_annotations_as_df
1212
from ..mixp.decorators import Trackable
13+
from ..db.project_api import get_project_and_folder_metadata
1314

1415
logger = logging.getLogger("superannotate-python-sdk")
1516

@@ -44,6 +45,12 @@ def benchmark(
4445
:return: Pandas DateFrame with columns (creatorEmail, QA, imageName, instanceId, className, area, attribute, folderName, score)
4546
:rtype: pandas DataFrame
4647
"""
48+
49+
if isinstance(project, dict):
50+
get_project_and_folder_metadata(project['name'])
51+
else:
52+
get_project_and_folder_metadata(project)
53+
4754
def aggregate_attributes(instance_df):
4855
def attribute_to_list(attribute_df):
4956
attribute_names = list(attribute_df["attributeName"])

superannotate/consensus_benchmark/consensus.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Main module for consensus computation
33
"""
44
import logging
5+
from ..db.project_api import get_project_and_folder_metadata
56
import tempfile
67
import pandas as pd
78
from pathlib import Path
@@ -45,6 +46,8 @@ def consensus(
4546
if annot_type not in supported_types:
4647
raise NotImplementedError
4748

49+
get_project_and_folder_metadata(project)
50+
4851
if export_root is None:
4952
with tempfile.TemporaryDirectory() as export_dir:
5053
proj_export_meta = prepare_export(project)

superannotate/db/exports.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,21 @@ def prepare_export(
127127
"""
128128
if not isinstance(project, dict):
129129
project = get_project_metadata_bare(project)
130+
130131
upload_state = upload_state_int_to_str(project.get("upload_state"))
131-
if upload_state == "External" and include_fuse == True:
132-
logger.info(
133-
"Include fuse functionality is not supported for projects containing images attached with URLs"
132+
133+
if upload_state == "External" and include_fuse:
134+
logger.warning(
135+
"Include fuse functionality is not supported for projects containing items attached with URLs"
134136
)
135137
include_fuse = False
138+
if project["type"] == "Video":
139+
if only_pinned:
140+
logger.warning(
141+
"Pin functionality is not supported for projects containing videos attached with URLs"
142+
)
143+
only_pinned, include_fuse = False, False
144+
136145
team_id, project_id = project["team_id"], project["id"]
137146
if annotation_statuses is None:
138147
annotation_statuses = [2, 3, 4, 5]
@@ -144,9 +153,9 @@ def prepare_export(
144153
current_time = datetime.now().strftime("%b %d %Y %H:%M")
145154
json_req = {
146155
"include": annotation_statuses,
147-
"fuse": int(include_fuse),
148-
"is_pinned": int(only_pinned),
149156
"coco": 0,
157+
"is_pinned": int(only_pinned),
158+
"fuse": int(include_fuse),
150159
"time": current_time
151160
}
152161
if folder_names is not None:

superannotate/db/project_api.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import inspect
23

34
from .. import common
45
from ..api import API
@@ -41,6 +42,19 @@ def get_project_metadata_bare(project_name, include_complete_image_count=False):
4142
res = results[0]
4243
res["type"] = common.project_type_int_to_str(res["type"])
4344
res["user_role"] = common.user_role_int_to_str(res["user_role"])
45+
current_frame = inspect.currentframe()
46+
outer_function = inspect.getframeinfo(current_frame.f_back).function
47+
outer_outer_function = inspect.getframeinfo(
48+
current_frame.f_back.f_back
49+
).function
50+
if res.get("type") and res["type"] == "Video" and (
51+
outer_function in common.VIDEO_DEPRICATED_FUNCTIONS or
52+
outer_outer_function in common.VIDEO_DEPRICATED_FUNCTIONS
53+
):
54+
raise SABaseException(
55+
0,
56+
"The function does not support projects containing videos attached with URLs"
57+
)
4458
return res
4559
else:
4660
raise SANonExistingProjectNameException(
@@ -131,6 +145,18 @@ def get_project_and_folder_metadata(project):
131145
raise SAIncorrectProjectArgument(project)
132146
else:
133147
raise SAIncorrectProjectArgument(project)
148+
current_frame = inspect.currentframe()
149+
outer_function = inspect.getframeinfo(current_frame.f_back).function
150+
outer_outer_function = inspect.getframeinfo(
151+
current_frame.f_back.f_back
152+
).function
153+
if project.get("type") and project["type"] == "Video" \
154+
and (outer_function in common.VIDEO_DEPRICATED_FUNCTIONS
155+
or outer_outer_function in common.VIDEO_DEPRICATED_FUNCTIONS):
156+
raise SABaseException(
157+
0,
158+
"The function does not support projects containing videos attached with URLs"
159+
)
134160
return project, folder
135161

136162

superannotate/db/project_images.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .teams import get_team_metadata
2323
from ..mixp.decorators import Trackable
2424
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
25+
get_image_array_to_upload, __create_file, __copy_images, __move_images, get_project_folder_string
2626

2727
logger = logging.getLogger("superannotate-python-sdk")
2828
_api = API.get_instance()
@@ -118,7 +118,7 @@ def upload_image_to_project(
118118
except Exception as e:
119119
raise SABaseException(0, "Couldn't upload to data server.") from e
120120

121-
__create_image(
121+
__create_file(
122122
[img_name], [key],
123123
project,
124124
annotation_status,
@@ -640,8 +640,8 @@ def assign_folder(project, folder_name, users):
640640

641641
@Trackable
642642
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
643+
"""Removes assignment of given folder for all assignees.
644+
With SDK, the user can be assigned to a role in the project
645645
with the share_project function.
646646
647647
:param project: project name or folder path (e.g., "project1/folder1")

0 commit comments

Comments
 (0)