Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3696b47
public links implemented
ero1311 Mar 17, 2021
5f3e91b
Merge branch 'master' of https://github.com/superannotateai/superanno…
ero1311 Mar 18, 2021
9a0b7ed
Merge branch 'master' of https://github.com/superannotateai/superanno…
ero1311 Mar 19, 2021
912870e
Merge branch 'master' of https://github.com/superannotateai/superanno…
ero1311 Mar 19, 2021
2c6a946
refactored to avoid code duplication in attach image urls
ero1311 Mar 22, 2021
2c6df3d
Merge branch 'master' of https://github.com/superannotateai/superanno…
ero1311 Mar 23, 2021
c4e142c
Disabled some SDK functions for projects with URL attached images
ero1311 Mar 23, 2021
8a372c7
add num to send limit
hovnatan Mar 24, 2021
b98fd06
Send 1000 images to delete
hovnatan Mar 24, 2021
299af4b
Version bump
hovnatan Mar 24, 2021
ed537d5
Version bump
hovnatan Mar 24, 2021
5cdb714
Fix fuse creation
hovnatan Mar 25, 2021
74bf115
Code cleanup
hovnatan Mar 26, 2021
2c75dfb
Code cleanup
hovnatan Mar 26, 2021
cad9287
Update makefile
hovnatan Mar 26, 2021
15fed8c
Version bump
hovnatan Mar 26, 2021
bf5045c
Docs update
hovnatan Mar 26, 2021
e3325fd
Merge branch 'master' of https://github.com/superannotateai/superanno…
ero1311 Mar 26, 2021
35bc63f
handling duplicates in csv in attach URLs, disabling fuse generation …
ero1311 Mar 26, 2021
49a083a
fixed docstring to include dupe images list on return
ero1311 Mar 26, 2021
ddf25ce
Merge pull request #15 from superannotateai/public_links_dev
ero1311 Mar 29, 2021
d32cb8d
Update version.py
rcmanga Mar 29, 2021
27b6720
fixed bug in run_training disabling
ero1311 Mar 30, 2021
5bf077c
added URL attaching to cli docs
ero1311 Mar 30, 2021
798821b
Update version.py
rcmanga Mar 30, 2021
5cd1775
prepare_export enhancement for projects with attached URLs
ero1311 Mar 31, 2021
1ec95ee
Download files in chunks
hovnatan Mar 31, 2021
8d63a50
Update version.py
rcmanga Mar 31, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ PYTESTS=pytest
COVERAGE=coverage

tests: check_formatting docs
$(PYTESTS) -n auto --full-trace --verbose tests
$(PYTESTS) -n auto --full-trace tests

stress-tests: SA_STRESS_TESTS=1
stress-tests: tests
Expand Down
13 changes: 13 additions & 0 deletions docs/source/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ to look for. If the argument is not given then value *jpg,jpeg,png,tif,tiff,webp

----------

.. _ref_attach_image_urls:

Attaching image URLs
~~~~~~~~~~~~~~~~~~~~

To attach image URLs to project use:

.. code-block:: bash
superannotatecli attach-image-urls --project <project_name/folder_name> --attachments <csv_path> [--annotation_status <annotation_status>]
----------

.. _ref_upload_videos:

Uploading videos
Expand Down
1 change: 1 addition & 0 deletions docs/source/superannotate.sdk.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ ________
.. autofunction:: superannotate.delete_folders
.. autofunction:: superannotate.rename_folder
.. autofunction:: superannotate.upload_images_to_project
.. autofunction:: superannotate.attach_image_urls_to_project
.. autofunction:: superannotate.upload_images_from_public_urls_to_project
.. autofunction:: superannotate.upload_images_from_google_cloud_to_project
.. autofunction:: superannotate.upload_images_from_azure_blob_to_project
Expand Down
4 changes: 4 additions & 0 deletions docs/source/tutorial.sdk.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ To create a new "Vector" project with name "Example Project 1" and description

sa.create_project(project, "test", "Vector")

.. warning::

In general, SDK functions are not thread-safe.

Creating a folder in a project
______________________________

