Skip to content

Commit aaa0d34

Browse files
Vaghinak BasentsyanVaghinak Basentsyan
authored andcommitted
Added sdk/cli function attach_document_urls_to_project
1 parent 9bab7e1 commit aaa0d34

File tree

12 files changed

+430
-171
lines changed

12 files changed

+430
-171
lines changed

src/superannotate/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
from superannotate.lib.app.interface.sdk_interface import aggregate_annotations_as_df
4646
from superannotate.lib.app.interface.sdk_interface import assign_folder
4747
from superannotate.lib.app.interface.sdk_interface import assign_images
48+
from superannotate.lib.app.interface.sdk_interface import (
49+
attach_document_urls_to_project,
50+
)
4851
from superannotate.lib.app.interface.sdk_interface import attach_image_urls_to_project
4952
from superannotate.lib.app.interface.sdk_interface import attach_video_urls_to_project
5053
from superannotate.lib.app.interface.sdk_interface import benchmark
@@ -247,6 +250,7 @@
247250
"upload_images_from_folder_to_project",
248251
"attach_image_urls_to_project",
249252
"attach_video_urls_to_project",
253+
"attach_document_urls_to_project",
250254
# Video Section
251255
"upload_videos_from_folder_to_project",
252256
# Annotation Section

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def aggregate_annotations_as_df(
179179
and "___objects.json" not in json_paths[0].name
180180
):
181181
raise AppException(
182-
"The function does not support projects containing videos attached with URLs"
182+
"The function does not support projects containing videos / documents attached with URLs"
183183
)
184184

185185
if verbose:

src/superannotate/lib/app/input_converters/conversion.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ def export_annotation(
143143
project_type="Vector",
144144
task="object_detection",
145145
):
146-
if project_type == "Video":
146+
if project_type == "Video" or project_type == "Text":
147147
raise AppValidationException(
148-
"The function does not support projects containing videos attached with URLs"
148+
f"The function does not support projects containing {project_type} attached with URLs"
149149
)
150150
"""Converts SuperAnnotate annotation formate to the other annotation formats. Currently available (project_type, task) combinations for converter
151151
presented below:

src/superannotate/lib/app/input_converters/sa_conversion.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def sa_convert_project_type(input_dir, output_dir):
182182
img_names = from_vector_to_pixel(json_paths, output_dir)
183183
elif ".json" in json_paths[0].name:
184184
raise AppException(
185-
"The function does not support projects containing videos attached with URLs"
185+
"The function does not support projects containing videos / document attached with URLs"
186186
)
187187
else:
188188
raise AppException(

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from lib.app.helpers import split_project_path
1515
from lib.app.input_converters.conversion import import_annotation
1616
from lib.app.interface.base_interface import BaseInterfaceFacade
17+
from lib.app.interface.sdk_interface import attach_document_urls_to_project
1718
from lib.app.interface.sdk_interface import attach_image_urls_to_project
1819
from lib.app.interface.sdk_interface import attach_video_urls_to_project
1920
from lib.app.interface.sdk_interface import create_folder
@@ -263,6 +264,17 @@ def attach_video_urls(
263264
)
264265
sys.exit(0)
265266

267+
@staticmethod
268+
def attach_document_urls(
269+
project: str, attachments: str, annotation_status: Optional[Any] = None
270+
):
271+
attach_document_urls_to_project(
272+
project=project,
273+
attachments=attachments,
274+
annotation_status=annotation_status,
275+
)
276+
sys.exit(0)
277+
266278
def _attach_urls(
267279
self, project: str, attachments: str, annotation_status: Optional[Any] = None
268280
):

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

Lines changed: 142 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -523,10 +523,14 @@ def copy_image(
523523
destination_project
524524
)
525525

526-
project = controller.get_project_metadata(destination_project).data
527-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
526+
project = controller.get_project_metadata(destination_project).data["project"]
527+
if (
528+
project.project_type == constances.ProjectType.VIDEO.value
529+
or project.project_type == constances.ProjectType.DOCUMENT.value
530+
):
528531
raise AppValidationException(
529-
"The function does not support projects containing videos attached with URLs"
532+
"The function does not support projects containing "
533+
f"{constances.ProjectType.get_name(project.project_type)} attached with URLs"
530534
)
531535

532536
img_bytes = get_image_bytes(project=source_project, image_name=image_name)
@@ -778,10 +782,15 @@ def move_images(
778782
"""
779783
project_name, source_folder_name = extract_project_folder(source_project)
780784

