From 547a1db279e452d498c16b893a8da12c0dd06c89 Mon Sep 17 00:00:00 2001 From: VavoTK Date: Sun, 6 Nov 2022 17:35:25 +0400 Subject: [PATCH 01/27] checking for 'id' only if attribute has one, else leave name as is --- src/superannotate/lib/core/usecases/images.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/superannotate/lib/core/usecases/images.py b/src/superannotate/lib/core/usecases/images.py index d7d978e6e..10abf0f09 100644 --- a/src/superannotate/lib/core/usecases/images.py +++ b/src/superannotate/lib/core/usecases/images.py @@ -1593,15 +1593,14 @@ def fill_classes_data(self, annotations: dict): attribute["groupName"] = annotation_class["attribute_groups"][ attribute["groupId"] ]["name"] - if attribute["id"] not in list( + if attribute.get("id") in list( annotation_class["attribute_groups"][attribute["groupId"]][ "attributes" ].keys() ): - continue - attribute["name"] = annotation_class["attribute_groups"][ - attribute["groupId"] - ]["attributes"][attribute["id"]] + attribute["name"] = annotation_class["attribute_groups"][ + attribute["groupId"] + ]["attributes"][attribute["id"]] def execute(self): if self.is_valid(): From a39bdf15a41715f36357858ef5665d504fd2a7b1 Mon Sep 17 00:00:00 2001 From: VavoTK Date: Sun, 6 Nov 2022 18:34:45 +0400 Subject: [PATCH 02/27] fixing chunk size issue with listing names --- src/superannotate/lib/core/usecases/images.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/superannotate/lib/core/usecases/images.py b/src/superannotate/lib/core/usecases/images.py index d7d978e6e..26184a443 100644 --- a/src/superannotate/lib/core/usecases/images.py +++ b/src/superannotate/lib/core/usecases/images.py @@ -849,6 +849,7 @@ def execute(self) -> Response: class UploadImagesToProject(BaseInteractiveUseCase): MAX_WORKERS = 10 + LIST_NAME_CHUNK_SIZE = 500 def __init__( self, @@ -1023,6 +1024,7 @@ def filter_paths(self, paths: List[str]): for path in paths: name_path_map[Path(path).name].append(path) + CHUNK_SIZE = UploadImagesFromFolderToProject.LIST_NAME_CHUNK_SIZE filtered_paths = [] duplicated_paths = [] for file_name in name_path_map: @@ -1030,17 +1032,22 @@ def filter_paths(self, paths: List[str]): duplicated_paths.append(name_path_map[file_name][1:]) filtered_paths.append(name_path_map[file_name][0]) - response = self._service_provider.items.list_by_names( - project=self._project, - folder=self._folder, - names=[image.split("/")[-1] for image in filtered_paths], - ) - if not response.ok: - self._response.errors = AppException(response.error) - return self._response + image_list = [] + for i in range(0, len(filtered_paths), CHUNK_SIZE): + response = self._service_provider.items.list_by_names( + project=self._project, + folder=self._folder, + names=[ + image.split("/")[-1] for image in filtered_paths[i : i + CHUNK_SIZE] + ], + ) + + if not response.ok: + raise AppException(response.error) + image_list.extend([image.name for image in response.data]) + image_list=set(image_list) images_to_upload = [] - image_list = [image.name for image in response.data] for path in filtered_paths: if Path(path).name not in image_list: From b40eb6599b979d8d4435587a0fef5ceb6d01f7f8 Mon Sep 17 00:00:00 2001 From: VavoTK Date: Sun, 6 Nov 2022 19:36:17 +0400 Subject: [PATCH 03/27] fix getting project --- .../lib/app/interface/sdk_interface.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index e8d0a620f..2e0cc5731 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -795,13 +795,19 @@ def assign_folder( :type users: list of str """ - contributors = ( - self.controller.projects.get_by_name( - project_name=project_name, include_contributors=True - ) - .data["project"] - .users + response = self.controller.projects.get_by_name(name=project_name) + if response.errors: + raise AppException(response.errors) + project = response.data + response = self.controller.projects.get_metadata( + project = project, + include_contributors = True ) + + if response.errors: + raise AppException(response.errors) + + contributors=response.data.users verified_users = [i["user_id"] for i in contributors] verified_users = set(users).intersection(set(verified_users)) unverified_contributor = set(users) - verified_users From e97c847d44ca0fa28dc02c0ef2ea3b4d16d056a9 Mon Sep 17 00:00:00 2001 From: VavoTK Date: Sun, 6 Nov 2022 19:36:54 +0400 Subject: [PATCH 04/27] make validate --- src/superannotate/lib/app/interface/sdk_interface.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index 2e0cc5731..fb78ced55 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -800,14 +800,13 @@ def assign_folder( raise AppException(response.errors) project = response.data response = self.controller.projects.get_metadata( - project = project, - include_contributors = True + project=project, include_contributors=True ) if response.errors: raise AppException(response.errors) - contributors=response.data.users + contributors = response.data.users verified_users = [i["user_id"] for i in contributors] verified_users = set(users).intersection(set(verified_users)) unverified_contributor = set(users) - verified_users From db74f92e9ca64ad09780b478787684a9820133b8 Mon Sep 17 00:00:00 2001 From: VavoTK Date: Mon, 7 Nov 2022 10:17:09 +0400 Subject: [PATCH 05/27] using correct baseclasses --- src/superannotate/lib/core/usecases/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/superannotate/lib/core/usecases/images.py b/src/superannotate/lib/core/usecases/images.py index 26184a443..7954b0bcd 100644 --- a/src/superannotate/lib/core/usecases/images.py +++ b/src/superannotate/lib/core/usecases/images.py @@ -1024,7 +1024,7 @@ def filter_paths(self, paths: List[str]): for path in paths: name_path_map[Path(path).name].append(path) - CHUNK_SIZE = UploadImagesFromFolderToProject.LIST_NAME_CHUNK_SIZE + CHUNK_SIZE = UploadImagesToProject.LIST_NAME_CHUNK_SIZE filtered_paths = [] duplicated_paths = [] for file_name in name_path_map: From 72038bc441018468e29c53523294cdf0be4b95b6 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Mon, 7 Nov 2022 10:40:57 +0400 Subject: [PATCH 06/27] Deprecation Tasks --- docs/source/superannotate.sdk.rst | 2 - .../lib/app/interface/cli_interface.py | 38 +--- .../lib/app/interface/sdk_interface.py | 197 ++---------------- src/superannotate/lib/core/usecases/images.py | 143 ------------- .../lib/infrastructure/controller.py | 26 --- 5 files changed, 23 insertions(+), 383 deletions(-) diff --git a/docs/source/superannotate.sdk.rst b/docs/source/superannotate.sdk.rst index fd4e7fac6..53b626f4d 100644 --- a/docs/source/superannotate.sdk.rst +++ b/docs/source/superannotate.sdk.rst @@ -42,7 +42,6 @@ ________ .. automethod:: superannotate.SAClient.upload_videos_from_folder_to_project .. _ref_upload_annotations_from_folder_to_project: .. automethod:: superannotate.SAClient.upload_annotations_from_folder_to_project -.. automethod:: superannotate.SAClient.upload_preannotations_from_folder_to_project .. automethod:: superannotate.SAClient.add_contributors_to_project .. automethod:: superannotate.SAClient.get_project_settings .. automethod:: superannotate.SAClient.set_project_default_image_quality_in_editor @@ -107,7 +106,6 @@ ______ .. automethod:: superannotate.SAClient.download_image .. automethod:: superannotate.SAClient.download_image_annotations .. automethod:: superannotate.SAClient.upload_image_annotations -.. automethod:: superannotate.SAClient.copy_image .. automethod:: superannotate.SAClient.pin_image .. automethod:: superannotate.SAClient.add_annotation_bbox_to_image .. automethod:: superannotate.SAClient.add_annotation_point_to_image diff --git a/src/superannotate/lib/app/interface/cli_interface.py b/src/superannotate/lib/app/interface/cli_interface.py index 7fe0ffec0..410e3c506 100644 --- a/src/superannotate/lib/app/interface/cli_interface.py +++ b/src/superannotate/lib/app/interface/cli_interface.py @@ -129,27 +129,6 @@ def export_project( ) sys.exit(0) - def upload_preannotations( - self, project, folder, dataset_name=None, task=None, format=None - ): - """ - To upload preannotations from folder to project use - Optional argument format accepts input annotation format. It can have COCO or SuperAnnotate values. - If the argument is not given then SuperAnnotate (the native annotation format) is assumed. - Only when COCO format is specified dataset-name and task arguments are required. - dataset-name specifies JSON filename (without extension) in . - task specifies the COCO task for conversion. Please see import_annotation_format for more details. - """ - self._upload_annotations( - project=project, - folder=folder, - format=format, - dataset_name=dataset_name, - task=task, - pre=True, - ) - sys.exit(0) - def upload_annotations( self, project, folder, dataset_name=None, task=None, format=None ): @@ -167,13 +146,10 @@ def upload_annotations( format=format, dataset_name=dataset_name, task=task, - pre=False, ) sys.exit(0) - def _upload_annotations( - self, project, folder, format, dataset_name, task, pre=True - ): + def _upload_annotations(self, project, folder, format, dataset_name, task): project_folder_name = project project_name, folder_name = split_project_path(project) project = SAClient().controller.get_project(project_name) @@ -197,14 +173,10 @@ def _upload_annotations( task=task, ) annotations_path = temp_dir - if pre: - SAClient().upload_preannotations_from_folder_to_project( - project_folder_name, annotations_path - ) - else: - SAClient().upload_annotations_from_folder_to_project( - project_folder_name, annotations_path - ) + + SAClient().upload_annotations_from_folder_to_project( + project_folder_name, annotations_path + ) sys.exit(0) def attach_image_urls( diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index e8d0a620f..5c969c030 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -437,112 +437,6 @@ def search_folders( ] return [folder.name for folder in data if not folder.is_root] - def copy_image( - self, - source_project: Union[NotEmptyStr, dict], - image_name: NotEmptyStr, - destination_project: Union[NotEmptyStr, dict], - include_annotations: Optional[StrictBool] = False, - copy_annotation_status: Optional[StrictBool] = False, - copy_pin: Optional[StrictBool] = False, - ): - """Copy image to a project. The image's project is the same as destination - project then the name will be changed to _()., - where is the next available number deducted from project image list. - - :param source_project: project name plus optional subfolder in the project (e.g., "project1/folder1") or - metadata of the project of source project - :type source_project: str or dict - :param image_name: image name - :type image_name: str - :param destination_project: project name or metadata of the project of destination project - :type destination_project: str or dict - :param include_annotations: enables annotations copy - :type include_annotations: bool - :param copy_annotation_status: enables annotations status copy - :type copy_annotation_status: bool - :param copy_pin: enables image pin status copy - :type copy_pin: bool - """ - warning_msg = "The SAClient.copy_image method will be deprecated with the Superannotate Python SDK 4.4.6 release" - warnings.warn(warning_msg, DeprecationWarning) - logger.warning(warning_msg) - source_project_name, source_folder_name = extract_project_folder(source_project) - destination_project_name, destination_folder_name = extract_project_folder( - destination_project - ) - source_project_metadata = self.controller.projects.get_by_name( - source_project_name - ).data - destination_project_metadata = self.controller.projects.get_by_name( - destination_project_name - ).data - - if destination_project_metadata.type.value in [ - constants.ProjectType.VIDEO.value, - constants.ProjectType.DOCUMENT.value, - ] or source_project_metadata.type.value in [ - constants.ProjectType.VIDEO.value, - constants.ProjectType.DOCUMENT.value, - ]: - raise AppException(LIMITED_FUNCTIONS[source_project_metadata.type]) - - response = self.controller.copy_image( - from_project_name=source_project_name, - from_folder_name=source_folder_name, - to_project_name=destination_project_name, - to_folder_name=destination_folder_name, - image_name=image_name, - copy_annotation_status=copy_annotation_status, - ) - if response.errors: - raise AppException(response.errors) - if copy_pin: - destination_project = self.controller.get_project( - destination_project_metadata - ) - _folder = self.controller.get_folder( - destination_project, destination_folder_name - ) - item = self.controller.items.get_by_name( - destination_project_metadata, _folder, image_name - ).data - item.is_pinned = 1 - self.controller.items.update( - project=destination_project_metadata, - folder=_folder, - image_name=image_name, - is_pinned=1, - ) - if include_annotations: - source_project = self.controller.get_project(source_project_name) - source_folder = self.controller.get_folder( - source_project, source_folder_name - ) - source_image = self.controller.items.get_by_name( - source_project, source_folder, image_name - ).data - destination_project = self.controller.get_project(destination_project) - destination_folder = self.controller.get_folder( - destination_project, destination_folder_name - ) - destination_image = self.controller.items.get_by_name( - destination_project, destination_folder, image_name - ).data - self.controller.annotation_classes.copy_multiple( - source_project=source_project, - source_folder=source_folder, - source_item=source_image, - destination_project=destination_project, - destination_folder=destination_folder, - destination_item=destination_image, - ) - - logger.info( - f"Copied image {source_project}/{image_name}" - f" to {destination_project_name}/{destination_folder_name}." - ) - def get_project_metadata( self, project: Union[NotEmptyStr, dict], @@ -1616,79 +1510,6 @@ def upload_annotations_from_folder_to_project( raise AppException(response.errors) return response.data - def upload_preannotations_from_folder_to_project( - self, - project: Union[NotEmptyStr, dict], - folder_path: Union[str, Path], - from_s3_bucket=None, - recursive_subfolders: Optional[StrictBool] = False, - ): - """Finds and uploads all JSON files in the folder_path as pre-annotations to the project. - - The JSON files should follow specific naming convention. For Vector - projects they should be named "___objects.json" (e.g., if - image is cats.jpg the annotation filename should be cats.jpg___objects.json), for Pixel projects - JSON file should be named "___pixel.json" and also second mask - image file should be present with the name "___save.png". In both cases - image with should be already present on the platform. - - Existing pre-annotations will be overwritten. - - :param project: project name or folder path (e.g., "project1/folder1") - :type project: str - :param folder_path: from which folder to upload the pre-annotations - :type folder_path: Path-like (str or Path) - :param from_s3_bucket: AWS S3 bucket to use. If None then folder_path is in local filesystem - :type from_s3_bucket: str - :param recursive_subfolders: enable recursive subfolder parsing - :type recursive_subfolders: bool - - :return: paths to pre-annotations uploaded and could-not-upload - :rtype: tuple of list of strs - """ - warning_msg = ( - "The SAClient.upload_preannotations_from_folder_to_project" - " method will be deprecated with the Superannotate Python SDK 4.4.6 release" - ) - warnings.warn(warning_msg, DeprecationWarning) - logger.warning(warning_msg) - project_name, folder_name = extract_project_folder(project) - project_folder_name = project_name + (f"/{folder_name}" if folder_name else "") - project = self.controller.get_project(project_name) - if project.type in [ - constants.ProjectType.VIDEO, - constants.ProjectType.DOCUMENT, - ]: - raise AppException(LIMITED_FUNCTIONS[project.type]) - if recursive_subfolders: - logger.info( - "When using recursive subfolder parsing same name annotations in different " - "subfolders will overwrite each other.", - ) - logger.info( - "The JSON files should follow a specific naming convention, matching file names already present " - "on the platform. Existing annotations will be overwritten" - ) - annotation_paths = get_annotation_paths( - folder_path, from_s3_bucket, recursive_subfolders - ) - logger.info( - f"Uploading {len(annotation_paths)} annotations from {folder_path} to the project {project_folder_name}." - ) - project, folder = self.controller.get_project_folder(project_name, folder_name) - response = self.controller.annotations.upload_from_folder( - project=project, - folder=folder, - team=self.controller.team, - annotation_paths=annotation_paths, # noqa: E203 - client_s3_bucket=from_s3_bucket, - folder_path=folder_path, - is_pre_annotations=True, - ) - if response.errors: - raise AppException(response.errors) - return response.data - def upload_image_annotations( self, project: Union[NotEmptyStr, dict], @@ -1971,6 +1792,12 @@ def add_annotation_bbox_to_image( :param error: if not None, marks annotation as error (True) or no-error (False) :type error: bool """ + warning_msg = ( + "The SAClient.add_annotation_bbox_to_image method will " + "be deprecated with the Superannotate Python SDK 4.4.7 release" + ) + warnings.warn(warning_msg, DeprecationWarning) + logger.warning(warning_msg) project_name, folder_name = extract_project_folder(project) project = self.controller.get_project(project_name) @@ -2035,6 +1862,12 @@ def add_annotation_point_to_image( :param error: if not None, marks annotation as error (True) or no-error (False) :type error: bool """ + warning_msg = ( + "The SAClient.add_annotation_point_to_image method will " + "be deprecated with the Superannotate Python SDK 4.4.7 release" + ) + warnings.warn(warning_msg, DeprecationWarning) + logger.warning(warning_msg) project, folder = self.controller.get_project_folder_by_path(project) if project.type in [ constants.ProjectType.VIDEO, @@ -2091,6 +1924,12 @@ def add_annotation_comment_to_image( :param resolved: comment resolve status :type resolved: bool """ + warning_msg = ( + "The SAClient.add_annotation_comment_to_image method will " + "be deprecated with the Superannotate Python SDK 4.4.7 release" + ) + warnings.warn(warning_msg, DeprecationWarning) + logger.warning(warning_msg) project_name, folder_name = extract_project_folder(project) project = self.controller.projects.get_by_name(project_name).data if project.type in [ diff --git a/src/superannotate/lib/core/usecases/images.py b/src/superannotate/lib/core/usecases/images.py index d7d978e6e..46c1f47a8 100644 --- a/src/superannotate/lib/core/usecases/images.py +++ b/src/superannotate/lib/core/usecases/images.py @@ -1287,149 +1287,6 @@ def execute(self): return self._response -class CopyImageUseCase(BaseUseCase): - def __init__( - self, - from_project: ProjectEntity, - from_folder: FolderEntity, - image_name: str, - to_project: ProjectEntity, - to_folder: FolderEntity, - service_provider: BaseServiceProvider, - s3_repo, - include_annotations: Optional[bool] = True, - copy_annotation_status: Optional[bool] = True, - copy_pin: Optional[bool] = True, - move=False, - ): - super().__init__() - self._from_project = from_project - self._from_folder = from_folder - self._image_name = image_name - self._to_project = to_project - self._to_folder = to_folder - self._s3_repo = s3_repo - self._include_annotations = include_annotations - self._copy_annotation_status = copy_annotation_status - self._copy_pin = copy_pin - self._service_provider = service_provider - self._move = move - - def validate_copy_path(self): - if ( - self._from_project.name == self._to_project.name - and self._from_folder.name == self._to_folder.name - ): - raise AppValidationException( - "Cannot move image if source_project == destination_project." - ) - - def validate_project_type(self): - if self._from_project.type in ( - constances.ProjectType.VIDEO.value, - constances.ProjectType.DOCUMENT.value, - ): - raise AppValidationException( - constances.LIMITED_FUNCTIONS[self._from_project.type] - ) - - def validate_limitations(self): - response = self._service_provider.get_limitations( - project=self._to_project, - folder=self._to_folder, - ) - if not response.ok: - raise AppValidationException(response.error) - - if self._move and self._from_project.id == self._to_project.id: - if self._from_folder.id == self._to_folder.id: - raise AppValidationException( - "Cannot move image if source_project == destination_project." - ) - - if response.data.folder_limit.remaining_image_count < 1: - raise AppValidationException(constances.COPY_FOLDER_LIMIT_ERROR_MESSAGE) - if response.data.project_limit.remaining_image_count < 1: - raise AppValidationException(constances.COPY_PROJECT_LIMIT_ERROR_MESSAGE) - if ( - response.data.user_limit - and response.data.user_limit.remaining_image_count < 1 - ): - raise AppValidationException(constances.COPY_SUPER_LIMIT_ERROR_MESSAGE) - - @property - def s3_repo(self): - self._auth_data = self._service_provider.get_s3_upload_auth_token( - project=self._to_project, folder=self._to_folder - ).data - if "error" in self._auth_data: - raise AppException(self._auth_data.get("error")) - return self._s3_repo( - self._auth_data["accessKeyId"], - self._auth_data["secretAccessKey"], - self._auth_data["sessionToken"], - self._auth_data["bucket"], - ) - - def execute(self) -> Response: - if self.is_valid(): - image = ( - GetImageUseCase( - project=self._from_project, - folder=self._from_folder, - image_name=self._image_name, - service_provider=self._service_provider, - ) - .execute() - .data - ) - - image_bytes = ( - GetImageBytesUseCase( - project=self._from_project, - folder=self._from_folder, - image=image, - service_provider=self._service_provider, - ) - .execute() - .data - ) - image_path = f"{self._to_folder}/{self._image_name}" - - auth_data = self._service_provider.get_s3_upload_auth_token( - project=self._to_project, folder=self._to_folder - ) - if not auth_data.ok: - raise AppException(auth_data.error) - s3_response = UploadImageS3UseCase( - project=self._to_project, - image_path=image_path, - image=image_bytes, - service_provider=self._service_provider, - upload_path=auth_data.data["filePath"], - s3_repo=self.s3_repo, - ).execute() - if s3_response.errors: - raise AppException(s3_response.errors) - image_entity = s3_response.data - del image_bytes - - attach_response = AttachFileUrlsUseCase( - project=self._to_project, - folder=self._to_folder, - attachments=[image_entity], - service_provider=self._service_provider, - annotation_status=image.annotation_status_code - if self._copy_annotation_status - else None, - upload_state_code=constances.UploadState.BASIC.value, - ).execute() - if attach_response.errors: - raise AppException(attach_response.errors) - self._response.data = image_entity - return self._response - - class DeleteAnnotations(BaseUseCase): POLL_AWAIT_TIME = 2 CHUNK_SIZE = 2000 diff --git a/src/superannotate/lib/infrastructure/controller.py b/src/superannotate/lib/infrastructure/controller.py index 2a89894db..604ff60c9 100644 --- a/src/superannotate/lib/infrastructure/controller.py +++ b/src/superannotate/lib/infrastructure/controller.py @@ -997,32 +997,6 @@ def update(self, project: ProjectEntity, folder: FolderEntity): ) return use_case.execute() - def copy_image( - self, - from_project_name: str, - from_folder_name: str, - to_project_name: str, - to_folder_name: str, - image_name: str, - copy_annotation_status: bool = False, - move: bool = False, - ): - from_project = self.get_project(from_project_name) - to_project = self.get_project(to_project_name) - to_folder = self.get_folder(to_project, to_folder_name) - use_case = usecases.CopyImageUseCase( - from_project=from_project, - from_folder=self.get_folder(from_project, from_folder_name), - to_project=to_project, - to_folder=to_folder, - service_provider=self.service_provider, - image_name=image_name, - s3_repo=self.s3_repo, - copy_annotation_status=copy_annotation_status, - move=move, - ) - return use_case.execute() - def un_assign_folder(self, project_name: str, folder_name: str): project_entity = self.get_project(project_name) folder = self.get_folder(project_entity, folder_name) From 8c8716af318c393e5ce3960f5db236d9f82b03e6 Mon Sep 17 00:00:00 2001 From: VavoTK Date: Mon, 7 Nov 2022 16:50:57 +0400 Subject: [PATCH 07/27] using the ssl_verify variable --- src/superannotate/lib/infrastructure/controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/superannotate/lib/infrastructure/controller.py b/src/superannotate/lib/infrastructure/controller.py index 2a89894db..0669f3dfd 100644 --- a/src/superannotate/lib/infrastructure/controller.py +++ b/src/superannotate/lib/infrastructure/controller.py @@ -761,7 +761,7 @@ def __init__(self, token: str, host: str, ssl_verify: bool, version: str): self._user_id = None self._reporter = None - http_client = HttpClient(api_url=host, token=token) + http_client = HttpClient(api_url=host, token=token, verify_ssl=ssl_verify) self.service_provider = ServiceProvider(http_client) self._team = self.get_team().data From 3a5403f70411f256d38f8cd207501edd06821001 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Mon, 7 Nov 2022 18:11:17 +0400 Subject: [PATCH 08/27] Fix encoding issue --- src/superannotate/lib/app/interface/sdk_interface.py | 2 +- src/superannotate/lib/core/usecases/annotations.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index 5c969c030..04df7a0cd 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -1277,7 +1277,7 @@ def create_annotation_classes_from_classes_json( file.seek(0) data = file else: - data = open(classes_json) + data = open(classes_json, encoding="utf-8") classes_json = json.load(data) try: annotation_classes = parse_obj_as(List[AnnotationClassEntity], classes_json) diff --git a/src/superannotate/lib/core/usecases/annotations.py b/src/superannotate/lib/core/usecases/annotations.py index d0290e6dc..fcc2c04c4 100644 --- a/src/superannotate/lib/core/usecases/annotations.py +++ b/src/superannotate/lib/core/usecases/annotations.py @@ -532,7 +532,7 @@ async def get_annotation( if self._project.type == constants.ProjectType.PIXEL.value: mask = self.get_annotation_from_s3(self._client_s3_bucket, mask_path) else: - async with aiofiles.open(path) as file: + async with aiofiles.open(path, encoding="utf-8") as file: content = await file.read() if self._project.type == constants.ProjectType.PIXEL.value: async with aiofiles.open(mask_path, "rb") as mask: @@ -835,7 +835,9 @@ def _get_annotation_json(self) -> tuple: ), ) else: - annotation_json = json.load(open(self._annotation_path)) + annotation_json = json.load( + open(self._annotation_path, encoding="utf-8") + ) if self._project.type == constants.ProjectType.PIXEL.value: mask = open( self._annotation_path.replace( @@ -1313,7 +1315,7 @@ def download_annotation_classes(self, path: str): if response.ok: classes_path = Path(path) / "classes" classes_path.mkdir(parents=True, exist_ok=True) - with open(classes_path / "classes.json", "w+") as file: + with open(classes_path / "classes.json", "w+", encoding="utf-8") as file: json.dump( [ i.dict( From 8cc7f81123adf968a80e126b5eb732639a4d1a9d Mon Sep 17 00:00:00 2001 From: VavoTK Date: Tue, 8 Nov 2022 17:08:50 +0400 Subject: [PATCH 09/27] adding pagination with chunk sizes --- .../lib/core/usecases/annotations.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/superannotate/lib/core/usecases/annotations.py b/src/superannotate/lib/core/usecases/annotations.py index fcc2c04c4..055242e78 100644 --- a/src/superannotate/lib/core/usecases/annotations.py +++ b/src/superannotate/lib/core/usecases/annotations.py @@ -409,6 +409,7 @@ def execute(self): class UploadAnnotationsFromFolderUseCase(BaseReportableUseCase): MAX_WORKERS = 16 CHUNK_SIZE = 100 + CHUNK_SIZE_PATHS = 500 CHUNK_SIZE_MB = 10 * 1024 * 1024 STATUS_CHANGE_CHUNK_SIZE = 100 AUTH_DATA_CHUNK_SIZE = 500 @@ -579,14 +580,27 @@ def get_existing_name_item_mapping( @property def annotation_upload_data(self) -> UploadAnnotationAuthData: - if not self._annotation_upload_data: - response = self._service_provider.get_annotation_upload_data( + CHUNK_SIZE=UploadAnnotationsFromFolderUseCase.CHUNK_SIZE_PATHS + + if self._annotation_upload_data: + return self._annotation_upload_data + + images={} + for i in range(0, len(self._item_ids), CHUNK_SIZE): + tmp = self._service_provider.get_annotation_upload_data( project=self._project, folder=self._folder, - item_ids=self._item_ids, + item_ids=self._item_ids[i:i+CHUNK_SIZE], + ) - if response.ok: - self._annotation_upload_data = response.data + if not tmp.ok: + raise AppException(tmp.errors) + else: + images.update(tmp.data.images) + + self._annotation_upload_data=tmp.data + self._annotation_upload_data.images=images + return self._annotation_upload_data @property From 50a1c229a555693644e1156018ae73116fe45a9e Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Tue, 8 Nov 2022 17:10:21 +0400 Subject: [PATCH 10/27] Update __init__.py --- src/superannotate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index 6dc56ced2..18c456941 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -2,7 +2,7 @@ import sys -__version__ = "4.4.5" +__version__ = "4.4.6dev1" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From 31e869ae19f9e5993ab62a03eb5f8f02afb24a83 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Wed, 9 Nov 2022 15:02:10 +0400 Subject: [PATCH 11/27] Create README.rst --- README.rst | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 README.rst diff --git a/README.rst b/README.rst new file mode 100644 index 000000000..573d0c75c --- /dev/null +++ b/README.rst @@ -0,0 +1,129 @@ +=============================== +SuperAnnotate Python SDK +=============================== + +|Python| |License| |Changelog| + +.. |Python| image:: https://img.shields.io/static/v1?label=python&message=3.7/3.8/3.9/3.10/3.11&color=blue&style=flat-square + :target: https://pypi.org/project/superannotate/ + :alt: Python Versions +.. |License| image:: https://img.shields.io/static/v1?label=license&message=MIT&color=green&style=flat-square + :target: https://github.com/superannotateai/superannotate-python-sdk/blob/master/LICENSE/ + :alt: License +.. |Changelog| image:: https://img.shields.io/static/v1?label=change&message=log&color=yellow&style=flat-square + :target: https://github.com/superannotateai/superannotate-python-sdk/blob/master/CHANGELOG.md + :alt: Changelog + +Welcome to the SuperAnnotate Python Software Development Kit (SDK), which enables Python programmers to create software that incorporates services of the platform and effortlessly integrates SuperAnnotate into their AI process. (check out the :ref:`supported features` below). + +Resources +--------------- + +- API Reference and User Guide available on `Read the Docs `_ +- `Platform documentation `_ +Authentication + +--------------- + +.. code-block:: python + + from superannotate import SAClient + # by environment variable SA_TOKEN + sa_client = SAClient() + # by token + sa_client = SAClient(token=’’) + # by config file + # default path is ~/.superannotate/config.json + sa_client = SAClient(config_path=“~/.superannotate/dev_config.json”) + +Using superannotate +------------------- + +.. code-block:: python + + from superannotate import SAClient + sa_client =SAClient() + project = “Dogs” + sa_client.create_project( + project_name=project, + project_description=“Test project generated via SDK”, + project_type=“Vector” + ) + sa_client.create_annotation_class( + project=project, + name=“dog”, + color=“#F9E0FA”, + class_type=“tag” + ) + sa_client.attach_items( + project=project, + attachments=[ + { + “url”: “https://drive.google.com/uc?export=download&id=1ipOrZNSTlPUkI_hnrW9aUD5yULqqq5Vl”, + “name”: “dog.jpeg” + } + ] + ) + sa_client.upload_annotations( + project=project, + annotations=[ + { + “metadata”: {“name”: “dog.jpeg”}, + “instances”: [ + {“type”: “tag”, “className”: “dog”} + ] + } + ] + ) + sa_client.get_annotations(project=project, items=[“dog.jpeg”]) + +Installation +------------ + +SuperAnnotate python SDK is available on PyPI: + +.. code-block:: bash + + pip install superannotate + + +The package officially supports Python 3.7+ and was tested under Linux and +Windows (`Anaconda `_ +) platforms. + +For more detailed installation steps and package usage please have a look at the `tutorial `_ +. +.. _supported features: + +Supported Features +------------------ + +- search/get/create/clone/update/delete projects +- search/get/create/delete folders +- assign folders to project contributors +- upload items to a project from a local or AWS S3 folder +- attach items by URL or from an integrated storage, meanwhile keeping them secure in your cloud provider +- get integrated cloud storages +- upload annotations (also from local or AWS S3 folder) +- delete annotations +- set items annotations statuses +- get/download/export annotations from a project (also to a local or AWS S3 folder) +- invite/search team contributors or add contributors to a specific project +- search/get/copy/move items in a project +- query items using SA Query Language +- define custom metadata for items and upload custom values (query based on your custom metadata) +- upload priority scores +- get available subsets (sets of segregated items), query items in a subset or add items to a subset +- assign or anassign items to project contributors +- download an image that has been uploaded to project +- search/create/download/delete project annotation classes +- search/download models +- run predictions +- convert annotations from/to COCO format +- convert annotation from VOC, SuperVisely, LabelBox, DataLoop, VGG, VoTT, SageMaker, GoogleCloud, YOLO formats +- CLI commands for simple tasks + +Questions and Issues +-------------------- + +For questions and issues please use this repo’s issue tracker on GitHub or contact support@superannotate.com. From d0c11da16a65107481dad4b3f9f6291c23e7cfb6 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Wed, 9 Nov 2022 15:06:37 +0400 Subject: [PATCH 12/27] Update README.rst --- README.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 573d0c75c..d5cb3d120 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,10 @@ -=============================== +.. image:: https://raw.githubusercontent.com/superannotateai/superannotate-python-sdk/master/docs/source/sa_logo.png + :width: 200 + :alt: SuperAnnotate AI + :target: https://app.superannotate.com + +---------- + SuperAnnotate Python SDK =============================== @@ -26,7 +32,7 @@ Authentication --------------- .. code-block:: python - + from superannotate import SAClient # by environment variable SA_TOKEN sa_client = SAClient() @@ -39,8 +45,9 @@ Authentication Using superannotate ------------------- -.. code-block:: python +.. code-block:: python + :linenos: from superannotate import SAClient sa_client =SAClient() project = “Dogs” From 2713193628633e63787f448cae4c00a50d06333d Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Wed, 9 Nov 2022 15:09:38 +0400 Subject: [PATCH 13/27] Update README.rst --- README.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index d5cb3d120..c53f006b0 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ SuperAnnotate Python SDK :target: https://github.com/superannotateai/superannotate-python-sdk/blob/master/CHANGELOG.md :alt: Changelog -Welcome to the SuperAnnotate Python Software Development Kit (SDK), which enables Python programmers to create software that incorporates services of the platform and effortlessly integrates SuperAnnotate into their AI process. (check out the :ref:`supported features` below). +Welcome to the SuperAnnotate Python Software Development Kit (SDK), which enables Python programmers to create software that incorporates services of the platform and effortlessly integrates SuperAnnotate into their AI process. Resources --------------- @@ -99,8 +99,7 @@ Windows (`Anaconda `_ ) platforms. For more detailed installation steps and package usage please have a look at the `tutorial `_ -. -.. _supported features: + Supported Features ------------------ From ef140ab3b6ba2ca4d3bdf9c2aba15a61d5c31f62 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Wed, 9 Nov 2022 15:09:49 +0400 Subject: [PATCH 14/27] Delete README.md --- README.md | 62 ------------------------------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index a548dee90..000000000 --- a/README.md +++ /dev/null @@ -1,62 +0,0 @@ - - -# SuperAnnotate Python SDK - -SuperAnnotate Python SDK allows access to the platform without - web browser: - -```python -import superannotate as sa - -sa.create_project("Example Project 1", "example", "Vector") - -sa.upload_images_from_folder_to_project("Example Project 1", "") -``` - -## Installation - -SDK is available on PyPI: - -```console -pip install superannotate -``` - -The package officially supports Python 3.6+ and was tested under Linux and -Windows ([Anaconda](https://www.anaconda.com/products/individual#windows)) platforms. - -For more detailed installation steps and package usage please have a look at the -[tutorial](https://superannotate.readthedocs.io/en/stable/tutorial.sdk.html). - -## Supported Features - -- Search projects -- Create/delete a project -- Upload images to a project from a local or AWS S3 folder -- Upload videos to a project from a local folder -- Upload annotations/pre-annotations to a project from local or AWS S3 folder -- Set the annotation status of the images being uploaded -- Export annotations from a project to a local or AWS S3 folder -- Share and unshare a project with a team contributor -- Invite a team contributor -- Search images in a project -- Download a single image -- Copy/move image between projects -- Get image bytes (e.g., for numpy array creation) -- Set image annotation status -- Download image annotations/pre-annotations -- Create/download project annotation classes -- Convert annotation format from/to COCO -- Convert annotation format from VOC, SuperVisely, LabelBox, DataLoop, VGG, VoTT, SageMaker, GoogleCloud, YOLO -- Add annotations to images on platform -- Add annotations to local SuperAnnotate format JSONs -- CLI commands for simple tasks - -## Full SDK reference, tutorial available on [Read the Docs](https://superannotate.readthedocs.io) - -## License - -This SDK is distributed under the MIT License, see [LICENSE](./LICENSE). - -## Questions and Issues - -For questions and issues please use this repo's issue tracker on GitHub. From 7e93c44880b2286e8b7f52403a9ad7ad1e5e0549 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Wed, 9 Nov 2022 15:12:40 +0400 Subject: [PATCH 15/27] Updated readme file --- setup.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 7094269e1..c7a003434 100644 --- a/setup.py +++ b/setup.py @@ -17,10 +17,6 @@ def get_version(): with open("requirements.txt") as f: requirements.extend(f.read().splitlines()) -with open('README.md') as f: - readme = f.read() - -readme = "\n".join(readme.split('\n')[2:]) setup( name='superannotate', @@ -32,7 +28,7 @@ def get_version(): license='MIT', author='SuperAnnotate AI', url='https://github.com/superannotateai/superannotate-python-sdk', - long_description=readme, + long_description=open('README.rst').read(), long_description_content_type='text/markdown', install_requires=requirements, setup_requires=['wheel'], From 45a1359d435d17db7bb2535d7903545bef891ca5 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Wed, 9 Nov 2022 16:15:01 +0400 Subject: [PATCH 16/27] Fix tests --- .../lib/core/usecases/annotations.py | 11 ++-- src/superannotate/lib/core/usecases/images.py | 2 +- .../test_annotations_upload_status_change.py | 12 ----- .../test_missing_annotation_upload.py | 20 -------- tests/integration/test_basic_images.py | 12 ----- tests/integration/test_cli.py | 13 ----- .../test_depricated_functions_document.py | 8 --- .../test_depricated_functions_video.py | 8 --- tests/integration/test_limitations.py | 50 ++----------------- 9 files changed, 10 insertions(+), 126 deletions(-) diff --git a/src/superannotate/lib/core/usecases/annotations.py b/src/superannotate/lib/core/usecases/annotations.py index 055242e78..1b46a188f 100644 --- a/src/superannotate/lib/core/usecases/annotations.py +++ b/src/superannotate/lib/core/usecases/annotations.py @@ -580,26 +580,25 @@ def get_existing_name_item_mapping( @property def annotation_upload_data(self) -> UploadAnnotationAuthData: - CHUNK_SIZE=UploadAnnotationsFromFolderUseCase.CHUNK_SIZE_PATHS + CHUNK_SIZE = UploadAnnotationsFromFolderUseCase.CHUNK_SIZE_PATHS if self._annotation_upload_data: return self._annotation_upload_data - images={} + images = {} for i in range(0, len(self._item_ids), CHUNK_SIZE): tmp = self._service_provider.get_annotation_upload_data( project=self._project, folder=self._folder, - item_ids=self._item_ids[i:i+CHUNK_SIZE], - + item_ids=self._item_ids[i : i + CHUNK_SIZE], ) if not tmp.ok: raise AppException(tmp.errors) else: images.update(tmp.data.images) - self._annotation_upload_data=tmp.data - self._annotation_upload_data.images=images + self._annotation_upload_data = tmp.data + self._annotation_upload_data.images = images return self._annotation_upload_data diff --git a/src/superannotate/lib/core/usecases/images.py b/src/superannotate/lib/core/usecases/images.py index 9dadea2a1..ee76fb3c3 100644 --- a/src/superannotate/lib/core/usecases/images.py +++ b/src/superannotate/lib/core/usecases/images.py @@ -1046,7 +1046,7 @@ def filter_paths(self, paths: List[str]): raise AppException(response.error) image_list.extend([image.name for image in response.data]) - image_list=set(image_list) + image_list = set(image_list) images_to_upload = [] for path in filtered_paths: diff --git a/tests/integration/annotations/test_annotations_upload_status_change.py b/tests/integration/annotations/test_annotations_upload_status_change.py index 3be566e4d..0637beab3 100644 --- a/tests/integration/annotations/test_annotations_upload_status_change.py +++ b/tests/integration/annotations/test_annotations_upload_status_change.py @@ -38,18 +38,6 @@ def test_upload_annotations_from_folder_to_project__upload_status(self, reporter sa.get_item_metadata(self.PROJECT_NAME, self.IMAGE_NAME)["annotation_status"] ) - @pytest.mark.flaky(reruns=2) - @patch("lib.infrastructure.controller.Reporter") - def test_upload_preannotations_from_folder_to_project__upload_status(self, reporter): - reporter_mock = MagicMock() - reporter.return_value = reporter_mock - sa.upload_image_to_project(self.PROJECT_NAME, join(self.folder_path, self.IMAGE_NAME)) - sa.upload_preannotations_from_folder_to_project(self.PROJECT_NAME, self.folder_path) - self.assertEqual( - constances.AnnotationStatus.IN_PROGRESS.name, - sa.get_item_metadata(self.PROJECT_NAME, self.IMAGE_NAME)["annotation_status"] - ) - @pytest.mark.flaky(reruns=2) @patch("lib.infrastructure.controller.Reporter") def test_upload_image_annotations__upload_status(self, reporter): diff --git a/tests/integration/annotations/test_missing_annotation_upload.py b/tests/integration/annotations/test_missing_annotation_upload.py index 098282f86..51ff0d9d9 100644 --- a/tests/integration/annotations/test_missing_annotation_upload.py +++ b/tests/integration/annotations/test_missing_annotation_upload.py @@ -45,23 +45,3 @@ def test_missing_annotation_upload(self): ] ) ) - - def test_missing_pre_annotation_upload(self): - - sa.upload_images_from_folder_to_project( - self.PROJECT_NAME, self.folder_path, annotation_status="NotStarted" - ) - sa.create_annotation_classes_from_classes_json( - self.PROJECT_NAME, f"{self.folder_path}/classes/classes.json" - ) - ( - uploaded, - could_not_upload, - missing_images, - ) = sa.upload_preannotations_from_folder_to_project( - self.PROJECT_NAME, self.folder_path - ) - self.assertEqual(len(uploaded), 3) - self.assertEqual(len(could_not_upload), 0) - self.assertEqual(len(missing_images), 1) - diff --git a/tests/integration/test_basic_images.py b/tests/integration/test_basic_images.py index b6177c43a..25f835449 100644 --- a/tests/integration/test_basic_images.py +++ b/tests/integration/test_basic_images.py @@ -94,18 +94,6 @@ def folder_path(self): def classes_json_path(self): return f"{self.folder_path}/classes/classes.json" - def test_vector_annotations_with_tag_folder_upload_preannotation(self): - sa.upload_images_from_folder_to_project( - self.PROJECT_NAME, self.folder_path, annotation_status="InProgress" - ) - sa.create_annotation_classes_from_classes_json( - self.PROJECT_NAME, self.classes_json_path - ) - uploaded_annotations, _, _ = sa.upload_preannotations_from_folder_to_project( - self.PROJECT_NAME, self.folder_path - ) - self.assertEqual(len(uploaded_annotations), 1) - class TestPixelImages(BaseTestCase): PROJECT_NAME = "sample_project_pixel" diff --git a/tests/integration/test_cli.py b/tests/integration/test_cli.py index cd333ee31..f43f27000 100644 --- a/tests/integration/test_cli.py +++ b/tests/integration/test_cli.py @@ -127,19 +127,6 @@ def test_upload_export(self): self.assertEqual(len(list(test_dir.glob("*.jpg"))), 0) self.assertEqual(len(list(test_dir.glob("*.png"))), 0) - def test_vector_pre_annotation_folder_upload_download_cli(self): - self._create_project() - - sa.create_annotation_classes_from_classes_json( - self.PROJECT_NAME, f"{self.vector_folder_path}/classes/classes.json" - ) - self.safe_run(self._cli.upload_images, project=self.PROJECT_NAME, folder=str(self.convertor_data_path), - extensions="jpg", - set_annotation_status="QualityCheck") - self.safe_run(self._cli.upload_preannotations, project=self.PROJECT_NAME, folder=str(self.convertor_data_path), - format="COCO", - dataset_name="instances_test") - def test_vector_annotation_folder_upload_download_cli(self): self._create_project() sa.create_annotation_classes_from_classes_json( diff --git a/tests/integration/test_depricated_functions_document.py b/tests/integration/test_depricated_functions_document.py index f021e67fc..0663322c9 100644 --- a/tests/integration/test_depricated_functions_document.py +++ b/tests/integration/test_depricated_functions_document.py @@ -83,10 +83,6 @@ def test_deprecated_functions(self): sa.download_image_annotations(self.PROJECT_NAME, self.UPLOAD_IMAGE_NAME, "./") except AppException as e: self.assertIn(self.EXCEPTION_MESSAGE, str(e)) - try: - sa.copy_image(self.PROJECT_NAME, self.UPLOAD_IMAGE_NAME, self.PROJECT_NAME_2) - except AppException as e: - self.assertIn(self.EXCEPTION_MESSAGE, str(e)) try: sa.upload_images_to_project(self.PROJECT_NAME, [self.image_path, ]) except AppException as e: @@ -103,10 +99,6 @@ def test_deprecated_functions(self): sa.set_project_workflow(self.PROJECT_NAME, [{}]) except AppException as e: self.assertIn(self.EXCEPTION_MESSAGE, str(e)) - try: - sa.upload_preannotations_from_folder_to_project(self.PROJECT_NAME, self.folder_path) - except AppException as e: - self.assertIn(self.EXCEPTION_MESSAGE, str(e)) try: sa.add_annotation_point_to_image(self.PROJECT_NAME, self.UPLOAD_IMAGE_NAME, [1, 2], "some class") except AppException as e: diff --git a/tests/integration/test_depricated_functions_video.py b/tests/integration/test_depricated_functions_video.py index 3ad847abb..1160c6472 100644 --- a/tests/integration/test_depricated_functions_video.py +++ b/tests/integration/test_depricated_functions_video.py @@ -76,10 +76,6 @@ def test_deprecated_functions(self): sa.download_image_annotations(self.PROJECT_NAME, self.UPLOAD_IMAGE_NAME, "./") except AppException as e: self.assertIn(self.EXCEPTION_MESSAGE, str(e)) - try: - sa.copy_image(self.PROJECT_NAME, self.UPLOAD_IMAGE_NAME, self.PROJECT_NAME_2) - except AppException as e: - self.assertIn(self.EXCEPTION_MESSAGE, str(e)) try: sa.upload_images_to_project(self.PROJECT_NAME, [self.image_path, ]) except AppException as e: @@ -96,10 +92,6 @@ def test_deprecated_functions(self): sa.set_project_workflow(self.PROJECT_NAME, [{}]) except AppException as e: self.assertIn(self.EXCEPTION_MESSAGE, str(e)) - try: - sa.upload_preannotations_from_folder_to_project(self.PROJECT_NAME, self.folder_path) - except AppException as e: - self.assertIn(self.EXCEPTION_MESSAGE, str(e)) try: sa.get_project_workflow(self.PROJECT_NAME) except AppException as e: diff --git a/tests/integration/test_limitations.py b/tests/integration/test_limitations.py index cac030348..a45d575c1 100644 --- a/tests/integration/test_limitations.py +++ b/tests/integration/test_limitations.py @@ -1,21 +1,19 @@ import os -from unittest.mock import patch from os.path import dirname +from unittest.mock import patch -from src.superannotate import SAClient -sa = SAClient() from src.superannotate import AppException +from src.superannotate import SAClient from src.superannotate.lib.core import UPLOAD_FOLDER_LIMIT_ERROR_MESSAGE from src.superannotate.lib.core import UPLOAD_PROJECT_LIMIT_ERROR_MESSAGE from src.superannotate.lib.core import UPLOAD_USER_LIMIT_ERROR_MESSAGE -from src.superannotate.lib.core import COPY_FOLDER_LIMIT_ERROR_MESSAGE -from src.superannotate.lib.core import COPY_PROJECT_LIMIT_ERROR_MESSAGE -from src.superannotate.lib.core import COPY_SUPER_LIMIT_ERROR_MESSAGE from tests.integration.base import BaseTestCase from tests.moks.limitatoins import folder_limit_response from tests.moks.limitatoins import project_limit_response from tests.moks.limitatoins import user_limit_response +sa = SAClient() + class TestLimitsUploadImagesFromFolderToProject(BaseTestCase): PROJECT_NAME = "TestLimitsUploadImagesFromFolderToProject" @@ -49,43 +47,3 @@ def test_user_limitations(self, *_): _, _, __ = sa.upload_images_from_folder_to_project( project=self._project["name"], folder_path=self.folder_path ) - - -class TestLimitsCopyImage(BaseTestCase): - PROJECT_NAME = "TestLimitsCopyImage" - PROJECT_DESCRIPTION = "Desc" - PROJECT_TYPE = "Vector" - TEST_FOLDER_PTH = "data_set" - TEST_FOLDER_PATH = "data_set/sample_project_vector" - EXAMPLE_IMAGE_1 = "example_image_1.jpg" - - @property - def folder_path(self): - return os.path.join(dirname(dirname(__file__)), self.TEST_FOLDER_PATH) - - def test_folder_limitations(self): - sa.upload_image_to_project(self._project["name"], os.path.join(self.folder_path, self.EXAMPLE_IMAGE_1)) - sa.create_folder(self._project["name"], self._project["name"]) - with patch("lib.infrastructure.serviceprovider.ServiceProvider.get_limitations") as limit_response: - limit_response.return_value = folder_limit_response - with self.assertRaisesRegexp(AppException, COPY_FOLDER_LIMIT_ERROR_MESSAGE): - _, _, __ = sa.copy_image( - self._project["name"], self.folder_path, f"{self.PROJECT_NAME}/{self.PROJECT_NAME}") - - def test_project_limitations(self, ): - sa.upload_image_to_project(self._project["name"], os.path.join(self.folder_path, self.EXAMPLE_IMAGE_1)) - sa.create_folder(self._project["name"], self._project["name"]) - with patch("lib.infrastructure.serviceprovider.ServiceProvider.get_limitations") as limit_response: - limit_response.return_value = project_limit_response - with self.assertRaisesRegexp(AppException, COPY_PROJECT_LIMIT_ERROR_MESSAGE): - _, _, __ = sa.copy_image( - self._project["name"], self.folder_path, f"{self.PROJECT_NAME}/{self.PROJECT_NAME}") - - def test_user_limitations(self, ): - sa.upload_image_to_project(self._project["name"], os.path.join(self.folder_path, self.EXAMPLE_IMAGE_1)) - sa.create_folder(self._project["name"], self._project["name"]) - with patch("lib.infrastructure.serviceprovider.ServiceProvider.get_limitations") as limit_response: - limit_response.return_value = user_limit_response - with self.assertRaisesRegexp(AppException, COPY_SUPER_LIMIT_ERROR_MESSAGE): - _, _, __ = sa.copy_image( - self._project["name"], self.folder_path, f"{self.PROJECT_NAME}/{self.PROJECT_NAME}") From ea19f4759a43ced19ec7f17a5abb16ac0998cee5 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Thu, 10 Nov 2022 10:22:34 +0400 Subject: [PATCH 17/27] Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c53f006b0..570e6fc5b 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ Authentication sa_client = SAClient(token=’’) # by config file # default path is ~/.superannotate/config.json - sa_client = SAClient(config_path=“~/.superannotate/dev_config.json”) + sa_client = SAClient(config_path='~/.superannotate/dev_config.json') Using superannotate ------------------- From 6cb61b4939138481ec56815006560d490889efb7 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Thu, 10 Nov 2022 10:23:12 +0400 Subject: [PATCH 18/27] Update __init__.py --- src/superannotate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index 18c456941..a631cdcfd 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -2,7 +2,7 @@ import sys -__version__ = "4.4.6dev1" +__version__ = "4.4.6dev2" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From 4201f50d9e562e3383dfdddb26eb6488d7abe142 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 10 Nov 2022 11:16:55 +0400 Subject: [PATCH 19/27] README fix --- README.rst | 32 +++++++++---------- ...load_annotations_from_folder_to_project.py | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index c53f006b0..a0f1c3b26 100644 --- a/README.rst +++ b/README.rst @@ -27,8 +27,9 @@ Resources - API Reference and User Guide available on `Read the Docs `_ - `Platform documentation `_ -Authentication + +Authentication --------------- .. code-block:: python @@ -37,37 +38,36 @@ Authentication # by environment variable SA_TOKEN sa_client = SAClient() # by token - sa_client = SAClient(token=’’) + sa_client = SAClient(token='') # by config file # default path is ~/.superannotate/config.json - sa_client = SAClient(config_path=“~/.superannotate/dev_config.json”) + sa_client = SAClient(config_path='~/.superannotate/dev_config.json') Using superannotate ------------------- .. code-block:: python - :linenos: from superannotate import SAClient sa_client =SAClient() - project = “Dogs” + project = 'Dogs' sa_client.create_project( project_name=project, - project_description=“Test project generated via SDK”, - project_type=“Vector” + project_description='Test project generated via SDK', + project_type='Vector' ) sa_client.create_annotation_class( project=project, - name=“dog”, - color=“#F9E0FA”, - class_type=“tag” + name='dog', + color='#F9E0FA', + class_type='tag' ) sa_client.attach_items( project=project, attachments=[ { - “url”: “https://drive.google.com/uc?export=download&id=1ipOrZNSTlPUkI_hnrW9aUD5yULqqq5Vl”, - “name”: “dog.jpeg” + 'url': 'https://drive.google.com/uc?export=download&id=1ipOrZNSTlPUkI_hnrW9aUD5yULqqq5Vl', + 'name': 'dog.jpeg' } ] ) @@ -75,14 +75,14 @@ Using superannotate project=project, annotations=[ { - “metadata”: {“name”: “dog.jpeg”}, - “instances”: [ - {“type”: “tag”, “className”: “dog”} + 'metadata': {'name': 'dog.jpeg'}, + 'instances': [ + {'type': 'tag', 'className': 'dog'} ] } ] ) - sa_client.get_annotations(project=project, items=[“dog.jpeg”]) + sa_client.get_annotations(project=project, items=['dog.jpeg']) Installation ------------ diff --git a/tests/integration/annotations/test_upload_annotations_from_folder_to_project.py b/tests/integration/annotations/test_upload_annotations_from_folder_to_project.py index 252d46804..59c99d566 100644 --- a/tests/integration/annotations/test_upload_annotations_from_folder_to_project.py +++ b/tests/integration/annotations/test_upload_annotations_from_folder_to_project.py @@ -95,7 +95,7 @@ def test_upload_large_annotations(self): def test_upload_big_annotations(self): sa.attach_items( self.PROJECT_NAME, - [{"name": f"aearth_mov_00{i}.jpg", "url": f"url_{i}"} for i in range(1, 6)] # noqa + [{"name": f" rst-lintaearth_mov_00{i}.jpg", "url": f"url_{i}"} for i in range(1, 6)] # noqa ) sa.create_annotation_classes_from_classes_json( self.PROJECT_NAME, f"{self.big_annotations_folder_path}/classes/classes.json" From 69878acdc1db499698c8bd91d603d30fd39184e6 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 10 Nov 2022 11:21:03 +0400 Subject: [PATCH 20/27] Readme fix --- setup.py | 2 +- src/superannotate/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c7a003434..011f86b56 100644 --- a/setup.py +++ b/setup.py @@ -27,12 +27,12 @@ def get_version(): description='Python SDK to SuperAnnotate platform', license='MIT', author='SuperAnnotate AI', + author_email='suppoort@superannotate.com', url='https://github.com/superannotateai/superannotate-python-sdk', long_description=open('README.rst').read(), long_description_content_type='text/markdown', install_requires=requirements, setup_requires=['wheel'], - description_file="README.md", entry_points={ 'console_scripts': ['superannotatecli = superannotate.lib.app.bin.superannotate:main'] }, diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index a631cdcfd..0dcd71c1d 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -2,7 +2,7 @@ import sys -__version__ = "4.4.6dev2" +__version__ = "4.4.6dev3" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From a32853a687aeb64ced2d9ea7002b5b7c7f2f9786 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 10 Nov 2022 11:36:10 +0400 Subject: [PATCH 21/27] Readme fix --- README.rst | 110 ++++++++++++++++------------------ src/superannotate/__init__.py | 2 +- 2 files changed, 53 insertions(+), 59 deletions(-) diff --git a/README.rst b/README.rst index a0f1c3b26..e539f5da7 100644 --- a/README.rst +++ b/README.rst @@ -1,15 +1,11 @@ -.. image:: https://raw.githubusercontent.com/superannotateai/superannotate-python-sdk/master/docs/source/sa_logo.png - :width: 200 - :alt: SuperAnnotate AI - :target: https://app.superannotate.com - ----------- - +=============================== SuperAnnotate Python SDK =============================== |Python| |License| |Changelog| +Welcome to the SuperAnnotate Python Software Development Kit (SDK), which enables Python programmers to create software that incorporates services of the platform and effortlessly integrates SuperAnnotate into their AI process. + .. |Python| image:: https://img.shields.io/static/v1?label=python&message=3.7/3.8/3.9/3.10/3.11&color=blue&style=flat-square :target: https://pypi.org/project/superannotate/ :alt: Python Versions @@ -20,13 +16,11 @@ SuperAnnotate Python SDK :target: https://github.com/superannotateai/superannotate-python-sdk/blob/master/CHANGELOG.md :alt: Changelog -Welcome to the SuperAnnotate Python Software Development Kit (SDK), which enables Python programmers to create software that incorporates services of the platform and effortlessly integrates SuperAnnotate into their AI process. - Resources --------------- -- API Reference and User Guide available on `Read the Docs `_ -- `Platform documentation `_ +- API Reference and User Guide available on `Read the Docs `__ +- `Platform documentation `__ Authentication @@ -34,55 +28,55 @@ Authentication .. code-block:: python - from superannotate import SAClient - # by environment variable SA_TOKEN - sa_client = SAClient() - # by token - sa_client = SAClient(token='') - # by config file - # default path is ~/.superannotate/config.json - sa_client = SAClient(config_path='~/.superannotate/dev_config.json') + >>> from superannotate import SAClient + # by environment variable SA_TOKEN + >>> sa_client = SAClient() + # by token + >>> sa_client = SAClient(token='') + # by config file + # default path is ~/.superannotate/config.json + >>> sa_client = SAClient(config_path='~/.superannotate/dev_config.json') Using superannotate ------------------- -.. code-block:: python - - from superannotate import SAClient - sa_client =SAClient() - project = 'Dogs' - sa_client.create_project( - project_name=project, - project_description='Test project generated via SDK', - project_type='Vector' - ) - sa_client.create_annotation_class( - project=project, - name='dog', - color='#F9E0FA', - class_type='tag' - ) - sa_client.attach_items( - project=project, - attachments=[ - { - 'url': 'https://drive.google.com/uc?export=download&id=1ipOrZNSTlPUkI_hnrW9aUD5yULqqq5Vl', - 'name': 'dog.jpeg' - } - ] - ) - sa_client.upload_annotations( - project=project, - annotations=[ - { - 'metadata': {'name': 'dog.jpeg'}, - 'instances': [ - {'type': 'tag', 'className': 'dog'} - ] - } - ] - ) - sa_client.get_annotations(project=project, items=['dog.jpeg']) +.. code-block:: python + + >>> from superannotate import SAClient + >>> sa_client =SAClient() + >>> project = 'Dogs' + >>> sa_client.create_project( + project_name=project, + project_description='Test project generated via SDK', + project_type='Vector' + ) + >>> sa_client.create_annotation_class( + project=project, + name='dog', + color='#F9E0FA', + class_type='tag' + ) + >>> sa_client.attach_items( + project=project, + attachments=[ + { + 'url': 'https://drive.google.com/uc?export=download&id=1ipOrZNSTlPUkI_hnrW9aUD5yULqqq5Vl', + 'name': 'dog.jpeg' + } + ] + ) + >>> sa_client.upload_annotations( + project=project, + annotations=[ + { + 'metadata': {'name': 'dog.jpeg'}, + 'instances': [ + {'type': 'tag', 'className': 'dog'} + ] + } + ] + ) + >>> sa_client.get_annotations(project=project, items=['dog.jpeg']) Installation ------------ @@ -95,10 +89,10 @@ SuperAnnotate python SDK is available on PyPI: The package officially supports Python 3.7+ and was tested under Linux and -Windows (`Anaconda `_ +Windows (`Anaconda `__ ) platforms. -For more detailed installation steps and package usage please have a look at the `tutorial `_ +For more detailed installation steps and package usage please have a look at the `tutorial `__ Supported Features diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index 0dcd71c1d..994ec5292 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -2,7 +2,7 @@ import sys -__version__ = "4.4.6dev3" +__version__ = "4.4.6dev4" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From 5dab0575dba39a2c8cf03ddaa1928c9c8d1a2fea Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 10 Nov 2022 15:48:25 +0400 Subject: [PATCH 22/27] aggregastions change --- README.rst | 3 +-- src/superannotate/lib/app/analytics/aggregators.py | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index e539f5da7..d95540dba 100644 --- a/README.rst +++ b/README.rst @@ -1,9 +1,8 @@ -=============================== SuperAnnotate Python SDK =============================== - |Python| |License| |Changelog| + Welcome to the SuperAnnotate Python Software Development Kit (SDK), which enables Python programmers to create software that incorporates services of the platform and effortlessly integrates SuperAnnotate into their AI process. .. |Python| image:: https://img.shields.io/static/v1?label=python&message=3.7/3.8/3.9/3.10/3.11&color=blue&style=flat-square diff --git a/src/superannotate/lib/app/analytics/aggregators.py b/src/superannotate/lib/app/analytics/aggregators.py index 4e241bf97..ec9ccdad5 100644 --- a/src/superannotate/lib/app/analytics/aggregators.py +++ b/src/superannotate/lib/app/analytics/aggregators.py @@ -91,6 +91,7 @@ class DataAggregator: "polygon": lambda annotation: annotation["points"], "polyline": lambda annotation: annotation["points"], "cuboid": lambda annotation: annotation["points"], + "comment": lambda annotation: annotation["points"], "point": lambda annotation: {"x": annotation["x"], "y": annotation["y"]}, "annotation_type": lambda annotation: dict( cx=annotation["cx"], @@ -123,7 +124,7 @@ def annotation_suffix(self): self._annotation_suffix = PIXEL_ANNOTATION_POSTFIX else: self._annotation_suffix = ATTACHED_VIDEO_ANNOTATION_POSTFIX - return self._annotation_suffix + return ATTACHED_VIDEO_ANNOTATION_POSTFIX def get_annotation_paths(self): annotations_paths = [] @@ -378,7 +379,7 @@ def __append_annotation(annotation_dict): for annotation_path in annotations_paths: annotation_json = json.load(open(annotation_path)) - parts = annotation_path.name.split(self.annotation_suffix) + parts = Path(annotation_path).name.split(self.annotation_suffix) if len(parts) != 2: continue image_name = parts[0] @@ -449,8 +450,8 @@ def __append_annotation(annotation_dict): attributes = annotation.get("attributes") user_metadata = self.__get_user_metadata(annotation) folder_name = None - if annotation_path.parent != Path(self.project_root): - folder_name = annotation_path.parent.name + if Path(annotation_path).parent != Path(self.project_root): + folder_name = Path(annotation_path).parent.name num_added = 0 if not attributes: annotation_dict = { From 12355d1574a618ce8162ff006aacf2726ee5af36 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 10 Nov 2022 17:29:35 +0400 Subject: [PATCH 23/27] updated per_frame convertor --- src/superannotate/lib/core/video_convertor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/superannotate/lib/core/video_convertor.py b/src/superannotate/lib/core/video_convertor.py index ca179a2a3..758478f7e 100644 --- a/src/superannotate/lib/core/video_convertor.py +++ b/src/superannotate/lib/core/video_convertor.py @@ -178,6 +178,8 @@ def _process(self): for instance in self._annotation_data["instances"]: instance_id = next(self.id_generator) annotation_type = instance["meta"]["type"] + if annotation_type == "comment": + continue class_name = instance["meta"].get("className") class_id = instance["meta"].get("classId", -1) for parameter in instance.get("parameters", []): From 0738846076ccd196bdc3562e60a6d73bceffaf97 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Fri, 11 Nov 2022 10:10:30 +0400 Subject: [PATCH 24/27] Fix setup.py --- setup.py | 2 +- src/superannotate/lib/core/video_convertor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 011f86b56..f7da21cc2 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ def get_version(): author_email='suppoort@superannotate.com', url='https://github.com/superannotateai/superannotate-python-sdk', long_description=open('README.rst').read(), - long_description_content_type='text/markdown', + long_description_content_type='text/x-rst', install_requires=requirements, setup_requires=['wheel'], entry_points={ diff --git a/src/superannotate/lib/core/video_convertor.py b/src/superannotate/lib/core/video_convertor.py index 758478f7e..f951df256 100644 --- a/src/superannotate/lib/core/video_convertor.py +++ b/src/superannotate/lib/core/video_convertor.py @@ -187,7 +187,7 @@ def _process(self): interpolated_frames = {} for timestamp in parameter["timestamps"]: frames_mapping[ - int(math.ceil(timestamp["timestamp"] / self.ratio)) + int(math.ceil(timesstamp["timestamp"] / self.ratio)) ].append(timestamp) frames_mapping = self.merge_first_frame(frames_mapping) From 3335ab05084ce5effd3dde1411c284b8ef0c03e5 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Fri, 11 Nov 2022 10:11:56 +0400 Subject: [PATCH 25/27] Update __init__.py --- src/superannotate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index 994ec5292..05328a426 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -2,7 +2,7 @@ import sys -__version__ = "4.4.6dev4" +__version__ = "4.4.6b1" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From 10e0e550360a129b6920393def49c676b1295044 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Fri, 11 Nov 2022 11:44:33 +0400 Subject: [PATCH 26/27] 4.4.6b2 --- src/superannotate/__init__.py | 2 +- src/superannotate/lib/core/video_convertor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index 05328a426..e441f04f4 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -2,7 +2,7 @@ import sys -__version__ = "4.4.6b1" +__version__ = "4.4.6b2" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) diff --git a/src/superannotate/lib/core/video_convertor.py b/src/superannotate/lib/core/video_convertor.py index f951df256..758478f7e 100644 --- a/src/superannotate/lib/core/video_convertor.py +++ b/src/superannotate/lib/core/video_convertor.py @@ -187,7 +187,7 @@ def _process(self): interpolated_frames = {} for timestamp in parameter["timestamps"]: frames_mapping[ - int(math.ceil(timesstamp["timestamp"] / self.ratio)) + int(math.ceil(timestamp["timestamp"] / self.ratio)) ].append(timestamp) frames_mapping = self.merge_first_frame(frames_mapping) From 43c6bc67fd12a18e264af44488096623f5b61dbb Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Sun, 13 Nov 2022 20:38:00 +0400 Subject: [PATCH 27/27] Changelog update --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25103e5a1..72901cf20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog All release highlights of this project will be documented in this file. + +## 4.4.6 - November 23, 2022 +### Updated +- `SAClient.aggregate_annotations_as_df` method to aggregate "comment" type instances. +- `SAClient.add_annotation_bbox_to_image`, `SAClient.add_annotation_point_to_image`, `SAClient.add_annotation_comment_to_image` _methods_ to add deprecation warnings. +### Fixed +- Special characters are being encoded after annotation upload (Windows) +- `SAClient.assign_folder` _method_ to address the invalid argument name. +- `SAClient.upload_images_from_folder_to_project` _method_ to address uploading of more than 500 items. +- `SAClient.upload_annotations_from_folder_to_project` _method_ to address the issue of a folder size being more than 25,5 MB. +- `SAClient.download_image` _method_ to address the KeyError 'id' when `include_annotations` is set to `True`. +### Removed +`SAClient.upload_preannotations_from_folder_to_project` _method_ +`SAClient.copy_image` _method_ +### ## 4.4.5 - October 23, 2022 ### Added - `SAClient.add_items_to_subset` _method_ to associate given items with a Subset.