Expand Down
4 changes: 2 additions & 2 deletions superannotate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ def consensus(*args, **kwargs):
upload_images_from_google_cloud_to_project,
upload_images_from_public_urls_to_project,
upload_images_from_s3_bucket_to_project, upload_images_to_project,
upload_preannotations_from_folder_to_project, upload_video_to_project,
upload_videos_from_folder_to_project
attach_image_urls_to_project, upload_preannotations_from_folder_to_project,
upload_video_to_project, upload_videos_from_folder_to_project
)
from .db.search_projects import search_projects
from .db.teams import (
Expand Down
27 changes: 27 additions & 0 deletions superannotate/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def main():
create_folder(command, further_args)
elif command == "upload-images":
image_upload(command, further_args)
elif command == "attach-image-urls":
attach_image_urls(command, further_args)
elif command == "upload-videos":
video_upload(command, further_args)
elif command in ["upload-preannotations", "upload-annotations"]:
Expand Down Expand Up @@ -288,6 +290,31 @@ def image_upload(command_name, args):
)


def attach_image_urls(command_name, args):
parser = argparse.ArgumentParser(prog=_CLI_COMMAND + " " + command_name)
parser.add_argument(
'--project', required=True, help='Project name to upload'
)
parser.add_argument(
'--attachments',
required=True,
help='path to csv file on attachments metadata'
)
parser.add_argument(
'--annotation_status',
required=False,
default="NotStarted",
help=
'Set images\' annotation statuses after upload. Default is NotStarted'
)
args = parser.parse_args(args)
sa.attach_image_urls_to_project(
project=args.project,
attachments=args.attachments,
annotation_status=args.annotation_status
)


def export_project(command_name, args):
parser = argparse.ArgumentParser(prog=_CLI_COMMAND + " " + command_name)
parser.add_argument(
Expand Down
12 changes: 12 additions & 0 deletions superannotate/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@
"Completed": 5,
"Skipped": 6
}

_UPLOAD_STATES_STR_TO_CODES = {"Initial": 1, "Basic": 2, "External": 3}
_UPLOAD_STATES_CODES_TO_STR = {1: "Initial", 2: "Basic", 3: "External"}