781-
project = controller.get_project_metadata(project_name).data
782-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
785+
project = controller.get_project_metadata(project_name).data["project"]
786+
787+
if (
788+
project.project_type == constances.ProjectType.VIDEO.value
789+
or project.project_type == constances.ProjectType.DOCUMENT.value
790+
):
783791
raise AppValidationException(
784-
"The function does not support projects containing videos attached with URLs"
792+
"The function does not support projects containing "
793+
f"{constances.ProjectType.get_name(project.project_type)} attached with URLs"
785794
)
786795

787796
_, destination_folder_name = extract_project_folder(destination_project)
@@ -1132,13 +1141,16 @@ def assign_images(project: Union[str, dict], image_names: List[str], user: str):
11321141
project_name, folder_name = extract_project_folder(project)
11331142
if not folder_name:
11341143
folder_name = "root"
1135-
contributors = (
1136-
controller.get_project_metadata(
1137-
project_name=project_name, include_contributors=True
1144+
project_entity = controller.get_project_metadata(
1145+
project_name=project_name, include_contributors=True
1146+
).data["project"]
1147+
1148+
if project_entity.project_type == constances.ProjectType.DOCUMENT.value:
1149+
raise AppValidationException(
1150+
"The function does not support projects containing document attached with URLs"
11381151
)
1139-
.data["project"]
1140-
.users
1141-
)
1152+
contributors = project_entity.users
1153+
11421154
contributor = None
11431155
for c in contributors:
11441156
if c["user_id"] == user:
@@ -1312,8 +1324,8 @@ def upload_images_from_google_cloud_to_project(
13121324
failed_images = []
13131325
duplicated_images = []
13141326
project_name, folder_name = extract_project_folder(project)
1315-
project = controller.get_project_metadata(project_name).data
1316-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
1327+
project = controller.get_project_metadata(project_name).data["project"]
1328+
if project.project_type == constances.ProjectType.VIDEO.value:
13171329
raise AppValidationException(
13181330
"The function does not support projects containing videos attached with URLs"
13191331
)
@@ -1413,8 +1425,8 @@ def upload_images_from_azure_blob_to_project(
14131425
failed_images = []
14141426
duplicated_images = []
14151427
project_name, folder_name = extract_project_folder(project)
1416-
project = controller.get_project_metadata(project_name).data
1417-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
1428+
project = controller.get_project_metadata(project_name).data["project"]
1429+
if project.project_type == constances.ProjectType.VIDEO.value:
14181430
raise AppValidationException(
14191431
"The function does not support projects containing videos attached with URLs"
14201432
)
@@ -1865,7 +1877,11 @@ def upload_videos_from_folder_to_project(
18651877
"""
18661878

18671879
project_name, folder_name = extract_project_folder(project)
1868-
1880+
project = controller.get_project_metadata(project_name).data["project"]
1881+
if project.project_type == constances.ProjectType.DOCUMENT.value:
1882+
raise AppValidationException(
1883+
"The function does not support projects containing document attached with URLs"
1884+
)
18691885
video_paths = []
18701886
for extension in extensions:
18711887
if not recursive_subfolders:
@@ -2158,10 +2174,14 @@ def move_image(
21582174
)
21592175

21602176
source_project_name, source_folder_name = extract_project_folder(source_project)
2161-
project = controller.get_project_metadata(source_project_name).data
2162-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
2177+
project = controller.get_project_metadata(source_project_name).data["project"]
2178+
if (
2179+
project.project_type == constances.ProjectType.VIDEO.value
2180+
or project.project_type == constances.ProjectType.DOCUMENT.value
2181+
):
21632182
raise AppValidationException(
2164-
"The function does not support projects containing videos attached with URLs"
2183+
"The function does not support projects containing "
2184+
f"{constances.ProjectType.get_name(project.project_type)} attached with URLs"
21652185
)
21662186

21672187
destination_project, destination_folder = extract_project_folder(
@@ -2240,6 +2260,9 @@ def download_export(
22402260
extract_zip_contents=extract_zip_contents,
22412261
to_s3_bucket=to_s3_bucket,
22422262
)
2263+
if response.errors:
2264+
raise AppException(response.errors)
2265+
22432266
downloaded_folder_path = response.data
22442267

22452268
if to_s3_bucket:
@@ -2404,10 +2427,14 @@ def attach_image_urls_to_project(project, attachments, annotation_status="NotSta
24042427
:rtype: tuple
24052428
"""
24062429
project_name, folder_name = extract_project_folder(project)
2407-
project = controller.get_project_metadata(project_name).data
2408-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
2430+
project = controller.get_project_metadata(project_name).data["project"]
2431+
if (
2432+
project.project_type == constances.ProjectType.VIDEO.value
2433+
or project.project_type == constances.ProjectType.DOCUMENT.value
2434+
):
24092435
raise AppValidationException(
2410-
"The function does not support projects containing videos attached with URLs"
2436+
"The function does not support projects containing "
2437+
f"{constances.ProjectType.get_name(project.project_type)} attached with URLs"
24112438
)
24122439

24132440
image_data = pd.read_csv(attachments, dtype=str)
@@ -2461,8 +2488,8 @@ def attach_video_urls_to_project(project, attachments, annotation_status="NotSta
24612488
:rtype: (list, list, list)
24622489
"""
24632490
project_name, folder_name = extract_project_folder(project)
2464-
project = controller.get_project_metadata(project_name).data
2465-
if project["project"].project_type != constances.ProjectType.VIDEO.value:
2491+
project = controller.get_project_metadata(project_name).data["project"]
2492+
if project.project_type != constances.ProjectType.VIDEO.value:
24662493
raise AppValidationException("The function does not support")
24672494

24682495
image_data = pd.read_csv(attachments, dtype=str)
@@ -2533,10 +2560,15 @@ def upload_annotations_from_folder_to_project(
25332560
"""
25342561

25352562
project_name, folder_name = extract_project_folder(project)
2536-
project = controller.get_project_metadata(project_name).data
2537-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
2563+
project = controller.get_project_metadata(project_name).data["project"]
2564+
2565+
if (
2566+
project.project_type == constances.ProjectType.VIDEO.value
2567+
or project.project_type == constances.ProjectType.DOCUMENT.value
2568+
):
25382569
raise AppValidationException(
2539-
"The function does not support projects containing videos attached with URLs"
2570+
"The function does not support projects containing "
2571+
f"{constances.ProjectType.get_name(project.project_type)} attached with URLs"
25402572
)
25412573

25422574
if recursive_subfolders:
@@ -2610,10 +2642,14 @@ def upload_preannotations_from_folder_to_project(
26102642
:rtype: tuple of list of strs
26112643
"""
26122644
project_name, folder_name = extract_project_folder(project)
2613-
project = controller.get_project_metadata(project_name).data
2614-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
2645+
project = controller.get_project_metadata(project_name).data["project"]
2646+
if (
2647+
project.project_type == constances.ProjectType.VIDEO.value
2648+
or project.project_type == constances.ProjectType.DOCUMENT.value
2649+
):
26152650
raise AppValidationException(
2616-
"The function does not support projects containing videos attached with URLs"
2651+
"The function does not support projects containing "
2652+
f"{constances.ProjectType.get_name(project.project_type)} attached with URLs"
26172653
)
26182654

26192655
if recursive_subfolders:
@@ -3449,10 +3485,14 @@ def upload_image_to_project(
34493485
"""
34503486
project_name, folder_name = extract_project_folder(project)
34513487

3452-
project = controller.get_project_metadata(project_name).data
3453-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
3488+
project = controller.get_project_metadata(project_name).data["project"]
3489+
if (
3490+
project.project_type == constances.ProjectType.VIDEO.value
3491+
or project.project_type == constances.ProjectType.DOCUMENT.value
3492+
):
34543493
raise AppValidationException(
3455-
"The function does not support projects containing videos attached with URLs"
3494+
"The function does not support projects containing "
3495+
f"{constances.ProjectType.get_name(project.project_type)} attached with URLs"
34563496
)
34573497

34583498
if not isinstance(img, io.BytesIO):
@@ -3539,10 +3579,15 @@ def upload_images_to_project(
35393579
uploaded_image_entities = []
35403580
failed_images = []
35413581
project_name, folder_name = extract_project_folder(project)
3542-
project = controller.get_project_metadata(project_name).data
3543-
if project["project"].project_type == constances.ProjectType.VIDEO.value:
3582+
project = controller.get_project_metadata(project_name).data["project"]
3583+
3584+
if (
3585+
project.project_type == constances.ProjectType.VIDEO.value
3586+
or project.project_type == constances.ProjectType.DOCUMENT.value
3587+
):
35443588
raise AppValidationException(
3545-
"The function does not support projects containing videos attached with URLs"
3589+
"The function does not support projects containing "
3590+
f"{constances.ProjectType.get_name(project.project_type)} attached with URLs"
35463591
)
35473592

35483593
ProcessedImage = namedtuple("ProcessedImage", ["uploaded", "path", "entity"])
@@ -3700,3 +3745,63 @@ def delete_annotations(project: str, image_names: List[str] = None):
37003745
)
37013746
if response.errors:
37023747
raise AppException(response.errors)
3748+
3749+
3750+
@Trackable
3751+
@typechecked
3752+
def attach_document_urls_to_project(
3753+
project: Union[str, dict],
3754+
attachments: Union[Path, str],
3755+
annotation_status: Optional[str] = "NotStarted",
3756+
):
3757+
"""Link documents on external storage to SuperAnnotate.
3758+
3759+
:param project: project name or project folder path
3760+
:type project: str or dict
3761+
:param attachments: path to csv file on attachments metadata
3762+
:type attachments: Path-like (str or Path)
3763+
:param annotation_status: value to set the annotation statuses of the linked documents: NotStarted InProgress QualityCheck Returned Completed Skipped
3764+
:type annotation_status: str
3765+
3766+
:return: list of attached documents, list of not attached documents, list of skipped documents
3767+
:rtype: tuple
3768+
"""
3769+
project_name, folder_name = extract_project_folder(project)
3770+
3771+
image_data = pd.read_csv(attachments, dtype=str)
3772+
image_data = image_data[~image_data["url"].isnull()]
3773+
if "name" in image_data.columns:
3774+
image_data["name"] = (
3775+
image_data["name"]
3776+
.fillna("")
3777+
.apply(lambda cell: cell if str(cell).strip() else str(uuid.uuid4()))
3778+
)
3779+
else:
3780+
image_data["name"] = [str(uuid.uuid4()) for _ in range(len(image_data.index))]
3781+
3782+
image_data = pd.DataFrame(image_data, columns=["name", "url"])
3783+
img_names_urls = image_data.rename(columns={"url": "path"}).to_dict(
3784+
orient="records"
3785+
)
3786+
list_of_not_uploaded = []
3787+
duplicate_images = []
3788+
for i in range(0, len(img_names_urls), 500):
3789+
response = controller.attach_urls(
3790+
project_name=project_name,
3791+
folder_name=folder_name,
3792+
files=ImageSerializer.deserialize(
3793+
img_names_urls[i : i + 500] # noqa: E203
3794+
),
3795+
annotation_status=annotation_status,
3796+
)
3797+
if response.errors:
3798+
list_of_not_uploaded.append(response.data[0])
3799+
duplicate_images.append(response.data[1])
3800+
3801+
list_of_uploaded = [
3802+
image["name"]
3803+
for image in img_names_urls
3804+
if image["name"] not in list_of_not_uploaded
3805+
]
3806+
3807+
return list_of_uploaded, list_of_not_uploaded, duplicate_images

src/superannotate/lib/core/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class ProjectType(BaseTitledEnum):
3232
VECTOR = "Vector", 1
3333
PIXEL = "Pixel", 2
3434
VIDEO = "Video", 3
35+
DOCUMENT = "Document", 4
3536

3637

3738
class UserRole(BaseTitledEnum):

0 commit comments

Comments
 (0)