_USER_ROLES = {"Admin": 2, "Annotator": 3, "QA": 4, "Customer": 5, "Viewer": 6}
_AVAILABLE_SEGMENTATION_MODELS = ['autonomous', 'generic']
_MODEL_TRAINING_STATUSES = {
Expand Down Expand Up @@ -118,6 +122,14 @@ def annotation_status_str_to_int(annotation_status):
return _ANNOTATION_STATUSES[annotation_status]


def upload_state_str_to_int(upload_state):
return _UPLOAD_STATES_STR_TO_CODES[upload_state]


def upload_state_int_to_str(upload_state):
return _UPLOAD_STATES_CODES_TO_STR[upload_state]


def annotation_status_int_to_str(annotation_status):
"""Converts metadata annotation_status int value to a string

Expand Down
53 changes: 35 additions & 18 deletions superannotate/db/exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
import zipfile
from datetime import datetime
from pathlib import Path
import shutil

import boto3
import requests
from tqdm import tqdm

from ..api import API
from ..common import annotation_status_str_to_int
from ..common import annotation_status_str_to_int, upload_state_int_to_str
from ..exceptions import (
SABaseException, SAExistingExportNameException,
SANonExistingExportNameException
Expand Down Expand Up @@ -123,6 +124,12 @@ def prepare_export(
"""
if not isinstance(project, dict):
project = get_project_metadata_bare(project)
upload_state = upload_state_int_to_str(project.get("upload_state"))
if upload_state == "External" and include_fuse == True:
logger.info(
"Include fuse functionality is not supported for projects containing images attached with URLs"
)
include_fuse = False
team_id, project_id = project["team_id"], project["id"]
if annotation_statuses is None:
annotation_statuses = [2, 3, 4, 5]
Expand Down Expand Up @@ -203,6 +210,15 @@ def __upload_files_to_aws_thread(
already_uploaded[i] = True


def _download_file(url, local_filename):
with requests.get(url, stream=True) as r:
r.raise_for_status()
with open(local_filename, 'wb') as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
return local_filename


def download_export(
project, export, folder_path, extract_zip_contents=True, to_s3_bucket=None
):
Expand Down Expand Up @@ -237,25 +253,26 @@ def download_export(
break

filename = Path(res['path']).name
r = requests.get(res['download'], allow_redirects=True)
if to_s3_bucket is None:
filepath = Path(folder_path) / filename
open(filepath, 'wb').write(r.content)
if extract_zip_contents:
with zipfile.ZipFile(filepath, 'r') as f:
f.extractall(folder_path)
Path.unlink(filepath)
logger.info("Extracted %s to folder %s", filepath, folder_path)
else:
logger.info("Downloaded export ID %s to %s", res['id'], filepath)
else:
with tempfile.TemporaryDirectory() as tmpdirname:
filepath = Path(tmpdirname) / filename
open(filepath, 'wb').write(r.content)
with tempfile.TemporaryDirectory() as tmpdirname:
temp_filepath = Path(tmpdirname) / filename
_download_file(res['download'], temp_filepath)
if to_s3_bucket is None:
filepath = Path(folder_path) / filename
shutil.copyfile(temp_filepath, filepath)
if extract_zip_contents:
with zipfile.ZipFile(filepath, 'r') as f:
f.extractall(tmpdirname)
f.extractall(folder_path)
Path.unlink(filepath)
logger.info("Extracted %s to folder %s", filepath, folder_path)
else:
logger.info(
"Downloaded export ID %s to %s", res['id'], filepath
)
else:
if extract_zip_contents:
with zipfile.ZipFile(temp_filepath, 'r') as f:
f.extractall(tmpdirname)
Path.unlink(temp_filepath)
files_to_upload = []
for file in Path(tmpdirname).rglob("*.*"):
if not file.is_file():
Expand Down Expand Up @@ -290,4 +307,4 @@ def download_export(
t.join()
finish_event.set()
tqdm_thread.join()
logger.info("Exported to AWS %s/%s", to_s3_bucket, folder_path)
logger.info("Exported to AWS %s/%s", to_s3_bucket, folder_path)
30 changes: 26 additions & 4 deletions superannotate/db/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,11 @@ def download_image(
:return: paths of downloaded image and annotations if included
:rtype: tuple
"""
if (include_fuse or include_overlay) and not include_annotations:
raise SABaseException(
0,
"To download fuse or overlay image need to set include_annotations=True in download_image"
)
if not Path(local_dir_path).is_dir():
raise SABaseException(
0, f"local_dir_path {local_dir_path} is not an existing directory"
Expand All @@ -631,6 +636,12 @@ def download_image(
)

project, project_folder = get_project_and_folder_metadata(project)
upload_state = common.upload_state_int_to_str(project.get("upload_state"))
if upload_state == "External":
raise SABaseException(
0,
"The function does not support projects containing images attached with URLs"
)
img = get_image_bytes(
(project, project_folder), image_name, variant=variant
)
Expand Down Expand Up @@ -698,6 +709,13 @@ def get_image_bytes(project, image_name, variant='original'):
:return: io.BytesIO() of the image
:rtype: io.BytesIO()
"""
project, project_folder = get_project_and_folder_metadata(project)
upload_state = common.upload_state_int_to_str(project.get("upload_state"))
if upload_state == "External":
raise SABaseException(
0,
"The function does not support projects containing images attached with URLs"
)
if variant not in ["original", "lores"]:
raise SABaseException(
0, "Image download variant should be either original or lores"
Expand Down Expand Up @@ -818,7 +836,7 @@ def _get_image_pre_or_annotations(project, image_name, pre):
fill_class_and_attribute_names(res_json, annotation_classes_dict)
result = {
f"{pre}annotation_json":
response.json(),
res_json,
f"{pre}annotation_json_filename":
common.get_annotation_json_name(image_name, project_type)
}
Expand Down Expand Up @@ -1041,12 +1059,11 @@ def create_fuse_image(
(image_size[1], image_size[0], 4), [0, 0, 0, 255], np.uint8
)
fi_ovl[:, :, :3] = np.array(pil_image)
fi_pil_ovl = Image.fromarray(fi_ovl)
draw_ovl = ImageDraw.Draw(fi_pil_ovl)
if project_type == "Vector":
fi_pil = Image.fromarray(fi)
draw = ImageDraw.Draw(fi_pil)
if output_overlay:
fi_pil_ovl = Image.fromarray(fi_ovl)
draw_ovl = ImageDraw.Draw(fi_pil_ovl)
for annotation in annotation_json["instances"]:
if "className" not in annotation:
continue
Expand Down Expand Up @@ -1159,6 +1176,11 @@ def create_fuse_image(
temp_mask = np.alltrue(annotation_mask == part_color, axis=2)
fi[temp_mask] = fill_color
fi_pil = Image.fromarray(fi)
alpha = 0.5 # transparency measure
if output_overlay:
fi_pil_ovl = Image.fromarray(
cv2.addWeighted(fi, alpha, fi_ovl, 1 - alpha, 0)
)

if in_memory:
if output_overlay:
Expand Down
Loading