From 2709cc412bb82427f263e6364e51b456284f057d Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 24 Nov 2022 14:11:56 +0400 Subject: [PATCH 01/23] Deleted deprecated functions --- docs/source/superannotate.sdk.rst | 5 +- .../lib/app/annotation_helpers.py | 198 ----- .../lib/app/interface/sdk_interface.py | 220 +---- src/superannotate/lib/app/interface/types.py | 13 + src/superannotate/lib/app/serializers.py | 15 + src/superannotate/lib/core/entities/folder.py | 2 + src/superannotate/lib/core/enums.py | 8 + .../lib/core/usecases/annotations.py | 2 + .../classes/classes.json | 308 +++++++ .../sample_video.mp4.json | 837 ++++++++++++++++++ .../annotations/test_annotation_adding.py | 211 ----- .../test_annotations_upload_status_change.py | 86 -- .../integration/annotations/video/__init__.py | 0 .../test_get_annotations_per_frame.py | 0 .../test_video_annotation_upload.py | 0 .../video/test_video_free_text_numeric.py | 32 + tests/integration/folders/test_folders.py | 5 +- tests/integration/items/test_copy_items.py | 19 - tests/integration/items/test_move_items.py | 18 - .../test_depricated_functions_document.py | 4 - tests/integration/test_fuse_gen.py | 53 -- 21 files changed, 1237 insertions(+), 799 deletions(-) delete mode 100644 src/superannotate/lib/app/annotation_helpers.py create mode 100644 tests/data_set/sample_video_num_free_text/classes/classes.json create mode 100644 tests/data_set/sample_video_num_free_text/sample_video.mp4.json delete mode 100644 tests/integration/annotations/test_annotation_adding.py delete mode 100644 tests/integration/annotations/test_annotations_upload_status_change.py create mode 100644 tests/integration/annotations/video/__init__.py rename tests/integration/annotations/{ => video}/test_get_annotations_per_frame.py (100%) rename tests/integration/annotations/{ => video}/test_video_annotation_upload.py (100%) create mode 100644 tests/integration/annotations/video/test_video_free_text_numeric.py diff --git a/docs/source/superannotate.sdk.rst b/docs/source/superannotate.sdk.rst index 53b626f4d..6a2fca982 100644 --- a/docs/source/superannotate.sdk.rst +++ b/docs/source/superannotate.sdk.rst @@ -28,6 +28,8 @@ ________ .. automethod:: superannotate.SAClient.get_project_metadata .. automethod:: superannotate.SAClient.get_project_image_count .. automethod:: superannotate.SAClient.search_folders +.. automethod:: superannotate.SAClient.assign_folder +.. automethod:: superannotate.SAClient.unassign_folder .. automethod:: superannotate.SAClient.get_folder_metadata .. automethod:: superannotate.SAClient.create_folder .. automethod:: superannotate.SAClient.delete_folders @@ -107,9 +109,6 @@ ______ .. automethod:: superannotate.SAClient.download_image_annotations .. automethod:: superannotate.SAClient.upload_image_annotations .. automethod:: superannotate.SAClient.pin_image -.. automethod:: superannotate.SAClient.add_annotation_bbox_to_image -.. automethod:: superannotate.SAClient.add_annotation_point_to_image -.. automethod:: superannotate.SAClient.add_annotation_comment_to_image .. automethod:: superannotate.SAClient.upload_priority_scores ---------- diff --git a/src/superannotate/lib/app/annotation_helpers.py b/src/superannotate/lib/app/annotation_helpers.py deleted file mode 100644 index 97fdb8dad..000000000 --- a/src/superannotate/lib/app/annotation_helpers.py +++ /dev/null @@ -1,198 +0,0 @@ -"""SuperAnnotate format annotation JSON helpers""" -import datetime -import json - -from superannotate.lib.app.exceptions import AppException - - -def fill_in_missing(annotation_json, image_name: str = ""): - for field in ["instances", "comments", "tags"]: - if field not in annotation_json: - annotation_json[field] = [] - if "metadata" not in annotation_json: - annotation_json["metadata"] = {"name": image_name} - - -def _preprocess_annotation_json(annotation_json, image_name: str = ""): - path = None - if not isinstance(annotation_json, dict) and annotation_json is not None: - path = annotation_json - annotation_json = json.load(open(annotation_json)) - elif annotation_json is None: - annotation_json = {} - - fill_in_missing(annotation_json, image_name) - - return (annotation_json, path) - - -def _postprocess_annotation_json(annotation_json, path): - if path is not None: - json.dump(annotation_json, open(path, "w")) - return None - else: - return annotation_json - - -def _add_created_updated(annotation): - created_at = ( - datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[ - :-3 - ] - + "Z" - ) - annotation["createdAt"] = created_at - annotation["updatedAt"] = created_at - return annotation - - -def add_annotation_comment_to_json( - annotation_json, - comment_text, - comment_coords, - comment_author, - resolved=False, - image_name="", -): - """Add a comment to SuperAnnotate format annotation JSON - - :param annotation_json: annotations in SuperAnnotate format JSON or filepath to JSON - :type annotation_json: dict or Pathlike (str or Path) - - :param comment_text: comment text - :type comment_text: str - - :param comment_coords: [x, y] coords - :type comment_coords: list - - :param comment_author: comment author email - :type comment_author: str - - :param resolved: comment resolve status - :type resolved: bool - """ - - if len(comment_coords) != 2: - raise AppException("Comment should have two values") - - annotation_json, path = _preprocess_annotation_json( - annotation_json, image_name=image_name - ) - - user_action = {"email": comment_author, "role": "Admin"} - - annotation = { - "x": comment_coords[0], - "y": comment_coords[1], - "correspondence": [{"text": comment_text, "email": comment_author}], - "resolved": resolved, - "creationType": "Preannotation", # noqa - "createdBy": user_action, - "updatedBy": user_action, - } - annotation = _add_created_updated(annotation) - annotation_json["comments"].append(annotation) - - return _postprocess_annotation_json(annotation_json, path) - - -def add_annotation_bbox_to_json( - annotation_json, - bbox, - annotation_class_name, - annotation_class_attributes=None, - error=None, - image_name: str = "", -): - """Add a bounding box annotation to SuperAnnotate format annotation JSON - - annotation_class_attributes has the form [ {"name" : "", "groupName" : ""}, ... ] - - :param annotation_json: annotations in SuperAnnotate format JSON or filepath to JSON - :type annotation_json: dict or Pathlike (str or Path) - - :param bbox: 4 element list of top-left x,y and bottom-right x, y coordinates - :type bbox: list of floats - - :param annotation_class_name: annotation class name - :type annotation_class_name: str - - :param annotation_class_attributes: list of annotation class attributes - :type annotation_class_attributes: list of 2 element dicts - - :param error: if not None, marks annotation as error (True) or no-error (False) - :type error: bool - """ - - if len(bbox) != 4: - raise AppException("Bounding boxes should have 4 float elements") - - annotation_json, path = _preprocess_annotation_json(annotation_json, image_name) - - annotation = { - "type": "bbox", - "points": {"x1": bbox[0], "y1": bbox[1], "x2": bbox[2], "y2": bbox[3]}, - "className": annotation_class_name, - "groupId": 0, - "pointLabels": {}, - "locked": False, - "visible": True, - "attributes": [] - if annotation_class_attributes is None - else annotation_class_attributes, - } - if error is not None: - annotation["error"] = error - annotation = _add_created_updated(annotation) - annotation_json["instances"].append(annotation) - - return _postprocess_annotation_json(annotation_json, path) - - -def add_annotation_point_to_json( - annotation_json, - point, - annotation_class_name, - image_name, - annotation_class_attributes=None, - error=None, -): - """Add a point annotation to SuperAnnotate format annotation JSON - - annotation_class_attributes has the form [ {"name" : "", "groupName" : ""}, ... ] - - :param annotation_json: annotations in SuperAnnotate format JSON or filepath to JSON - :type annotation_json: dict or Pathlike (str or Path) - :param point: [x,y] list of coordinates - :type point: list of floats - :param annotation_class_name: annotation class name - :type annotation_class_name: str - :param annotation_class_attributes: list of annotation class attributes - :type annotation_class_attributes: list of 2 element dicts - :param error: if not None, marks annotation as error (True) or no-error (False) - :type error: bool - """ - if len(point) != 2: - raise AppException("Point should be 2 element float list.") - - annotation_json, path = _preprocess_annotation_json(annotation_json, image_name) - annotation = { - "type": "point", - "x": point[0], - "y": point[1], - "className": annotation_class_name, - "groupId": 0, - "pointLabels": {}, - "locked": False, - "visible": True, - "attributes": [] - if annotation_class_attributes is None - else annotation_class_attributes, - } - if error is not None: - annotation["error"] = error - - annotation = _add_created_updated(annotation) - annotation_json["instances"].append(annotation) - - return _postprocess_annotation_json(annotation_json, path) diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index 57b9f2dd5..3762cb21c 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -4,7 +4,6 @@ import json import os import tempfile -import warnings from pathlib import Path from typing import Callable from typing import Dict @@ -16,9 +15,6 @@ import boto3 import lib.core as constants -from lib.app.annotation_helpers import add_annotation_bbox_to_json -from lib.app.annotation_helpers import add_annotation_comment_to_json -from lib.app.annotation_helpers import add_annotation_point_to_json from lib.app.helpers import get_annotation_paths from lib.app.helpers import get_name_url_duplicated_from_csv from lib.app.helpers import wrap_error as wrap_validation_errors @@ -31,12 +27,14 @@ from lib.app.interface.types import AttachmentDict from lib.app.interface.types import ClassType from lib.app.interface.types import EmailStr +from lib.app.interface.types import FolderStatusEnum from lib.app.interface.types import ImageQualityChoices from lib.app.interface.types import NotEmptyStr from lib.app.interface.types import ProjectStatusEnum from lib.app.interface.types import ProjectTypes from lib.app.interface.types import Setting from lib.app.serializers import BaseSerializer +from lib.app.serializers import FolderSerializer from lib.app.serializers import ProjectSerializer from lib.app.serializers import SettingsSerializer from lib.app.serializers import TeamSerializer @@ -403,14 +401,20 @@ def search_folders( self, project: NotEmptyStr, folder_name: Optional[NotEmptyStr] = None, + status: Optional[Union[FolderStatusEnum, List[FolderStatusEnum]]] = None, return_metadata: Optional[StrictBool] = False, ): """Folder name based case-insensitive search for folders in project. :param project: project name :type project: str + :param folder_name: the new folder's name :type folder_name: str. If None, all the folders in the project will be returned. + + :param status: search folders via status. If None, all folders will be returned. Available statuses are: + :type status: + :param return_metadata: return metadata of folders instead of names :type return_metadata: bool @@ -424,14 +428,19 @@ def search_folders( condition &= Condition("name", folder_name, EQ) if return_metadata: condition &= Condition("includeUsers", return_metadata, EQ) - + if status: + condition &= Condition( + "status", constants.ProjectStatus.get_value(status), EQ + ) response = self.controller.folders.list(project, condition) if response.errors: raise AppException(response.errors) data = response.data if return_metadata: return [ - BaseSerializer(folder).serialize(exclude={"completedCount", "is_root"}) + FolderSerializer(folder).serialize( + exclude={"completedCount", "is_root"} + ) for folder in data if not folder.is_root ] @@ -1770,205 +1779,6 @@ def run_prediction( raise AppException(response.errors) return response.data - def add_annotation_bbox_to_image( - self, - project: NotEmptyStr, - image_name: NotEmptyStr, - bbox: List[float], - annotation_class_name: NotEmptyStr, - annotation_class_attributes: Optional[List[dict]] = None, - error: Optional[StrictBool] = None, - ): - """Add a bounding box annotation to image annotations - - annotation_class_attributes has the form - [ {"name" : "" }, "groupName" : ""} ], ... ] - - :param project: project name or folder path (e.g., "project1/folder1") - :type project: str - :param image_name: image name - :type image_name: str - :param bbox: 4 element list of top-left x,y and bottom-right x, y coordinates - :type bbox: list of floats - :param annotation_class_name: annotation class name - :type annotation_class_name: str - :param annotation_class_attributes: list of annotation class attributes - :type annotation_class_attributes: list of 2 element dicts - :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) - - if project.type in [ - constants.ProjectType.VIDEO, - constants.ProjectType.DOCUMENT, - ]: - raise AppException(LIMITED_FUNCTIONS[project.type]) - folder = self.controller.get_folder(project, folder_name) - response = self.controller.annotations.list( - project=project, folder=folder, item_names=[image_name], verbose=False - ) - if not response.data: - raise AppException("Image not found.") - if response.data: - annotations = response.data[0] - else: - annotations = {} - annotations = add_annotation_bbox_to_json( - annotations, - bbox, - annotation_class_name, - annotation_class_attributes, - error, - image_name, - ) - self.controller.annotations.upload_image_annotations( - project=project, - folder=folder, - image=entities.ImageEntity( - **self.controller.items.get_by_name( - project, folder, image_name - ).data.dict() - ), - annotations=annotations, - team=self.controller.team, - ) - - def add_annotation_point_to_image( - self, - project: NotEmptyStr, - image_name: NotEmptyStr, - point: List[float], - annotation_class_name: NotEmptyStr, - annotation_class_attributes: Optional[List[dict]] = None, - error: Optional[StrictBool] = None, - ): - """Add a point annotation to image annotations - - annotation_class_attributes has the form [ {"name" : "", "groupName" : ""}, ... ] - - :param project: project name or folder path (e.g., "project1/folder1") - :type project: str - :param image_name: image name - :type image_name: str - :param point: [x,y] list of coordinates - :type point: list of floats - :param annotation_class_name: annotation class name - :type annotation_class_name: str - :param annotation_class_attributes: list of annotation class attributes - :type annotation_class_attributes: list of 2 element dicts - :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, - constants.ProjectType.DOCUMENT, - ]: - raise AppException(LIMITED_FUNCTIONS[project.type]) - response = self.controller.annotations.list( - project=project, folder=folder, item_names=[image_name], verbose=False - ) - if not response.data: - raise AppException("Image not found.") - if response.data: - annotations = response.data[0] - else: - annotations = {} - annotations = add_annotation_point_to_json( - annotations, - point, - annotation_class_name, - annotation_class_attributes, - error, - ) - response = self.controller.annotations.upload_image_annotations( - project=project, - folder=folder, - image=self.controller.items.get_by_name(project, folder, image_name).data, - annotations=annotations, - team=self.controller.team, - ) - if response.errors: - raise AppException(response.errors) - - def add_annotation_comment_to_image( - self, - project: NotEmptyStr, - image_name: NotEmptyStr, - comment_text: NotEmptyStr, - comment_coords: List[float], - comment_author: EmailStr, - resolved: Optional[StrictBool] = False, - ): - """Add a comment to SuperAnnotate format annotation JSON - - :param project: project name or folder path (e.g., "project1/folder1") - :type project: str - :param image_name: image name - :type image_name: str - :param comment_text: comment text - :type comment_text: str - :param comment_coords: [x, y] coords - :type comment_coords: list - :param comment_author: comment author email - :type comment_author: str - :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 [ - constants.ProjectType.VIDEO, - constants.ProjectType.DOCUMENT, - ]: - raise AppException(LIMITED_FUNCTIONS[project.type]) - project, folder = self.controller.get_project_folder(project_name, folder_name) - response = self.controller.annotations.list( - project=project, folder=folder, item_names=[image_name], verbose=False - ) - if not response.data: - raise AppException("Image not found.") - if response.data: - annotations = response.data[0] - else: - annotations = {} - annotations = add_annotation_comment_to_json( - annotations, - comment_text, - comment_coords, - comment_author, - resolved=resolved, - image_name=image_name, - ) - - self.controller.annotations.upload_image_annotations( - project=project, - folder=folder, - image=self.controller.items.get_by_name(project, folder, image_name).data, - annotations=annotations, - team=self.controller.team, - ) - def upload_image_to_project( self, project: NotEmptyStr, diff --git a/src/superannotate/lib/app/interface/types.py b/src/superannotate/lib/app/interface/types.py index 250ec3405..cb76193a1 100644 --- a/src/superannotate/lib/app/interface/types.py +++ b/src/superannotate/lib/app/interface/types.py @@ -7,6 +7,7 @@ from lib.core.enums import AnnotationStatus from lib.core.enums import BaseTitledEnum from lib.core.enums import ClassTypeEnum +from lib.core.enums import FolderStatus from lib.core.enums import ProjectStatus from lib.core.enums import ProjectType from lib.core.enums import UserRole @@ -70,6 +71,18 @@ def validate(cls, value: Union[str]) -> Union[str]: return value +class FolderStatusEnum(StrictStr): + @classmethod + def validate(cls, value: Union[str]) -> Union[str]: + if cls.curtail_length and len(value) > cls.curtail_length: + value = value[: cls.curtail_length] + if value.lower() not in FolderStatus.values(): + raise TypeError( + f"Available statuses is {', '.join(FolderStatus.titles())}. " + ) + return value + + class AnnotatorRole(StrictStr): ANNOTATOR_ROLES = (UserRole.ADMIN.name, UserRole.ANNOTATOR.name, UserRole.QA.name) diff --git a/src/superannotate/lib/app/serializers.py b/src/superannotate/lib/app/serializers.py index 461dfa908..eddd1958f 100644 --- a/src/superannotate/lib/app/serializers.py +++ b/src/superannotate/lib/app/serializers.py @@ -141,6 +141,21 @@ def serialize( return data +class FolderSerializer(BaseSerializer): + def serialize( + self, + fields: List[str] = None, + by_alias: bool = False, + flat: bool = False, + exclude: Set[str] = None, + ): + data = super().serialize(fields, by_alias, flat, exclude) + if not data.get("status"): + data["status"] = "Undefined" + + return data + + class SettingsSerializer: def __init__(self, data: dict): self.data = data diff --git a/src/superannotate/lib/core/entities/folder.py b/src/superannotate/lib/core/entities/folder.py index 14d20ccb3..7acde8065 100644 --- a/src/superannotate/lib/core/entities/folder.py +++ b/src/superannotate/lib/core/entities/folder.py @@ -2,12 +2,14 @@ from typing import Optional from lib.core.entities.base import TimedBaseModel +from lib.core.enums import FolderStatus from pydantic import Extra class FolderEntity(TimedBaseModel): id: Optional[int] name: Optional[str] + status: Optional[FolderStatus] project_id: Optional[int] parent_id: Optional[int] team_id: Optional[int] diff --git a/src/superannotate/lib/core/enums.py b/src/superannotate/lib/core/enums.py index 7679c3ae2..8e0c7407f 100644 --- a/src/superannotate/lib/core/enums.py +++ b/src/superannotate/lib/core/enums.py @@ -108,6 +108,14 @@ class ProjectStatus(BaseTitledEnum): OnHold = "OnHold", 4 +class FolderStatus(BaseTitledEnum): + Undefined = "Undefined", 0 + NotStarted = "NotStarted", 1 + InProgress = "InProgress", 2 + Completed = "Completed", 3 + OnHold = "OnHold", 4 + + class ExportStatus(BaseTitledEnum): IN_PROGRESS = "inProgress", 1 COMPLETE = "complete", 2 diff --git a/src/superannotate/lib/core/usecases/annotations.py b/src/superannotate/lib/core/usecases/annotations.py index 1b46a188f..7f983b62e 100644 --- a/src/superannotate/lib/core/usecases/annotations.py +++ b/src/superannotate/lib/core/usecases/annotations.py @@ -513,6 +513,8 @@ def prepare_annotation(self, annotation: dict, size) -> dict: service_provider=self._service_provider, ) errors = use_case.execute().data + logger.debug("Invalid json data") + logger.debug("\n".join(["-".join(i) for i in use_case.execute().data if i])) if errors: raise AppException(errors) diff --git a/tests/data_set/sample_video_num_free_text/classes/classes.json b/tests/data_set/sample_video_num_free_text/classes/classes.json new file mode 100644 index 000000000..afa8e6553 --- /dev/null +++ b/tests/data_set/sample_video_num_free_text/classes/classes.json @@ -0,0 +1,308 @@ +[ + { + "createdAt": "2022-08-16T06:36:40.000Z", + "updatedAt": "2022-11-23T07:43:46.000Z", + "id": 2012407, + "project_id": 214122, + "type": "tag", + "name": "class_tag", + "color": "#68527D", + "attribute_groups": [ + { + "createdAt": "2022-08-16T06:36:40.000Z", + "updatedAt": "2022-08-16T06:36:40.000Z", + "id": 2086145, + "group_type": "checklist", + "class_id": 2012407, + "name": "tag_group", + "attributes": [ + { + "createdAt": "2022-08-16T06:36:40.000Z", + "updatedAt": "2022-08-16T06:36:40.000Z", + "id": 4690051, + "group_id": 2086145, + "project_id": 214122, + "name": "atr_group1", + "default": 1, + "count": 0 + }, + { + "createdAt": "2022-08-16T06:36:40.000Z", + "updatedAt": "2022-08-16T06:36:40.000Z", + "id": 4690052, + "group_id": 2086145, + "project_id": 214122, + "name": "atr_group2", + "default": 0, + "count": 0 + }, + { + "createdAt": "2022-08-16T06:36:40.000Z", + "updatedAt": "2022-08-16T06:36:40.000Z", + "id": 4690053, + "group_id": 2086145, + "project_id": 214122, + "name": "atr_group3", + "default": 1, + "count": 0 + } + ], + "default_value": [ + "atr_group1", + "atr_group3" + ] + }, + { + "createdAt": "2022-11-23T07:43:46.000Z", + "updatedAt": "2022-11-23T07:43:46.000Z", + "id": 3077617, + "group_type": "text", + "class_id": 2012407, + "name": "text_group_tag", + "attributes": [] + }, + { + "createdAt": "2022-11-23T07:43:46.000Z", + "updatedAt": "2022-11-23T07:43:46.000Z", + "id": 3077618, + "group_type": "numeric", + "class_id": 2012407, + "name": "numeric_group_tag", + "attributes": [] + } + ], + "count": 0 + }, + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-11-23T07:43:07.000Z", + "id": 1820068, + "project_id": 214122, + "type": "object", + "name": "car", + "color": "#99C73D", + "attribute_groups": [ + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-06-29T12:12:17.000Z", + "id": 1752336, + "group_type": "checklist", + "class_id": 1820068, + "name": "movement", + "attributes": [ + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-06-29T12:12:17.000Z", + "id": 4014861, + "group_id": 1752336, + "project_id": 214122, + "name": "goes", + "default": 0, + "count": 0 + }, + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-06-29T12:12:17.000Z", + "id": 4014862, + "group_id": 1752336, + "project_id": 214122, + "name": "stops", + "default": 0, + "count": 0 + }, + { + "createdAt": "2022-08-16T07:22:59.000Z", + "updatedAt": "2022-08-16T07:22:59.000Z", + "id": 4690070, + "group_id": 1752336, + "project_id": 214122, + "name": "off", + "default": 0, + "count": 0 + } + ], + "default_value": [] + }, + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-06-29T12:12:17.000Z", + "id": 1752337, + "group_type": "radio", + "class_id": 1820068, + "name": "lights", + "attributes": [ + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-06-29T12:12:17.000Z", + "id": 4014863, + "group_id": 1752337, + "project_id": 214122, + "name": "lights_on", + "default": 0, + "count": 0 + }, + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-06-29T12:12:17.000Z", + "id": 4014864, + "group_id": 1752337, + "project_id": 214122, + "name": "lights_off", + "default": 0, + "count": 0 + } + ], + "default_value": null + }, + { + "createdAt": "2022-11-23T07:43:07.000Z", + "updatedAt": "2022-11-23T07:43:07.000Z", + "id": 3077615, + "group_type": "text", + "class_id": 1820068, + "name": "text_group", + "attributes": [] + }, + { + "createdAt": "2022-11-23T07:43:07.000Z", + "updatedAt": "2022-11-23T07:43:07.000Z", + "id": 3077616, + "group_type": "numeric", + "class_id": 1820068, + "name": "numeric group", + "attributes": [] + } + ], + "count": 0 + }, + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-08-16T07:24:29.000Z", + "id": 1820067, + "project_id": 214122, + "type": "object", + "name": "tree", + "color": "#DB5FA6", + "attribute_groups": [ + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-06-29T12:12:17.000Z", + "id": 1752335, + "group_type": "checklist", + "class_id": 1820067, + "name": "state", + "attributes": [ + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-06-29T12:12:17.000Z", + "id": 4014859, + "group_id": 1752335, + "project_id": 214122, + "name": "standing", + "default": 0, + "count": 0 + }, + { + "createdAt": "2022-06-29T12:12:17.000Z", + "updatedAt": "2022-06-29T12:12:17.000Z", + "id": 4014860, + "group_id": 1752335, + "project_id": 214122, + "name": "falling", + "default": 0, + "count": 0 + }, + { + "createdAt": "2022-08-16T07:24:30.000Z", + "updatedAt": "2022-08-16T07:24:30.000Z", + "id": 4690075, + "group_id": 1752335, + "project_id": 214122, + "name": "down", + "default": 0, + "count": 0 + } + ], + "default_value": [] + } + ], + "count": 0 + }, + { + "createdAt": "2022-03-31T13:06:42.000Z", + "updatedAt": "2022-03-31T13:06:42.000Z", + "id": 1645591, + "project_id": 214122, + "type": "object", + "name": "class3", + "color": "#AF0FF1", + "attribute_groups": [], + "count": 0 + }, + { + "createdAt": "2022-03-31T13:06:34.000Z", + "updatedAt": "2022-03-31T13:06:34.000Z", + "id": 1645590, + "project_id": 214122, + "type": "object", + "name": "class2", + "color": "#F37A77", + "attribute_groups": [], + "count": 0 + }, + { + "createdAt": "2022-03-31T13:06:29.000Z", + "updatedAt": "2022-08-10T11:53:48.000Z", + "id": 1645589, + "project_id": 214122, + "type": "tag", + "name": "class1", + "color": "#FDDC1C", + "attribute_groups": [ + { + "createdAt": "2022-08-10T11:53:48.000Z", + "updatedAt": "2022-08-10T11:53:48.000Z", + "id": 1941208, + "group_type": "checklist", + "class_id": 1645589, + "name": "atr_group_tag", + "attributes": [ + { + "createdAt": "2022-08-10T11:53:48.000Z", + "updatedAt": "2022-08-10T11:53:48.000Z", + "id": 4396132, + "group_id": 1941208, + "project_id": 214122, + "name": "atr1", + "default": 1, + "count": 0 + }, + { + "createdAt": "2022-08-10T11:53:48.000Z", + "updatedAt": "2022-08-10T11:53:48.000Z", + "id": 4396133, + "group_id": 1941208, + "project_id": 214122, + "name": "atr2", + "default": 0, + "count": 0 + }, + { + "createdAt": "2022-08-10T11:53:48.000Z", + "updatedAt": "2022-08-10T11:53:48.000Z", + "id": 4396134, + "group_id": 1941208, + "project_id": 214122, + "name": "atr3", + "default": 0, + "count": 0 + } + ], + "default_value": [ + "atr1" + ] + } + ], + "count": 0 + } +] \ No newline at end of file diff --git a/tests/data_set/sample_video_num_free_text/sample_video.mp4.json b/tests/data_set/sample_video_num_free_text/sample_video.mp4.json new file mode 100644 index 000000000..0d1192849 --- /dev/null +++ b/tests/data_set/sample_video_num_free_text/sample_video.mp4.json @@ -0,0 +1,837 @@ +{ + "metadata": { + "name": "Go_out_avi.avi", + "duration": 160440000, + "annotatorEmail": "automation_sa@mailinator.com", + "error": false, + "height": 360, + "lastAction": { + "timestamp": 1669189913059, + "email": "arturn@superannotate.com" + }, + "projectId": 214122, + "qaEmail": "test_automation1@mailinator.com", + "status": "InProgress", + "url": "videos/videos/Go_out_avi.avi", + "width": 480 + }, + "instances": [ + { + "meta": { + "type": "bbox", + "classId": 1820068, + "className": "car", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-07-04T10:49:10.450Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-11-23T07:51:12.500Z", + "start": 72377, + "end": 2270513, + "pointLabels": {} + }, + "parameters": [ + { + "start": 72377, + "end": 2270513, + "timestamps": [ + { + "attributes": [ + { + "groupId": 3077615, + "name": "car movement", + "groupName": "text_group" + }, + { + "groupId": 3077616, + "name": 3, + "groupName": "numeric group" + } + ], + "points": { + "x1": 47.91, + "x2": 152.14, + "y1": 96.44, + "y2": 208.38 + }, + "timestamp": 72377 + }, + { + "attributes": [ + { + "groupId": 3077615, + "name": "car movement", + "groupName": "text_group" + }, + { + "groupId": 3077616, + "name": 3, + "groupName": "numeric group" + }, + { + "id": 4014861, + "groupId": 1752336, + "name": "goes", + "groupName": "movement" + }, + { + "id": 4014863, + "groupId": 1752337, + "name": "lights_on", + "groupName": "lights" + } + ], + "points": { + "x1": 47.91, + "x2": 152.14, + "y1": 96.44, + "y2": 208.38 + }, + "timestamp": 444935 + }, + { + "attributes": [ + { + "groupId": 3077615, + "name": "car movement", + "groupName": "text_group" + }, + { + "groupId": 3077616, + "name": 3, + "groupName": "numeric group" + }, + { + "id": 4014863, + "groupId": 1752337, + "name": "lights_on", + "groupName": "lights" + }, + { + "id": 4014862, + "groupId": 1752336, + "name": "stops", + "groupName": "movement" + } + ], + "points": { + "x1": 47.91, + "x2": 152.14, + "y1": 96.44, + "y2": 208.38 + }, + "timestamp": 1413905 + }, + { + "attributes": [ + { + "groupId": 3077615, + "name": "car movement", + "groupName": "text_group" + }, + { + "groupId": 3077616, + "name": 3, + "groupName": "numeric group" + }, + { + "id": 4014862, + "groupId": 1752336, + "name": "stops", + "groupName": "movement" + }, + { + "id": 4014864, + "groupId": 1752337, + "name": "lights_off", + "groupName": "lights" + } + ], + "points": { + "x1": 47.91, + "x2": 152.14, + "y1": 96.44, + "y2": 208.38 + }, + "timestamp": 2270513 + } + ] + } + ] + }, + { + "meta": { + "type": "bbox", + "classId": 1820068, + "className": "car", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-07-04T10:49:10.453Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-11-23T07:48:01.953Z", + "start": 15967742, + "end": 26167742, + "pointLabels": { + "0": "testlabel", + "2": "label2" + } + }, + "parameters": [ + { + "start": 15967742, + "end": 26167742, + "timestamps": [ + { + "attributes": [], + "points": { + "x1": 150.3, + "x2": 271.83, + "y1": 61.21, + "y2": 302.07 + }, + "timestamp": 15967742 + }, + { + "attributes": [], + "points": { + "x1": 159.87, + "x2": 281.4, + "y1": 58.12, + "y2": 298.98 + }, + "timestamp": 23059570 + }, + { + "attributes": [], + "points": { + "x1": 159.87, + "x2": 281.4, + "y1": 58.12, + "y2": 298.98 + }, + "timestamp": 26167742 + } + ] + } + ] + }, + { + "meta": { + "type": "point", + "classId": 1645591, + "className": "class3", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-07-04T10:49:10.454Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-07-04T10:49:10.454Z", + "start": 2154547, + "end": 3836690 + }, + "parameters": [ + { + "start": 2154547, + "end": 3836690, + "timestamps": [ + { + "attributes": [], + "x": 125.23, + "y": 234.45, + "timestamp": 2154547 + }, + { + "attributes": [], + "x": 130.07000000000002, + "y": 237.95, + "timestamp": 2593436 + }, + { + "attributes": [], + "x": 130.07000000000002, + "y": 237.95, + "timestamp": 3836690 + } + ] + } + ] + }, + { + "meta": { + "type": "event", + "classId": 1645591, + "className": "class3", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-07-04T10:49:10.455Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-07-04T10:49:10.455Z", + "start": 5131014, + "end": 6517905 + }, + "parameters": [ + { + "start": 5131014, + "end": 6517905, + "timestamps": [ + { + "attributes": [], + "timestamp": 5131014 + }, + { + "attributes": [], + "timestamp": 6517905 + } + ] + } + ] + }, + { + "meta": { + "type": "polygon", + "classId": 1820067, + "className": "tree", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-07-04T10:49:10.455Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-10-18T11:21:20.267Z", + "start": 11704101, + "end": 19463671 + }, + "parameters": [ + { + "start": 11704101, + "end": 19463671, + "timestamps": [ + { + "attributes": [], + "points": [ + 72.93, + 82.17, + 170.77, + 89.95, + 131.95, + 220.25 + ], + "timestamp": 11704101 + }, + { + "attributes": [ + { + "id": 4014859, + "groupId": 1752335, + "name": "standing", + "groupName": "state" + } + ], + "points": [ + 72.93, + 82.17, + 170.77, + 89.95, + 131.95, + 220.25 + ], + "timestamp": 14244140 + }, + { + "attributes": [ + { + "id": 4014859, + "groupId": 1752335, + "name": "standing", + "groupName": "state" + } + ], + "points": [ + 33.59, + 65.03, + 131.43, + 72.81, + 92.61, + 203.11 + ], + "timestamp": 16335937 + }, + { + "attributes": [ + { + "id": 4014859, + "groupId": 1752335, + "name": "standing", + "groupName": "state" + } + ], + "points": [ + 33.59, + 65.03, + 131.43, + 72.81, + 92.61, + 203.11 + ], + "timestamp": 19463671 + } + ] + } + ] + }, + { + "meta": { + "type": "polyline", + "classId": 1820067, + "className": "tree", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-07-04T10:49:10.457Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-10-18T11:23:40.500Z", + "start": 8616210, + "end": 12391405 + }, + "parameters": [ + { + "start": 8616210, + "end": 12391405, + "timestamps": [ + { + "attributes": [ + { + "id": 4014860, + "groupId": 1752335, + "name": "falling", + "groupName": "state" + }, + { + "id": 4690075, + "groupId": 1752335, + "name": "down", + "groupName": "state" + } + ], + "points": [ + 147.08, + 160.03, + 223.49, + 196.65 + ], + "timestamp": 8616210 + }, + { + "attributes": [ + { + "id": 4014860, + "groupId": 1752335, + "name": "falling", + "groupName": "state" + }, + { + "id": 4690075, + "groupId": 1752335, + "name": "down", + "groupName": "state" + } + ], + "points": [ + 197.68, + 116.66, + 274.09, + 153.28 + ], + "timestamp": 11156249 + }, + { + "attributes": [ + { + "id": 4014860, + "groupId": 1752335, + "name": "falling", + "groupName": "state" + }, + { + "id": 4690075, + "groupId": 1752335, + "name": "down", + "groupName": "state" + } + ], + "points": [ + 197.68, + 116.66, + 274.09, + 153.28 + ], + "timestamp": 12391405 + } + ] + } + ] + }, + { + "meta": { + "type": "point", + "classId": 1820067, + "className": "tree", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-07-04T11:17:54.078Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-11-09T14:55:50.645Z", + "start": 28737304, + "end": 45725853 + }, + "parameters": [ + { + "start": 28737304, + "end": 34006640, + "timestamps": [ + { + "attributes": [ + { + "id": 4014859, + "groupId": 1752335, + "name": "standing", + "groupName": "state" + } + ], + "x": 235.82, + "y": 108.88, + "timestamp": 28737304 + }, + { + "attributes": [ + { + "id": 4014860, + "groupId": 1752335, + "name": "falling", + "groupName": "state" + } + ], + "x": 238.91, + "y": 118.14, + "timestamp": 30779297 + }, + { + "attributes": [ + { + "id": 4014860, + "groupId": 1752335, + "name": "falling", + "groupName": "state" + } + ], + "x": 238.91, + "y": 118.14, + "timestamp": 34006640 + } + ] + }, + { + "start": 35525853, + "end": 45725853, + "timestamps": [ + { + "attributes": [ + { + "id": 4014860, + "groupId": 1752335, + "name": "falling", + "groupName": "state" + } + ], + "x": 238.91, + "y": 118.14, + "timestamp": 35525853 + }, + { + "attributes": [ + { + "id": 4014860, + "groupId": 1752335, + "name": "falling", + "groupName": "state" + }, + { + "id": 4690075, + "groupId": 1752335, + "name": "down", + "groupName": "state" + } + ], + "x": 238.91, + "y": 118.14, + "timestamp": 38162926 + }, + { + "attributes": [ + { + "id": 4014859, + "groupId": 1752335, + "name": "standing", + "groupName": "state" + } + ], + "x": 238.91, + "y": 118.14, + "timestamp": 40949268 + }, + { + "attributes": [ + { + "id": 4014859, + "groupId": 1752335, + "name": "standing", + "groupName": "state" + } + ], + "x": 238.91, + "y": 118.14, + "timestamp": 45725853 + } + ] + } + ] + }, + { + "meta": { + "type": "tag", + "classId": 1645589, + "className": "class1", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-08-10T11:54:37.707Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-08-10T11:54:37.707Z", + "attributes": [ + { + "id": 4396132, + "groupId": 1941208, + "name": "atr1", + "groupName": "atr_group_tag" + } + ] + } + }, + { + "meta": { + "type": "tag", + "classId": 2012407, + "className": "class_tag", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-08-16T07:20:16.315Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-11-23T07:51:53.052Z", + "attributes": [ + { + "groupId": 3077618, + "name": "", + "groupName": "numeric_group_tag" + }, + { + "groupId": 3077617, + "name": "", + "groupName": "text_group_tag" + }, + { + "id": 4690051, + "groupId": 2086145, + "name": "atr_group1", + "groupName": "tag_group" + }, + { + "id": 4690053, + "groupId": 2086145, + "name": "atr_group3", + "groupName": "tag_group" + } + ] + } + }, + { + "meta": { + "type": "comment", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-11-09T13:27:28.873Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-11-09T14:26:35.123Z", + "start": 9801951, + "end": 12240000, + "correspondence": [ + { + "email": "arturn@superannotate.com", + "role": "Admin", + "text": "es incha ay axper", + "createdAt": "2022-11-09T13:27:39.129Z" + }, + { + "email": "arturn@superannotate.com", + "role": "Admin", + "text": "qez inch ay tgha", + "createdAt": "2022-11-09T14:26:32.861Z" + } + ], + "resolved": true + }, + "parameters": [ + { + "start": 9801951, + "end": 12240000, + "timestamps": [ + { + "points": { + "x1": 136.41, + "y1": 147.15, + "x2": 232.23, + "y2": 207.31 + }, + "timestamp": 9801951 + }, + { + "points": { + "x1": 184.47, + "y1": 106.54, + "x2": 280.29, + "y2": 166.7 + }, + "timestamp": 11156249 + }, + { + "points": { + "x1": 184.47, + "y1": 106.54, + "x2": 280.29, + "y2": 166.7 + }, + "timestamp": 12240000 + } + ] + } + ] + }, + { + "meta": { + "type": "comment", + "createdBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "createdAt": "2022-11-09T13:30:11.971Z", + "updatedBy": { + "email": "arturn@superannotate.com", + "role": "Admin" + }, + "updatedAt": "2022-11-09T13:30:49.326Z", + "start": 24231219, + "end": 30152195, + "correspondence": [ + { + "email": "arturn@superannotate.com", + "role": "Admin", + "text": "sxal tex es drel aperik", + "createdAt": "2022-11-09T13:30:26.194Z" + } + ], + "resolved": true + }, + "parameters": [ + { + "start": 24231219, + "end": 30152195, + "timestamps": [ + { + "points": { + "x1": 65.21, + "y1": 12.05, + "x2": 316.96, + "y2": 327.74 + }, + "timestamp": 24231219 + }, + { + "points": { + "x1": 59.73, + "y1": 0, + "x2": 311.48, + "y2": 315.69 + }, + "timestamp": 25077073 + }, + { + "points": { + "x1": 53.3, + "y1": 4.16, + "x2": 305.05, + "y2": 319.85 + }, + "timestamp": 26420487 + }, + { + "points": { + "x1": 53.3, + "y1": 4.16, + "x2": 305.05, + "y2": 319.85 + }, + "timestamp": 30152195 + } + ] + } + ] + } + ], + "tags": [ + "tags text" + ] +} \ No newline at end of file diff --git a/tests/integration/annotations/test_annotation_adding.py b/tests/integration/annotations/test_annotation_adding.py deleted file mode 100644 index 83965c0ec..000000000 --- a/tests/integration/annotations/test_annotation_adding.py +++ /dev/null @@ -1,211 +0,0 @@ -import json -import os -import tempfile -from pathlib import Path - -from src.superannotate import SAClient -from tests.integration.base import BaseTestCase - -sa = SAClient() - - -class TestAnnotationAdding(BaseTestCase): - PROJECT_NAME = "test_annotations_adding" - TEST_FOLDER_PATH = "data_set/sample_project_vector" - TEST_INVALID_ANNOTATION_FOLDER_PATH = "data_set/sample_project_vector_invalid" - PROJECT_DESCRIPTION = "desc" - PROJECT_TYPE = "Vector" - EXAMPLE_IMAGE_1 = "example_image_1.jpg" - EXAMPLE_IMAGE_2 = "example_image_2.jpg" - - def _attach_fake_items(self): - sa.attach_items(self.PROJECT_NAME, [{"name": self.EXAMPLE_IMAGE_1, "url": self.EXAMPLE_IMAGE_1}]) # noqa - - @property - def folder_path(self): - return os.path.join(Path(__file__).parent.parent.parent, self.TEST_FOLDER_PATH) - - @property - def invalid_json_path(self): - return os.path.join(Path(__file__).parent.parent.parent, self.TEST_INVALID_ANNOTATION_FOLDER_PATH) - - @property - def classes_json_path(self): - return f"{self.folder_path}/classes/classes.json" - - def test_add_point_to_empty_image(self): - sa.attach_items(self.PROJECT_NAME, [{"name": self.EXAMPLE_IMAGE_1, "url": self.EXAMPLE_IMAGE_1}]) # noqa - - sa.add_annotation_point_to_image( - self.PROJECT_NAME, self.EXAMPLE_IMAGE_1, [250, 250], "test_add" - ) - annotations_new = sa.get_annotations(self.PROJECT_NAME, [self.EXAMPLE_IMAGE_1])[0] - - del annotations_new["instances"][0]['createdAt'] - del annotations_new["instances"][0]['updatedAt'] - - self.assertEqual( - annotations_new["instances"][0], { - 'x': 250, - 'y': 250, - 'creationType': 'Preannotation', - 'visible': True, - 'locked': False, - 'probability': 100, - 'attributes': [], - 'type': 'point', - 'pointLabels': {}, - 'groupId': 0, - 'classId': -1, - 'className': "", - } - ) - - def test_add_bbox_to_empty_annotation(self): - sa.attach_items(self.PROJECT_NAME, [{"name": self.EXAMPLE_IMAGE_1, "url": self.EXAMPLE_IMAGE_1}]) # noqa - sa.add_annotation_bbox_to_image( - self.PROJECT_NAME, self.EXAMPLE_IMAGE_1, [10, 10, 500, 100], "test_add" - ) - annotations_new = sa.get_annotations(self.PROJECT_NAME, [self.EXAMPLE_IMAGE_1])[0] - del annotations_new["instances"][0]['createdAt'] - del annotations_new["instances"][0]['updatedAt'] - self.assertEqual(annotations_new['instances'][0], { - 'creationType': 'Preannotation', - 'visible': True, - 'locked': False, - 'probability': 100, - 'attributes': [], - 'type': 'bbox', - 'pointLabels': {}, - 'groupId': 0, - 'points': {'x1': 10, 'x2': 500, 'y1': 10, 'y2': 100}, - 'classId': -1, - 'className': '', - }) - - def test_add_comment_to_empty_annotation(self): - sa.attach_items(self.PROJECT_NAME, [{"name": self.EXAMPLE_IMAGE_1, "url": self.EXAMPLE_IMAGE_1}]) # noqa - - sa.add_annotation_comment_to_image(self.PROJECT_NAME, self.EXAMPLE_IMAGE_1, "some comment", [1, 2], - "abc@abc.com") - - annotations_new = sa.get_annotations(self.PROJECT_NAME, [self.EXAMPLE_IMAGE_1])[0] - - annotations_new['comments'][0]['createdAt'] = "" - annotations_new['comments'][0]['updatedAt'] = "" - - self.assertEqual(annotations_new['comments'][0], - {'createdBy': {'email': 'abc@abc.com', 'role': 'Admin'}, - 'updatedBy': {'email': 'abc@abc.com', 'role': 'Admin'}, - 'creationType': 'Preannotation', - 'createdAt': '', - 'updatedAt': '', - 'x': 1, 'y': 2, - 'resolved': False, - 'correspondence': [{'text': 'some comment', 'email': 'abc@abc.com'}] - }) - - def test_adding(self): - sa.attach_items(self.PROJECT_NAME, [{"name": self.EXAMPLE_IMAGE_1, "url": self.EXAMPLE_IMAGE_1}]) # noqa - sa.create_annotation_classes_from_classes_json( - self.PROJECT_NAME, self.classes_json_path - ) - sa.create_annotation_class( - self.PROJECT_NAME, - "test_add", - "#FF0000", - [{"name": "height", "attributes": [{"name": "tall"}, {"name": "short"}]}], - ) - sa.upload_annotations_from_folder_to_project( - self.PROJECT_NAME, self.folder_path - ) - - annotations = sa.get_annotations(self.PROJECT_NAME, [self.EXAMPLE_IMAGE_1])[0] - - sa.add_annotation_bbox_to_image( - self.PROJECT_NAME, self.EXAMPLE_IMAGE_1, [10, 10, 500, 100], "test_add" - ) - sa.add_annotation_point_to_image( - self.PROJECT_NAME, self.EXAMPLE_IMAGE_1, [250, 250], "test_add" - ) - sa.add_annotation_comment_to_image( - self.PROJECT_NAME, - self.EXAMPLE_IMAGE_1, - "Hello World", - [100, 100], - "super@annotate.com", - True, - ) - annotations_new = sa.get_annotations(self.PROJECT_NAME, [self.EXAMPLE_IMAGE_1])[0] - with tempfile.TemporaryDirectory() as tmpdir_name: - json.dump(annotations_new, open(f"{tmpdir_name}/new_anns.json", "w")) - self.assertEqual( - len(annotations_new["instances"]) + len(annotations_new["comments"]), - len(annotations["instances"]) + len(annotations["comments"]) + 3, - ) - - def test_add_bbox_no_init(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 - ) - sa.create_annotation_class(self.PROJECT_NAME, "test_add", "#FF0000") - sa.add_annotation_bbox_to_image( - self.PROJECT_NAME, self.EXAMPLE_IMAGE_1, [10, 10, 500, 100], "test_add" - ) - annotations_new = sa.get_annotations(self.PROJECT_NAME, [self.EXAMPLE_IMAGE_1])[0] - - self.assertEqual(len(annotations_new["instances"]), 1) - - export = sa.prepare_export(self.PROJECT_NAME, include_fuse=True) - with tempfile.TemporaryDirectory() as tmpdir_name: - sa.download_export(self.PROJECT_NAME, export["name"], tmpdir_name) - - non_empty_annotations = 0 - json_files = Path(tmpdir_name).glob("*.json") - for json_file in json_files: - json_ann = json.load(open(json_file)) - if "instances" in json_ann and len(json_ann["instances"]) > 0: - non_empty_annotations += 1 - self.assertEqual(len(json_ann["instances"]), 1) - - self.assertEqual(non_empty_annotations, 1) - - def test_add_bbox_json(self): - with tempfile.TemporaryDirectory() as tmpdir_name: - dest = Path(f"{tmpdir_name}/test.json") - source = Path(f"{self.folder_path}/{self.EXAMPLE_IMAGE_1}___objects.json") - - dest.write_text(source.read_text()) - annotations = json.load(open(dest)) - annotations_new = json.load(open(dest)) - - self.assertEqual( - len(annotations_new["instances"]), len(annotations["instances"]) - ) - self.assertEqual( - len(annotations_new["comments"]), len(annotations["comments"]) - ) - - def test_add_bbox_with_dict(self): - sa.attach_items(self.PROJECT_NAME, [{"name": self.EXAMPLE_IMAGE_1, "url": self.EXAMPLE_IMAGE_1}]) # noqa - sa.create_annotation_class( - self.PROJECT_NAME, - "test_add", - "#FF0000", - [{"name": "test", "attributes": [{"name": "yes"}, {"name": "no"}]}] - ) - sa.add_annotation_bbox_to_image( - project=self.PROJECT_NAME, - image_name=self.EXAMPLE_IMAGE_1, - bbox=[10, 20, 100, 150], - annotation_class_name="test_add", - annotation_class_attributes=[{'name': 'yes', 'groupName': 'test'}] - ) - annotation = sa.get_annotations(self.PROJECT_NAME, [self.EXAMPLE_IMAGE_1])[0] - self.assertTrue( - all([annotation["instances"][0]["attributes"][0][key] == val for key, val in - {'name': 'yes', 'groupName': 'test'}.items()]) - ) diff --git a/tests/integration/annotations/test_annotations_upload_status_change.py b/tests/integration/annotations/test_annotations_upload_status_change.py deleted file mode 100644 index 0637beab3..000000000 --- a/tests/integration/annotations/test_annotations_upload_status_change.py +++ /dev/null @@ -1,86 +0,0 @@ -import os -from os.path import join -from pathlib import Path -from unittest.mock import MagicMock -from unittest.mock import patch - -import pytest - -import src.superannotate.lib.core as constances -from src.superannotate import SAClient -from src.superannotate import AppException -from tests.integration.base import BaseTestCase - -sa = SAClient() - - -class TestAnnotationUploadStatusChangeVector(BaseTestCase): - PROJECT_NAME = "TestAnnotationUploadStatusChangeVector" - PROJECT_DESCRIPTION = "Desc" - PROJECT_TYPE = "Vector" - S3_FOLDER_PATH = "sample_project_vector" - TEST_FOLDER_PATH = "data_set/sample_project_vector" - IMAGE_NAME = "example_image_1.jpg" - - @property - def folder_path(self): - return os.path.join(Path(__file__).parent.parent.parent, self.TEST_FOLDER_PATH) - - @pytest.mark.flaky(reruns=2) - @patch("lib.infrastructure.controller.Reporter") - def test_upload_annotations_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_annotations_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): - reporter_mock = MagicMock() - reporter.return_value = reporter_mock - annotation_path = join(self.folder_path, f"{self.IMAGE_NAME}___objects.json") - sa.upload_image_to_project(self.PROJECT_NAME, join(self.folder_path, self.IMAGE_NAME)) - sa.upload_image_annotations(self.PROJECT_NAME, self.IMAGE_NAME, annotation_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_add_annotation_bbox_to_image__annotation_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.add_annotation_bbox_to_image(self.PROJECT_NAME, self.IMAGE_NAME, [1, 2, 3, 4], "bbox") - self.assertEqual( - constances.AnnotationStatus.IN_PROGRESS.name, - sa.get_item_metadata(self.PROJECT_NAME, self.IMAGE_NAME)["annotation_status"] - ) - - def test_add_annotation_bbox_to_image_not_existing_image(self): - reporter_mock = MagicMock() - with self.assertRaisesRegexp(AppException, "Image not found."): - sa.add_annotation_bbox_to_image(self.PROJECT_NAME, "self.IMAGE_NAME", [1, 2, 3, 4], "bbox") - - @pytest.mark.flaky(reruns=2) - @patch("lib.infrastructure.controller.Reporter") - def test_add_annotation_comment_to_image__annotation_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.add_annotation_comment_to_image( - self.PROJECT_NAME, - self.IMAGE_NAME, - "Hello World!", - [1, 2], - "user@superannoate.com") - self.assertEqual( - constances.AnnotationStatus.IN_PROGRESS.name, - sa.get_item_metadata(self.PROJECT_NAME, self.IMAGE_NAME)["annotation_status"] - ) diff --git a/tests/integration/annotations/video/__init__.py b/tests/integration/annotations/video/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/integration/annotations/test_get_annotations_per_frame.py b/tests/integration/annotations/video/test_get_annotations_per_frame.py similarity index 100% rename from tests/integration/annotations/test_get_annotations_per_frame.py rename to tests/integration/annotations/video/test_get_annotations_per_frame.py diff --git a/tests/integration/annotations/test_video_annotation_upload.py b/tests/integration/annotations/video/test_video_annotation_upload.py similarity index 100% rename from tests/integration/annotations/test_video_annotation_upload.py rename to tests/integration/annotations/video/test_video_annotation_upload.py diff --git a/tests/integration/annotations/video/test_video_free_text_numeric.py b/tests/integration/annotations/video/test_video_free_text_numeric.py new file mode 100644 index 000000000..bc7e22da4 --- /dev/null +++ b/tests/integration/annotations/video/test_video_free_text_numeric.py @@ -0,0 +1,32 @@ +import os + +from tests import DATA_SET_PATH + +from src.superannotate import SAClient +from tests.integration.base import BaseTestCase + +sa = SAClient() + + +class TestUploadVideoFreNumAnnotation(BaseTestCase): + PROJECT_NAME = "video annotation upload with ree text and numeric" + PROJECT_DESCRIPTION = "desc" + PROJECT_TYPE = "Video" + ANNOTATIONS_PATH = "sample_video_num_free_text" + ITEM_NAME = "sample_video.mp4" + CLASSES_PATH = "classes/classes.json" + + @property + def folder_path(self): + return os.path.join(DATA_SET_PATH, self.ANNOTATIONS_PATH) + + def test_upload(self): + sa.attach_items(self.PROJECT_NAME, [{'url': 'sad', 'name': self.ITEM_NAME}]) # noqa + sa.create_annotation_classes_from_classes_json( + self.PROJECT_NAME, os.path.join(self.folder_path, self.CLASSES_PATH) + ) + (uploaded_annotations, failed_annotations, missing_annotations) = sa.upload_annotations_from_folder_to_project( + self.PROJECT_NAME, self.folder_path) + self.assertEqual(len(uploaded_annotations), 0) + self.assertEqual(len(failed_annotations), 1) + self.assertEqual(len(missing_annotations), 0) diff --git a/tests/integration/folders/test_folders.py b/tests/integration/folders/test_folders.py index e61a7df32..7c57d5d3b 100644 --- a/tests/integration/folders/test_folders.py +++ b/tests/integration/folders/test_folders.py @@ -268,6 +268,7 @@ def test_create_long_name(self): def test_search_folder(self): sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1) sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_2) - folders = sa.search_folders(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1) + folders = sa.search_folders(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1, return_metadata=True) assert len(folders) == 1 - assert folders[0] == self.TEST_FOLDER_NAME_1 \ No newline at end of file + assert folders[0] == self.TEST_FOLDER_NAME_1 + assert folders[0]['status'] == 'NotStarted' \ No newline at end of file diff --git a/tests/integration/items/test_copy_items.py b/tests/integration/items/test_copy_items.py index 6f647910b..cf848cadb 100644 --- a/tests/integration/items/test_copy_items.py +++ b/tests/integration/items/test_copy_items.py @@ -54,25 +54,6 @@ def test_skipped_count(self): skipped_items = sa.copy_items(f"{self.PROJECT_NAME}", f"{self.PROJECT_NAME}/{self.FOLDER_1}") assert len(skipped_items) == 7 - def test_copy_item_with_annotations(self): - uploaded, _, _ = sa.attach_items( - self.PROJECT_NAME, [ - {"url": "https://drive.google.com/uc?export=download&id=1vwfCpTzcjxoEA4hhDxqapPOVvLVeS7ZS", - "name": self.IMAGE_NAME} - ] - ) - assert len(uploaded) == 1 - sa.create_annotation_class(self.PROJECT_NAME, "test_class", "#FF0000") - sa.add_annotation_bbox_to_image(self.PROJECT_NAME, self.IMAGE_NAME, [1, 2, 3, 4], "test_class") - sa.create_folder(self.PROJECT_NAME, self.FOLDER_1) - skipped_items = sa.copy_items( - self.PROJECT_NAME, f"{self.PROJECT_NAME}/{self.FOLDER_1}", include_annotations=True - ) - annotations = sa.get_annotations(f"{self.PROJECT_NAME}/{self.FOLDER_1}") - assert len(annotations) == 1 - assert len(skipped_items) == 0 - assert len(sa.search_items(f"{self.PROJECT_NAME}/{self.FOLDER_1}")) == 1 - def test_copy_items_wrong_items_list(self): uploaded, _, _ = sa.attach_items( self.PROJECT_NAME, [ diff --git a/tests/integration/items/test_move_items.py b/tests/integration/items/test_move_items.py index 109704f28..7e1bd4442 100644 --- a/tests/integration/items/test_move_items.py +++ b/tests/integration/items/test_move_items.py @@ -37,21 +37,3 @@ def test_move_items_from_folder(self): assert len(sa.search_items(f"{self.PROJECT_NAME}/{self.FOLDER_2}")) == 7 assert len(sa.search_items(f"{self.PROJECT_NAME}/{self.FOLDER_1}")) == 0 - def test_move_item_with_annotations(self): - uploaded, _, _ = sa.attach_items( - self.PROJECT_NAME, [ - {"url": "https://drive.google.com/uc?export=download&id=1vwfCpTzcjxoEA4hhDxqapPOVvLVeS7ZS", - "name": self.IMAGE_NAME} - ] - ) - assert len(uploaded) == 1 - sa.create_annotation_class(self.PROJECT_NAME, "test_class", "#FF0000") - sa.add_annotation_bbox_to_image(self.PROJECT_NAME, self.IMAGE_NAME, [1, 2, 3, 4], "test_class") - sa.create_folder(self.PROJECT_NAME, self.FOLDER_1) - skipped_items = sa.move_items( - self.PROJECT_NAME, f"{self.PROJECT_NAME}/{self.FOLDER_1}" - ) - annotations = sa.get_annotations(f"{self.PROJECT_NAME}/{self.FOLDER_1}") - assert len(annotations) == 1 - assert len(skipped_items) == 0 - assert len(sa.search_items(f"{self.PROJECT_NAME}/{self.FOLDER_1}")) == 1 diff --git a/tests/integration/test_depricated_functions_document.py b/tests/integration/test_depricated_functions_document.py index 0663322c9..9828ffa4c 100644 --- a/tests/integration/test_depricated_functions_document.py +++ b/tests/integration/test_depricated_functions_document.py @@ -99,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.add_annotation_point_to_image(self.PROJECT_NAME, self.UPLOAD_IMAGE_NAME, [1, 2], "some class") - 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_fuse_gen.py b/tests/integration/test_fuse_gen.py index ffde96e1f..1aa36cb24 100644 --- a/tests/integration/test_fuse_gen.py +++ b/tests/integration/test_fuse_gen.py @@ -62,59 +62,6 @@ def vector_classes_json(self): def pixel_classes_json(self): return f"{self.pixel_folder_path}/classes/classes.json" - @pytest.mark.flaky(reruns=3) - def test_fuse_image_create_vector(self): - with tempfile.TemporaryDirectory() as temp_dir: - temp_dir = pathlib.Path(temp_dir) - - sa.upload_image_to_project( - self.VECTOR_PROJECT_NAME, - f"{self.vector_folder_path}/{self.EXAMPLE_IMAGE_1}", - annotation_status="QualityCheck", - ) - - sa.create_annotation_classes_from_classes_json( - self.VECTOR_PROJECT_NAME, self.vector_classes_json - ) - sa.upload_image_annotations( - project=self.VECTOR_PROJECT_NAME, - image_name=self.EXAMPLE_IMAGE_1, - annotation_json=f"{self.vector_folder_path}/{self.EXAMPLE_IMAGE_1}___objects.json", - ) - - sa.add_annotation_bbox_to_image( - self.VECTOR_PROJECT_NAME, - self.EXAMPLE_IMAGE_1, - [20, 20, 40, 40], - "Human", - ) - sa.add_annotation_point_to_image( - self.VECTOR_PROJECT_NAME, - self.EXAMPLE_IMAGE_1, - [400, 400], - "Personal vehicle", - ) - export = sa.prepare_export(self.VECTOR_PROJECT_NAME, include_fuse=True) - (temp_dir / "export").mkdir() - sa.download_export(self.VECTOR_PROJECT_NAME, export, (temp_dir / "export")) - - paths = sa.download_image( - self.VECTOR_PROJECT_NAME, - self.EXAMPLE_IMAGE_1, - temp_dir, - include_annotations=True, - include_fuse=True, - include_overlay=True, - ) - im1 = Image.open(temp_dir / "export" / f"{self.EXAMPLE_IMAGE_1}___fuse.png") - im1_array = np.array(im1) - - im2 = Image.open(paths[2][0]) - im2_array = np.array(im2) - - self.assertEqual(im1_array.shape, im2_array.shape) - self.assertEqual(im1_array.dtype, im2_array.dtype) - def test_fuse_image_create_pixel(self): with tempfile.TemporaryDirectory() as temp_dir: temp_dir = pathlib.Path(temp_dir) From d6add78a56c843335fe3a0bffd8ffe7fb059bd6c Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 24 Nov 2022 15:57:43 +0400 Subject: [PATCH 02/23] test changes --- .../lib/core/usecases/annotations.py | 7 +- src/superannotate/lib/core/video_convertor.py | 2 +- tests/__init__.py | 8 +- .../sample_video.mp4.json | 82 +++++++++---------- .../video/test_video_annotation_upload.py | 37 ++++----- .../video/test_video_free_text_numeric.py | 8 +- tests/integration/folders/test_folders.py | 2 +- .../integration/test_validate_upload_state.py | 0 8 files changed, 73 insertions(+), 73 deletions(-) delete mode 100644 tests/integration/test_validate_upload_state.py diff --git a/src/superannotate/lib/core/usecases/annotations.py b/src/superannotate/lib/core/usecases/annotations.py index 7f983b62e..f6d8de2a3 100644 --- a/src/superannotate/lib/core/usecases/annotations.py +++ b/src/superannotate/lib/core/usecases/annotations.py @@ -150,7 +150,7 @@ async def upload(_chunk): for i in chunk: callback(i) except Exception: - logger.debug(traceback.format_exc()) + logger.debug(traceback.print_exc()) failed_annotations.extend([i.item.name for i in chunk]) finally: report.failed_annotations.extend(failed_annotations) @@ -513,9 +513,10 @@ def prepare_annotation(self, annotation: dict, size) -> dict: service_provider=self._service_provider, ) errors = use_case.execute().data - logger.debug("Invalid json data") - logger.debug("\n".join(["-".join(i) for i in use_case.execute().data if i])) + if errors: + logger.debug("Invalid json data") + logger.debug("\n".join(["-".join(i) for i in errors])) raise AppException(errors) annotation = UploadAnnotationUseCase.set_defaults( 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) diff --git a/tests/__init__.py b/tests/__init__.py index efdf5adcb..75b3b7865 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,8 +1,7 @@ -import sys import os +import sys from pathlib import Path - os.environ.update({"SA_VERSION_CHECK": "False"}) LIB_PATH = Path(__file__).parent.parent / "src" @@ -12,3 +11,8 @@ __all__ = [ "DATA_SET_PATH" ] + +from src.superannotate import get_default_logger + +logger = get_default_logger() +logger.setLevel("DEBUG") diff --git a/tests/data_set/sample_video_num_free_text/sample_video.mp4.json b/tests/data_set/sample_video_num_free_text/sample_video.mp4.json index 0d1192849..eb8613426 100644 --- a/tests/data_set/sample_video_num_free_text/sample_video.mp4.json +++ b/tests/data_set/sample_video_num_free_text/sample_video.mp4.json @@ -1,26 +1,25 @@ { "metadata": { "name": "Go_out_avi.avi", + "width": 480, + "height": 360, + "status": "InProgress", + "url": "videos/videos/Go_out_avi.avi", "duration": 160440000, - "annotatorEmail": "automation_sa@mailinator.com", + "projectId": 214122, "error": false, - "height": 360, + "annotatorEmail": "automation_sa@mailinator.com", + "qaEmail": "test_automation1@mailinator.com", "lastAction": { "timestamp": 1669189913059, "email": "arturn@superannotate.com" - }, - "projectId": 214122, - "qaEmail": "test_automation1@mailinator.com", - "status": "InProgress", - "url": "videos/videos/Go_out_avi.avi", - "width": 480 + } }, "instances": [ { "meta": { "type": "bbox", - "classId": 1820068, - "className": "car", + "pointLabels": {}, "createdBy": { "email": "arturn@superannotate.com", "role": "Admin" @@ -31,9 +30,10 @@ "role": "Admin" }, "updatedAt": "2022-11-23T07:51:12.500Z", + "classId": 1820068, + "className": "car", "start": 72377, - "end": 2270513, - "pointLabels": {} + "end": 2270513 }, "parameters": [ { @@ -167,8 +167,10 @@ { "meta": { "type": "bbox", - "classId": 1820068, - "className": "car", + "pointLabels": { + "0": "testlabel", + "2": "label2" + }, "createdBy": { "email": "arturn@superannotate.com", "role": "Admin" @@ -179,12 +181,10 @@ "role": "Admin" }, "updatedAt": "2022-11-23T07:48:01.953Z", + "classId": 1820068, + "className": "car", "start": 15967742, - "end": 26167742, - "pointLabels": { - "0": "testlabel", - "2": "label2" - } + "end": 26167742 }, "parameters": [ { @@ -228,8 +228,6 @@ { "meta": { "type": "point", - "classId": 1645591, - "className": "class3", "createdBy": { "email": "arturn@superannotate.com", "role": "Admin" @@ -240,6 +238,8 @@ "role": "Admin" }, "updatedAt": "2022-07-04T10:49:10.454Z", + "classId": 1645591, + "className": "class3", "start": 2154547, "end": 3836690 }, @@ -273,8 +273,6 @@ { "meta": { "type": "event", - "classId": 1645591, - "className": "class3", "createdBy": { "email": "arturn@superannotate.com", "role": "Admin" @@ -285,6 +283,8 @@ "role": "Admin" }, "updatedAt": "2022-07-04T10:49:10.455Z", + "classId": 1645591, + "className": "class3", "start": 5131014, "end": 6517905 }, @@ -308,8 +308,6 @@ { "meta": { "type": "polygon", - "classId": 1820067, - "className": "tree", "createdBy": { "email": "arturn@superannotate.com", "role": "Admin" @@ -320,6 +318,8 @@ "role": "Admin" }, "updatedAt": "2022-10-18T11:21:20.267Z", + "classId": 1820067, + "className": "tree", "start": 11704101, "end": 19463671 }, @@ -404,8 +404,6 @@ { "meta": { "type": "polyline", - "classId": 1820067, - "className": "tree", "createdBy": { "email": "arturn@superannotate.com", "role": "Admin" @@ -416,6 +414,8 @@ "role": "Admin" }, "updatedAt": "2022-10-18T11:23:40.500Z", + "classId": 1820067, + "className": "tree", "start": 8616210, "end": 12391405 }, @@ -500,8 +500,6 @@ { "meta": { "type": "point", - "classId": 1820067, - "className": "tree", "createdBy": { "email": "arturn@superannotate.com", "role": "Admin" @@ -512,6 +510,8 @@ "role": "Admin" }, "updatedAt": "2022-11-09T14:55:50.645Z", + "classId": 1820067, + "className": "tree", "start": 28737304, "end": 45725853 }, @@ -630,8 +630,6 @@ { "meta": { "type": "tag", - "classId": 1645589, - "className": "class1", "createdBy": { "email": "arturn@superannotate.com", "role": "Admin" @@ -642,6 +640,8 @@ "role": "Admin" }, "updatedAt": "2022-08-10T11:54:37.707Z", + "classId": 1645589, + "className": "class1", "attributes": [ { "id": 4396132, @@ -655,8 +655,6 @@ { "meta": { "type": "tag", - "classId": 2012407, - "className": "class_tag", "createdBy": { "email": "arturn@superannotate.com", "role": "Admin" @@ -667,15 +665,17 @@ "role": "Admin" }, "updatedAt": "2022-11-23T07:51:53.052Z", + "classId": 2012407, + "className": "class_tag", "attributes": [ { + "name": 3, "groupId": 3077618, - "name": "", "groupName": "numeric_group_tag" }, { + "name": "text_tag", "groupId": 3077617, - "name": "", "groupName": "text_group_tag" }, { @@ -706,8 +706,7 @@ "role": "Admin" }, "updatedAt": "2022-11-09T14:26:35.123Z", - "start": 9801951, - "end": 12240000, + "resolved": true, "correspondence": [ { "email": "arturn@superannotate.com", @@ -722,7 +721,8 @@ "createdAt": "2022-11-09T14:26:32.861Z" } ], - "resolved": true + "start": 9801951, + "end": 12240000 }, "parameters": [ { @@ -773,8 +773,7 @@ "role": "Admin" }, "updatedAt": "2022-11-09T13:30:49.326Z", - "start": 24231219, - "end": 30152195, + "resolved": false, "correspondence": [ { "email": "arturn@superannotate.com", @@ -783,7 +782,8 @@ "createdAt": "2022-11-09T13:30:26.194Z" } ], - "resolved": true + "start": 24231219, + "end": 30152195 }, "parameters": [ { diff --git a/tests/integration/annotations/video/test_video_annotation_upload.py b/tests/integration/annotations/video/test_video_annotation_upload.py index 958183b5e..eaf43572b 100644 --- a/tests/integration/annotations/video/test_video_annotation_upload.py +++ b/tests/integration/annotations/video/test_video_annotation_upload.py @@ -1,61 +1,52 @@ import os import tempfile -from pathlib import Path import pytest from src.superannotate import SAClient +from tests import DATA_SET_PATH from tests.integration.base import BaseTestCase - sa = SAClient() class TestUploadVideoAnnotation(BaseTestCase): PROJECT_NAME = "video annotation upload" - PATH_TO_URLS = "data_set/attach_video_for_annotation.csv" + PATH_TO_URLS = "attach_video_for_annotation.csv" PROJECT_DESCRIPTION = "desc" PROJECT_TYPE = "Video" - ANNOTATIONS_PATH = "data_set/video_annotation" - ANNOTATIONS_WITHOUT_CLASSES_PATH = "data_set/annotations" - CLASSES_PATH = "data_set/video_annotation/classes/classes.json" - ANNOTATIONS_PATH_INVALID_JSON = "data_set/video_annotation_invalid_json" - MINIMAL_ANNOTATION_PATH = "data_set/video_annotation_minimal_fields" - MINIMAL_ANNOTATION_TRUTH_PATH = "data_set/minimal_video_annotation_truth" + ANNOTATIONS_PATH = "video_annotation" + ANNOTATIONS_WITHOUT_CLASSES_PATH = "annotations" + CLASSES_PATH = "video_annotation/classes/classes.json" + ANNOTATIONS_PATH_INVALID_JSON = "video_annotation_invalid_json" + MINIMAL_ANNOTATION_PATH = "video_annotation_minimal_fields" + MINIMAL_ANNOTATION_TRUTH_PATH = "minimal_video_annotation_truth" maxDiff = None @property def minimal_annotation_truth_path(self): - return os.path.join(self.folder_path, self.MINIMAL_ANNOTATION_TRUTH_PATH) - - @property - def folder_path(self): - return Path(__file__).parent.parent.parent + return os.path.join(DATA_SET_PATH, self.MINIMAL_ANNOTATION_TRUTH_PATH) @property def csv_path(self): - return os.path.join(self.folder_path, self.PATH_TO_URLS) + return os.path.join(DATA_SET_PATH, self.PATH_TO_URLS) @property def annotations_path(self): - return os.path.join(self.folder_path, self.ANNOTATIONS_PATH) - - @property - def minimal_annotations_path(self): - return os.path.join(self.folder_path, self.MINIMAL_ANNOTATION_PATH) + return os.path.join(DATA_SET_PATH, self.ANNOTATIONS_PATH) @property def annotations_without_classes(self): - return os.path.join(self.folder_path, self.ANNOTATIONS_WITHOUT_CLASSES_PATH) + return os.path.join(DATA_SET_PATH, self.ANNOTATIONS_WITHOUT_CLASSES_PATH) @property def invalid_annotations_path(self): - return os.path.join(self.folder_path, self.ANNOTATIONS_PATH_INVALID_JSON) + return os.path.join(DATA_SET_PATH, self.ANNOTATIONS_PATH_INVALID_JSON) @property def classes_path(self): - return os.path.join(self.folder_path, self.CLASSES_PATH) + return os.path.join(DATA_SET_PATH, self.CLASSES_PATH) @pytest.fixture(autouse=True) def inject_fixtures(self, caplog): diff --git a/tests/integration/annotations/video/test_video_free_text_numeric.py b/tests/integration/annotations/video/test_video_free_text_numeric.py index bc7e22da4..2ad18b5cb 100644 --- a/tests/integration/annotations/video/test_video_free_text_numeric.py +++ b/tests/integration/annotations/video/test_video_free_text_numeric.py @@ -1,3 +1,4 @@ +import json import os from tests import DATA_SET_PATH @@ -27,6 +28,9 @@ def test_upload(self): ) (uploaded_annotations, failed_annotations, missing_annotations) = sa.upload_annotations_from_folder_to_project( self.PROJECT_NAME, self.folder_path) - self.assertEqual(len(uploaded_annotations), 0) - self.assertEqual(len(failed_annotations), 1) + self.assertEqual(len(uploaded_annotations), 1) + self.assertEqual(len(failed_annotations), 0) self.assertEqual(len(missing_annotations), 0) + annotation = sa.get_annotations(self.PROJECT_NAME)[0] + local_annotation = json.load(open(os.path.join(self.folder_path, (self.ITEM_NAME + ".json")))) + assert len(annotation['instances']) == len(local_annotation['instances']) \ No newline at end of file diff --git a/tests/integration/folders/test_folders.py b/tests/integration/folders/test_folders.py index 7c57d5d3b..3ff5c553d 100644 --- a/tests/integration/folders/test_folders.py +++ b/tests/integration/folders/test_folders.py @@ -270,5 +270,5 @@ def test_search_folder(self): sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_2) folders = sa.search_folders(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1, return_metadata=True) assert len(folders) == 1 - assert folders[0] == self.TEST_FOLDER_NAME_1 + assert folders[0]['name'] == self.TEST_FOLDER_NAME_1 assert folders[0]['status'] == 'NotStarted' \ No newline at end of file diff --git a/tests/integration/test_validate_upload_state.py b/tests/integration/test_validate_upload_state.py deleted file mode 100644 index e69de29bb..000000000 From 05ab14157a7cec4341cb0eca4ca46b6d394c1de8 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 24 Nov 2022 16:28:16 +0400 Subject: [PATCH 03/23] version update --- src/superannotate/__init__.py | 2 +- src/superannotate/lib/app/interface/sdk_interface.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index 994ec5292..2333d860c 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -2,7 +2,7 @@ import sys -__version__ = "4.4.6dev4" +__version__ = "4.4.7dev1" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index 3762cb21c..527649745 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -327,7 +327,9 @@ def create_folder(self, project: NotEmptyStr, folder_name: NotEmptyStr): if res.data: folder = res.data logger.info(f"Folder {folder.name} created in project {project.name}") - return folder.dict() + return FolderSerializer(folder).serialize( + exclude={"completedCount", "is_root"} + ) if res.errors: raise AppException(res.errors) From aa5e4b70e7fbfc44abb48ebfa53a9471694b5bb5 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Fri, 25 Nov 2022 18:01:29 +0400 Subject: [PATCH 04/23] updated search folders --- .../lib/app/interface/sdk_interface.py | 7 +- src/superannotate/lib/core/__init__.py | 2 + src/superannotate/lib/core/conditions.py | 40 ++++--- src/superannotate/lib/core/enums.py | 7 +- .../lib/infrastructure/services/folder.py | 14 +-- .../invalid_annotations/video.mp4.json | 112 ++++++++++++++++++ .../test_video_annotation_validation.py | 23 ++++ .../video/test_get_annotations_per_frame.py | 18 ++- tests/integration/folders/test_folders.py | 8 +- tests/unit/test_conditions.py | 14 +++ 10 files changed, 202 insertions(+), 43 deletions(-) create mode 100644 tests/data_set/invalid_annotations/video.mp4.json create mode 100644 tests/integration/annotations/validations/test_video_annotation_validation.py diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index 527649745..ead50f8ca 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -328,8 +328,8 @@ def create_folder(self, project: NotEmptyStr, folder_name: NotEmptyStr): folder = res.data logger.info(f"Folder {folder.name} created in project {project.name}") return FolderSerializer(folder).serialize( - exclude={"completedCount", "is_root"} - ) + exclude={"completedCount", "is_root"} + ) if res.errors: raise AppException(res.errors) @@ -432,7 +432,7 @@ def search_folders( condition &= Condition("includeUsers", return_metadata, EQ) if status: condition &= Condition( - "status", constants.ProjectStatus.get_value(status), EQ + "status", constants.FolderStatus.get_value(status), EQ ) response = self.controller.folders.list(project, condition) if response.errors: @@ -539,6 +539,7 @@ def search_annotation_classes( :param project: project name :type project: str + :param name_contains: search string. Returns those classes, where the given string is found anywhere within its name. If None, all annotation classes will be returned. :type name_contains: str diff --git a/src/superannotate/lib/core/__init__.py b/src/superannotate/lib/core/__init__.py index c6415d8a6..f99fd1637 100644 --- a/src/superannotate/lib/core/__init__.py +++ b/src/superannotate/lib/core/__init__.py @@ -2,6 +2,7 @@ from superannotate.lib.core.config import Config from superannotate.lib.core.enums import AnnotationStatus +from superannotate.lib.core.enums import FolderStatus from superannotate.lib.core.enums import ImageQuality from superannotate.lib.core.enums import ProjectStatus from superannotate.lib.core.enums import ProjectType @@ -105,6 +106,7 @@ INVALID_JSON_MESSAGE = "Invalid json" __alL__ = ( + FolderStatus, ProjectStatus, ProjectType, UserRole, diff --git a/src/superannotate/lib/core/conditions.py b/src/superannotate/lib/core/conditions.py index 9034d12e1..caa5e9f3f 100644 --- a/src/superannotate/lib/core/conditions.py +++ b/src/superannotate/lib/core/conditions.py @@ -1,7 +1,6 @@ from collections import namedtuple from typing import Any from typing import List -from typing import NamedTuple CONDITION_OR = "|" CONDITION_AND = "&" @@ -11,7 +10,7 @@ CONDITION_LT = "<" CONDITION_LE = "<=" -QueryCondition = namedtuple("QueryCondition", ("condition", "query", "pair")) +QueryCondition = namedtuple("QueryCondition", ("condition", "pair", "item")) class Condition: @@ -19,7 +18,9 @@ def __init__(self, key: str, value: Any, condition_type: str): self._key = key self._value = value self._type = condition_type - self._condition_set = [] # type: List[NamedTuple] + self._condition_set: List[QueryCondition] = [ + QueryCondition(CONDITION_AND, {key: value}, self) + ] @staticmethod def get_empty_condition(): @@ -32,13 +33,10 @@ def __or__(self, other): if not isinstance(other, Condition): raise Exception("Support the only Condition types") - self._condition_set.append( - QueryCondition( - CONDITION_OR, - other.build_query(), - {other._key: other._value} if type(other) == Condition else {}, + for _condition in other._condition_set: + self._condition_set.append( + QueryCondition(CONDITION_OR, _condition.pair, _condition.item) ) - ) return self def __and__(self, other): @@ -52,19 +50,23 @@ def __and__(self, other): elif not isinstance(other, (Condition, EmptyCondition)): raise Exception("Support the only Condition types") - self._condition_set.append( - QueryCondition( - CONDITION_AND, - other.build_query(), - {other._key: other._value} if type(other) == Condition else {}, + for _condition in other._condition_set: + self._condition_set.append( + QueryCondition(CONDITION_AND, _condition.pair, _condition.item) ) - ) return self + def _build(self): + return f"{self._key}{self._type}{self._value}" + def build_query(self): - return str(self) + "".join( - [f"{condition[0]}{condition[1]}" for condition in self._condition_set] - ) + items = [] + for condition in self._condition_set: + if not items: + items.append(condition.item._build()) + else: + items.extend([condition.condition, condition.item._build()]) + return "".join(items) def get_as_params_dict(self) -> dict: params = None if isinstance(self, EmptyCondition) else {self._key: self._value} @@ -75,7 +77,7 @@ def get_as_params_dict(self) -> dict: class EmptyCondition(Condition): def __init__(self, *args, **kwargs): # noqa - ... + self._condition_set = [] def __or__(self, other): return other diff --git a/src/superannotate/lib/core/enums.py b/src/superannotate/lib/core/enums.py index 8e0c7407f..8a943ffd5 100644 --- a/src/superannotate/lib/core/enums.py +++ b/src/superannotate/lib/core/enums.py @@ -41,6 +41,9 @@ def get_name(cls, value): def get_value(cls, name): for enum in list(cls): if enum.__doc__.lower() == name.lower(): + if isinstance(enum.value, int): + if enum.value < 0: + return "" return enum.value @classmethod @@ -101,7 +104,7 @@ class ImageQuality(BaseTitledEnum): class ProjectStatus(BaseTitledEnum): - Undefined = "Undefined", 0 + Undefined = "Undefined", -1 NotStarted = "NotStarted", 1 InProgress = "InProgress", 2 Completed = "Completed", 3 @@ -109,7 +112,7 @@ class ProjectStatus(BaseTitledEnum): class FolderStatus(BaseTitledEnum): - Undefined = "Undefined", 0 + Undefined = "Undefined", -1 NotStarted = "NotStarted", 1 InProgress = "InProgress", 2 Completed = "Completed", 3 diff --git a/src/superannotate/lib/infrastructure/services/folder.py b/src/superannotate/lib/infrastructure/services/folder.py index 47cf3a582..f4c7ad7f7 100644 --- a/src/superannotate/lib/infrastructure/services/folder.py +++ b/src/superannotate/lib/infrastructure/services/folder.py @@ -8,7 +8,7 @@ class FolderService(BaseFolderService): URL_BASE = "folder" - URL_LIST = "folders" + URL_LIST = "/folders" URL_UPDATE = "folder/{}" URL_GET_BY_NAME = "folder/getFolderByName" URL_DELETE_MULTIPLE = "image/delete/images" @@ -34,6 +34,12 @@ def list(self, condition: Condition = None): query_params=condition.get_as_params_dict() if condition else None, ) + def update(self, project: entities.ProjectEntity, folder: entities.FolderEntity): + params = {"project_id": project.id} + return self.client.request( + self.URL_UPDATE.format(folder.id), "put", data=folder.dict(), params=params + ) + def delete_multiple( self, project: entities.ProjectEntity, folders: List[entities.FolderEntity] ): @@ -69,9 +75,3 @@ def assign( params={"project_id": project.id}, data={"folder_name": folder.name, "assign_user_ids": users}, ) - - def update(self, project: entities.ProjectEntity, folder: entities.FolderEntity): - params = {"project_id": project.id} - return self.client.request( - self.URL_UPDATE.format(folder.id), "put", data=folder.dict(), params=params - ) diff --git a/tests/data_set/invalid_annotations/video.mp4.json b/tests/data_set/invalid_annotations/video.mp4.json new file mode 100644 index 000000000..455f2af46 --- /dev/null +++ b/tests/data_set/invalid_annotations/video.mp4.json @@ -0,0 +1,112 @@ +{ + "metadata": { + "width": "1920", + "height": 10.80, + "status": "InProgress", + "url": "https://drive.google.com/uc?export=download&id=1wTd4NeE3Jt39k9DL_5GIqrO6R_hucsps", + "duration": 30571000, + "projectId": 165611, + "error": false, + "annotatorEmail": "A34@3.4@htfd.cm", + "qaEmail": "43@fd.dcs", + "lastAction": { + "timestamp": 1635510590705, + "email": "varduhi@superannotate.com" + } + }, + "instances": [ + { + "meta": { + "type": "bbox", + "classId": 879460, + "pointLabels": { + "ds": "s" + }, + "createdBy": { + "email": "varduhi@superannotate.com", + "role": "Admin" + }, + "createdAt": "2021-10-29T12.29.45.668Z", + "updatedBy": { + "email": "varduhi@superannotate.com", + "role": "qwert" + }, + "updatedAt": "2021-10-29T12:29:50.696Z", + "start": 0, + "end": 30571000 + }, + "parameters": [ + { + "start": 0, + "end": 30571000, + "timestamps": [ + { + "points": { + "x": 595.85, + "y1": 223.82, + "x2": 1278.92, + "y2": 874.41 + }, + "timestamp": 0, + "attributes": [ + { + } + ] + }, + { + "points": { + "x1": 595.85, + "y1": 223.82, + "x2": 1278.92, + "y2": 874.41 + }, + "timestamp": 30571000, + "attributes": [ + { + }, + { + "groupName": "numeric group" + } + ] + } + ] + } + ] + }, + { + "meta": { + "type": "bbox", + "start": 0, + "end": 30571000 + }, + "parameters": [ + { + "start": 0, + "end": 3057.1000, + "timestamps": [ + { + "points": { + "x": 595.85, + "y1": 223.82, + "x2": 1278.92, + "y2": 874.41 + }, + "timestamp": 0, + "attributes": [ + { + }, + { + "groupName": "numeric group" + } + ] + } + ] + } + ] + } + ], + "tags": [ + 6, + null + ] +} diff --git a/tests/integration/annotations/validations/test_video_annotation_validation.py b/tests/integration/annotations/validations/test_video_annotation_validation.py new file mode 100644 index 000000000..f50274f36 --- /dev/null +++ b/tests/integration/annotations/validations/test_video_annotation_validation.py @@ -0,0 +1,23 @@ +import os +import json +from unittest import TestCase +from unittest.mock import patch + +from src.superannotate import SAClient +from tests import DATA_SET_PATH + +sa = SAClient() + + +class TestVectorValidators(TestCase): + PROJECT_NAME = "video annotation upload with ree text and numeric" + PROJECT_DESCRIPTION = "desc" + PROJECT_TYPE = "Video" + ANNOTATIONS_PATH = "invalid_annotations/video.mp4.json" + + # @patch('builtins.print') + def test_free_text_numeric_invalid(self): + json_data = json.load(open(os.path.join(DATA_SET_PATH, self.ANNOTATIONS_PATH))) + is_valid = sa.validate_annotations("video", json_data) + assert not is_valid + diff --git a/tests/integration/annotations/video/test_get_annotations_per_frame.py b/tests/integration/annotations/video/test_get_annotations_per_frame.py index 18be86750..ba68baea1 100644 --- a/tests/integration/annotations/video/test_get_annotations_per_frame.py +++ b/tests/integration/annotations/video/test_get_annotations_per_frame.py @@ -6,19 +6,20 @@ from src.superannotate import SAClient from tests.integration.base import BaseTestCase +from tests import DATA_SET_PATH sa = SAClient() class TestGetAnnotations(BaseTestCase): PROJECT_NAME = "test attach video urls" - PATH_TO_URLS = "data_set/attach_video_for_annotation.csv" - PATH_TO_URLS_WITHOUT_NAMES = "data_set/attach_urls_with_no_name.csv" - PATH_TO_50K_URLS = "data_set/501_urls.csv" + PATH_TO_URLS = "attach_video_for_annotation.csv" + PATH_TO_URLS_WITHOUT_NAMES = "attach_urls_with_no_name.csv" + PATH_TO_50K_URLS = "501_urls.csv" PROJECT_DESCRIPTION = "desc" - ANNOTATIONS_PATH = "data_set/video_convertor_annotations" + ANNOTATIONS_PATH = "video_convertor_annotations" VIDEO_NAME = "video.mp4" - CLASSES_PATH = "data_set/video_annotation/classes/classes.json" + CLASSES_PATH = "video_annotation/classes/classes.json" PROJECT_TYPE = "Video" @property @@ -27,15 +28,12 @@ def csv_path(self): @property def classes_path(self): - return os.path.join(self.folder_path, self.CLASSES_PATH) + return os.path.join(DATA_SET_PATH, self.CLASSES_PATH) - @property - def folder_path(self): - return Path(__file__).parent.parent.parent @property def annotations_path(self): - return os.path.join(self.folder_path, self.ANNOTATIONS_PATH) + return os.path.join(DATA_SET_PATH, self.ANNOTATIONS_PATH) def test_video_annotation_upload(self): sa.create_annotation_classes_from_classes_json(self.PROJECT_NAME, self.classes_path) diff --git a/tests/integration/folders/test_folders.py b/tests/integration/folders/test_folders.py index 3ff5c553d..9e2ec03ad 100644 --- a/tests/integration/folders/test_folders.py +++ b/tests/integration/folders/test_folders.py @@ -267,8 +267,12 @@ def test_create_long_name(self): def test_search_folder(self): sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1) - sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_2) + time.sleep(1) folders = sa.search_folders(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1, return_metadata=True) assert len(folders) == 1 assert folders[0]['name'] == self.TEST_FOLDER_NAME_1 - assert folders[0]['status'] == 'NotStarted' \ No newline at end of file + assert folders[0]['status'] == 'NotStarted' + folders = sa.search_folders(self.PROJECT_NAME, status='Completed', return_metadata=True) + assert len(folders) == 0 + folders = sa.search_folders(self.PROJECT_NAME, status='Undefined', return_metadata=True) + assert len(folders) == 0 \ No newline at end of file diff --git a/tests/unit/test_conditions.py b/tests/unit/test_conditions.py index 709260a6d..26d1fcd4e 100644 --- a/tests/unit/test_conditions.py +++ b/tests/unit/test_conditions.py @@ -1,6 +1,7 @@ from unittest import TestCase from src.superannotate.lib.core.conditions import Condition +import src.superannotate.lib.core as constants from src.superannotate.lib.core.conditions import CONDITION_EQ from src.superannotate.lib.core.conditions import CONDITION_GE @@ -18,3 +19,16 @@ def test_multiple_condition_query_build_from_tuple(self): condition = Condition("id", 1, CONDITION_EQ) | Condition("id", 2, CONDITION_GE) condition &= (Condition("id", 5, CONDITION_EQ) & Condition("id", 7, CONDITION_EQ)) self.assertEquals(condition.build_query(), "id=1|id>=2&id=5&id=7") + + def test_(self): + folder_name = "name" + status = "NotStarted" + return_metadata = True + condition = Condition("name", folder_name, CONDITION_EQ) + condition &= Condition("includeUsers", return_metadata, CONDITION_EQ) + _condition = Condition( + "status", constants.ProjectStatus.get_value(status), CONDITION_EQ + ) + _condition &= condition + assert _condition.get_as_params_dict() == {'status': 1, 'name': 'name', 'includeUsers': True} + From b923c04fcdf3adad590db24ba09a5b0f28fa6929 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Fri, 25 Nov 2022 18:24:36 +0400 Subject: [PATCH 05/23] Tests update --- .../test_upload_annotations_from_folder_to_project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 59c99d566..252d46804 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" rst-lintaearth_mov_00{i}.jpg", "url": f"url_{i}"} for i in range(1, 6)] # noqa + [{"name": f"aearth_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 ee56c1278108e95626cd157f099f42ac49a83861 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Mon, 28 Nov 2022 11:42:46 +0400 Subject: [PATCH 06/23] Added folder search tests --- tests/integration/folders/test_folders.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/integration/folders/test_folders.py b/tests/integration/folders/test_folders.py index 9e2ec03ad..1fa3be6fc 100644 --- a/tests/integration/folders/test_folders.py +++ b/tests/integration/folders/test_folders.py @@ -272,7 +272,13 @@ def test_search_folder(self): assert len(folders) == 1 assert folders[0]['name'] == self.TEST_FOLDER_NAME_1 assert folders[0]['status'] == 'NotStarted' + sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1 + "1") + sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1 + "2") + folders = sa.search_folders(self.PROJECT_NAME, return_metadata=True) + assert len(folders) == 3 folders = sa.search_folders(self.PROJECT_NAME, status='Completed', return_metadata=True) assert len(folders) == 0 folders = sa.search_folders(self.PROJECT_NAME, status='Undefined', return_metadata=True) - assert len(folders) == 0 \ No newline at end of file + assert len(folders) == 0 + folders = sa.search_folders(self.PROJECT_NAME, status='NotStarted', return_metadata=True) + assert len(folders) == 3 \ No newline at end of file From 553ce56512a705dd92eebfc7f68589810a99e400 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Mon, 28 Nov 2022 11:43:17 +0400 Subject: [PATCH 07/23] version update --- 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 2333d860c..886bc0bb9 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -2,7 +2,7 @@ import sys -__version__ = "4.4.7dev1" +__version__ = "4.4.7dev2" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From 897865f6873ee4039d5915b0b3d20dae535dd1bd Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Mon, 28 Nov 2022 16:13:40 +0400 Subject: [PATCH 08/23] docstring update --- src/superannotate/lib/app/analytics/common.py | 8 ++++---- src/superannotate/lib/app/interface/sdk_interface.py | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/superannotate/lib/app/analytics/common.py b/src/superannotate/lib/app/analytics/common.py index eff05837e..e2a8e63f9 100644 --- a/src/superannotate/lib/app/analytics/common.py +++ b/src/superannotate/lib/app/analytics/common.py @@ -383,9 +383,9 @@ def instance_consensus(inst_1, inst_2): :param inst_1: First instance for consensus score. :type inst_1: shapely object + :param inst_2: Second instance for consensus score. :type inst_2: shapely object - """ if inst_1.type == inst_2.type == "Polygon": intersect = inst_1.intersection(inst_2) @@ -404,19 +404,19 @@ def image_consensus(df, image_name, annot_type): :param df: Annotation data of all images :type df: pandas.DataFrame + :param image_name: The image name for which the consensus score will be computed :type image_name: str + :param annot_type: Type of annotation instances to consider. Available candidates are: ["bbox", "polygon", "point"] :type dataset_format: str - """ try: from shapely.geometry import Point, Polygon, box except ImportError: raise ImportError( - "To use superannotate.benchmark or superannotate.consensus functions please install " - "shapely package in Anaconda enviornment with # conda install shapely" + "To use superannotate.benchmark or superannotate.consensus functions please install shapely package." ) image_df = df[df["imageName"] == image_name] diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index ead50f8ca..fe3708ed7 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -2176,6 +2176,8 @@ def attach_items_from_integrated_storage( """ project, folder = self.controller.get_project_folder_by_path(project) _integration = None + if isinstance(integration, str): + integration = IntegrationEntity(name=integration) for i in self.controller.integrations.list().data: if integration == i.name: _integration = i From 7f7826aae86d7fe8122e30f463651da5b30d08f1 Mon Sep 17 00:00:00 2001 From: VavoTK Date: Mon, 28 Nov 2022 17:22:55 +0400 Subject: [PATCH 09/23] fixing __objects.json and freestyle attributes --- src/superannotate/lib/app/analytics/common.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/superannotate/lib/app/analytics/common.py b/src/superannotate/lib/app/analytics/common.py index eff05837e..0db7cf415 100644 --- a/src/superannotate/lib/app/analytics/common.py +++ b/src/superannotate/lib/app/analytics/common.py @@ -4,7 +4,6 @@ import pandas as pd import plotly.express as px from lib.app.exceptions import AppException -from lib.core import DEPRICATED_DOCUMENT_VIDEO_MESSAGE from superannotate.logger import get_default_logger @@ -44,14 +43,6 @@ def aggregate_image_annotations_as_df( :rtype: pandas DataFrame """ - json_paths = list(Path(str(project_root)).glob("*.json")) - if ( - json_paths - and "___pixel.json" not in json_paths[0].name - and "___objects.json" not in json_paths[0].name - ): - raise AppException(DEPRICATED_DOCUMENT_VIDEO_MESSAGE) - logger.info("Aggregating annotations from %s as pandas DataFrame", project_root) annotation_data = { @@ -101,12 +92,15 @@ def aggregate_image_annotations_as_df( classes_json = json.load(open(classes_path)) class_name_to_color = {} class_group_name_to_values = {} + freestyle_attributes = set() for annotation_class in classes_json: name = annotation_class["name"] color = annotation_class["color"] class_name_to_color[name] = color class_group_name_to_values[name] = {} for attribute_group in annotation_class["attribute_groups"]: + if attribute_group["group_type"] in ["text", "numeric"]: + freestyle_attributes.add(attribute_group["name"]) class_group_name_to_values[name][attribute_group["name"]] = [] for attribute in attribute_group["attributes"]: class_group_name_to_values[name][attribute_group["name"]].append( @@ -175,10 +169,14 @@ def __get_user_metadata(annotation): if not annotations_paths: logger.warning(f"Could not find annotations in {project_root}.") - if len(list(Path(project_root).rglob("*___objects.json"))) > 0: + + if "___objects.json" in annotations_paths[0].name: type_postfix = "___objects.json" - else: + elif "___pixel.json" in annotations_paths[0].name: type_postfix = "___pixel.json" + else: + type_postfix = ".json" + for annotation_path in annotations_paths: annotation_json = json.load(open(annotation_path)) parts = annotation_path.name.split(type_postfix) @@ -294,6 +292,7 @@ def __get_user_metadata(annotation): not in class_group_name_to_values[annotation_class_name][ attribute_group ] + and attribute_group not in freestyle_attributes ): logger.warning( "Annotation class group value %s not in classes json. Skipping.", From ac3e13a52b2aa285ba15b40a0328ecc3278b4524 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Tue, 29 Nov 2022 11:43:25 +0400 Subject: [PATCH 10/23] Test fix --- src/superannotate/__init__.py | 5 +---- src/superannotate/lib/app/analytics/common.py | 3 ++- .../video/test_get_annotations_per_frame.py | 3 +-- .../classes/test_create_annotation_class.py | 15 --------------- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index aa81c01dc..f829c2740 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -1,10 +1,7 @@ import os import sys - - -__version__ = "4.4.7dev2" - +__version__ = "4.4.7dev3" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) diff --git a/src/superannotate/lib/app/analytics/common.py b/src/superannotate/lib/app/analytics/common.py index e491a1609..4f1877658 100644 --- a/src/superannotate/lib/app/analytics/common.py +++ b/src/superannotate/lib/app/analytics/common.py @@ -99,7 +99,8 @@ def aggregate_image_annotations_as_df( class_name_to_color[name] = color class_group_name_to_values[name] = {} for attribute_group in annotation_class["attribute_groups"]: - if attribute_group["group_type"] in ["text", "numeric"]: + group_type = attribute_group.get("group_type") + if group_type and group_type in ["text", "numeric"]: freestyle_attributes.add(attribute_group["name"]) class_group_name_to_values[name][attribute_group["name"]] = [] for attribute in attribute_group["attributes"]: diff --git a/tests/integration/annotations/video/test_get_annotations_per_frame.py b/tests/integration/annotations/video/test_get_annotations_per_frame.py index ba68baea1..9c1731b79 100644 --- a/tests/integration/annotations/video/test_get_annotations_per_frame.py +++ b/tests/integration/annotations/video/test_get_annotations_per_frame.py @@ -2,7 +2,6 @@ import math import os from os.path import dirname -from pathlib import Path from src.superannotate import SAClient from tests.integration.base import BaseTestCase @@ -24,7 +23,7 @@ class TestGetAnnotations(BaseTestCase): @property def csv_path(self): - return os.path.join(dirname(dirname(dirname(__file__))), self.PATH_TO_URLS) + return os.path.join(DATA_SET_PATH, self.PATH_TO_URLS) @property def classes_path(self): diff --git a/tests/integration/classes/test_create_annotation_class.py b/tests/integration/classes/test_create_annotation_class.py index b91a4ad3b..2e23a68a1 100644 --- a/tests/integration/classes/test_create_annotation_class.py +++ b/tests/integration/classes/test_create_annotation_class.py @@ -146,21 +146,6 @@ def test_create_annotation_class(self): msg = str(e) self.assertEqual(msg, "Predefined tagging functionality is not supported for projects of type Video.") - def test_create_supported_annotation_class(self): - msg = "" - try: - sa.create_annotation_class( - self.PROJECT_NAME, "test_add", "#FF0000", - attribute_groups=[ - { - "group_type": "text", - "name": "name", - } - ] - ) - except Exception as e: - msg = str(e) - self.assertEqual(msg, "This project type doesn't support the attribute group types 'text' and 'numeric'.") def test_create_radio_annotation_class_attr_required(self): msg = "" From fe31dc32e16aa69189b0c3390fede371ade9d227 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Wed, 30 Nov 2022 11:21:27 +0400 Subject: [PATCH 11/23] Fix integrations search --- src/superannotate/lib/app/interface/sdk_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index fe3708ed7..55db1ea84 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -2179,7 +2179,7 @@ def attach_items_from_integrated_storage( if isinstance(integration, str): integration = IntegrationEntity(name=integration) for i in self.controller.integrations.list().data: - if integration == i.name: + if integration.name == i.name: _integration = i break else: From 853e09e6924960d3a0af043f014b0c8830195f23 Mon Sep 17 00:00:00 2001 From: VavoTK Date: Wed, 30 Nov 2022 15:55:39 +0400 Subject: [PATCH 12/23] replacing forbidden chars --- src/superannotate/lib/core/usecases/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/superannotate/lib/core/usecases/models.py b/src/superannotate/lib/core/usecases/models.py index 1d3f84c35..041830b1d 100644 --- a/src/superannotate/lib/core/usecases/models.py +++ b/src/superannotate/lib/core/usecases/models.py @@ -140,6 +140,7 @@ def execute(self): class DownloadExportUseCase(BaseReportableUseCase): + FORBIDDEN_CHARS = "*./\[]:;|,\"\'" def __init__( self, service_provider: BaseServiceProvider, @@ -205,6 +206,8 @@ def download_to_local_storage(self, destination: str, extract_zip=False): time.sleep(1) self.reporter.stop_spinner() filename = Path(export["path"]).name + for char in DownloadExportUseCase.FORBIDDEN_CHARS: + filename.replace(char, "_") filepath = Path(destination) / filename with requests.get(export["download"], stream=True) as response: response.raise_for_status() From 317191684e851ce93da872d4951a73b6a4fd5f54 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Wed, 30 Nov 2022 17:31:20 +0400 Subject: [PATCH 13/23] Added subsets tests --- src/superannotate/__init__.py | 2 +- src/superannotate/lib/core/usecases/items.py | 9 +++---- tests/integration/subsets/test_subsets.py | 28 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 tests/integration/subsets/test_subsets.py diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index f829c2740..1d94a3026 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -1,7 +1,7 @@ import os import sys -__version__ = "4.4.7dev3" +__version__ = "4.4.7dev4" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) diff --git a/src/superannotate/lib/core/usecases/items.py b/src/superannotate/lib/core/usecases/items.py index 978456d74..c88e5a9ed 100644 --- a/src/superannotate/lib/core/usecases/items.py +++ b/src/superannotate/lib/core/usecases/items.py @@ -1,4 +1,5 @@ import copy +import traceback from collections import defaultdict from concurrent.futures import as_completed from concurrent.futures import ThreadPoolExecutor @@ -942,12 +943,10 @@ def execute( for future in as_completed(futures): try: ids = future.result() - except Exception as e: - continue - - self.item_ids.extend(ids) + self.item_ids.extend(ids) + except Exception: + logger.debug(traceback.format_exc()) - futures = [] subsets = self._service_provider.subsets.list(self.project).data subset = None for s in subsets: diff --git a/tests/integration/subsets/test_subsets.py b/tests/integration/subsets/test_subsets.py new file mode 100644 index 000000000..3dbc31afb --- /dev/null +++ b/tests/integration/subsets/test_subsets.py @@ -0,0 +1,28 @@ +from src.superannotate import SAClient + +from tests.integration.base import BaseTestCase + +sa = SAClient() + + +class TestSubSets(BaseTestCase): + PROJECT_NAME = "Test-TestSubSets" + PROJECT_DESCRIPTION = "Desc" + PROJECT_TYPE = "Vector" + SUBSET_NAME = "SUBSET" + + def test_add_items_to_subset(self): + item_names = [{"name": f"earth_mov_00{i}.jpg", "url": f"url_{i}"} for i in range(1, 6)] + sa.attach_items( + self.PROJECT_NAME, + item_names # noqa + ) + subset_data = [] + for i in item_names: + subset_data.append( + { + "name": i['name'], + "path": self.PROJECT_NAME + } + ) + sa.add_items_to_subset(self.PROJECT_NAME, self.SUBSET_NAME, subset_data) From e12c8cdb2cb9e2fe72e1ab91fd814742cd03a83b Mon Sep 17 00:00:00 2001 From: VavoTK Date: Thu, 1 Dec 2022 11:55:43 +0400 Subject: [PATCH 14/23] repace is not inplace --- src/superannotate/lib/core/usecases/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/superannotate/lib/core/usecases/models.py b/src/superannotate/lib/core/usecases/models.py index 041830b1d..06149adf6 100644 --- a/src/superannotate/lib/core/usecases/models.py +++ b/src/superannotate/lib/core/usecases/models.py @@ -207,7 +207,7 @@ def download_to_local_storage(self, destination: str, extract_zip=False): self.reporter.stop_spinner() filename = Path(export["path"]).name for char in DownloadExportUseCase.FORBIDDEN_CHARS: - filename.replace(char, "_") + filename=filename.replace(char, "_") filepath = Path(destination) / filename with requests.get(export["download"], stream=True) as response: response.raise_for_status() From 3d8c94e8a10e25a708ec41c8450b95708a1b3a28 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Thu, 1 Dec 2022 12:01:19 +0400 Subject: [PATCH 15/23] 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 1d94a3026..fe5deba4b 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -1,7 +1,7 @@ import os import sys -__version__ = "4.4.7dev4" +__version__ = "4.4.7dev5" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From f9f02ddd4f35a105474db232fe0b1892fc2658a2 Mon Sep 17 00:00:00 2001 From: VavoTK Date: Thu, 1 Dec 2022 15:46:45 +0400 Subject: [PATCH 16/23] fixing .zip extension --- src/superannotate/lib/core/usecases/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/superannotate/lib/core/usecases/models.py b/src/superannotate/lib/core/usecases/models.py index 06149adf6..33b6db449 100644 --- a/src/superannotate/lib/core/usecases/models.py +++ b/src/superannotate/lib/core/usecases/models.py @@ -205,10 +205,10 @@ def download_to_local_storage(self, destination: str, extract_zip=False): raise AppException("Couldn't download export.") time.sleep(1) self.reporter.stop_spinner() - filename = Path(export["path"]).name + filename = Path(export["path"]).stem for char in DownloadExportUseCase.FORBIDDEN_CHARS: filename=filename.replace(char, "_") - filepath = Path(destination) / filename + filepath = Path(destination) / (filename+'.zip') with requests.get(export["download"], stream=True) as response: response.raise_for_status() with open(filepath, "wb") as f: From 071af7a6a9d66d237d70f9467fd312a8e2dea50b Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Thu, 1 Dec 2022 15:53:35 +0400 Subject: [PATCH 17/23] 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 fe5deba4b..bc3a40b5a 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -1,7 +1,7 @@ import os import sys -__version__ = "4.4.7dev5" +__version__ = "4.4.7dev6" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From 3cfad5ea254a9c7975474f03eca1a751b1d82a58 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Thu, 1 Dec 2022 16:28:33 +0400 Subject: [PATCH 18/23] 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 bc3a40b5a..bbd5517a6 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -1,7 +1,7 @@ import os import sys -__version__ = "4.4.7dev6" +__version__ = "4.4.7b1" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From 92f8e9a55d2a88af2287d5e41ae3c8eba94a23ba Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 1 Dec 2022 17:06:00 +0400 Subject: [PATCH 19/23] Update filename change logic --- src/superannotate/__init__.py | 2 +- src/superannotate/lib/core/usecases/models.py | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index bbd5517a6..73888e75e 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -1,7 +1,7 @@ import os import sys -__version__ = "4.4.7b1" +__version__ = "4.4.7b2" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) diff --git a/src/superannotate/lib/core/usecases/models.py b/src/superannotate/lib/core/usecases/models.py index 33b6db449..0ef05d390 100644 --- a/src/superannotate/lib/core/usecases/models.py +++ b/src/superannotate/lib/core/usecases/models.py @@ -2,6 +2,7 @@ import os.path import tempfile import time +import platform import zipfile from pathlib import Path from typing import List @@ -140,7 +141,8 @@ def execute(self): class DownloadExportUseCase(BaseReportableUseCase): - FORBIDDEN_CHARS = "*./\[]:;|,\"\'" + FORBIDDEN_CHARS = "*./\\[]:;|,\"'" + def __init__( self, service_provider: BaseServiceProvider, @@ -206,9 +208,10 @@ def download_to_local_storage(self, destination: str, extract_zip=False): time.sleep(1) self.reporter.stop_spinner() filename = Path(export["path"]).stem - for char in DownloadExportUseCase.FORBIDDEN_CHARS: - filename=filename.replace(char, "_") - filepath = Path(destination) / (filename+'.zip') + if platform.system().lower() == "windows": + for char in DownloadExportUseCase.FORBIDDEN_CHARS: + filename = filename.replace(char, "_") + filepath = Path(destination) / (filename + ".zip") with requests.get(export["download"], stream=True) as response: response.raise_for_status() with open(filepath, "wb") as f: From 2d0f7946e33942d3bee917b0563d916c7738c518 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Fri, 2 Dec 2022 13:02:41 +0400 Subject: [PATCH 20/23] Fix list_by_names --- .../lib/infrastructure/services/item.py | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/superannotate/lib/infrastructure/services/item.py b/src/superannotate/lib/infrastructure/services/item.py index 6916a3bdd..55b11cfc5 100644 --- a/src/superannotate/lib/infrastructure/services/item.py +++ b/src/superannotate/lib/infrastructure/services/item.py @@ -46,17 +46,26 @@ def list_by_names( folder: entities.FolderEntity, names: List[str], ): - return self.client.request( - self.URL_LIST_BY_NAMES, - "post", - data={ - "project_id": project.id, - "team_id": project.team_id, - "folder_id": folder.id, - "names": names, - }, - content_type=ItemListResponse, - ) + chunk_size = 200 + items = [] + response = None + for i in range(0, len(names), chunk_size): + response = self.client.request( + self.URL_LIST_BY_NAMES, + "post", + data={ + "project_id": project.id, + "team_id": project.team_id, + "folder_id": folder.id, + "names": names[i : i + chunk_size], # noqa + }, + content_type=ItemListResponse, + ) + if not response.ok: + return response + items.extend(response.data) + response.data = items + return response def attach( self, From 72d3c23bae2574d83f4124183eaaae4003bb1ce2 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Fri, 2 Dec 2022 16:57:36 +0400 Subject: [PATCH 21/23] Changed validation in SetAnnotationStatues --- src/superannotate/lib/core/usecases/items.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/superannotate/lib/core/usecases/items.py b/src/superannotate/lib/core/usecases/items.py index c88e5a9ed..4a035a382 100644 --- a/src/superannotate/lib/core/usecases/items.py +++ b/src/superannotate/lib/core/usecases/items.py @@ -658,16 +658,16 @@ def validate_items(self): existing_items = [] for i in range(0, len(self._item_names), self.CHUNK_SIZE): search_names = self._item_names[i : i + self.CHUNK_SIZE] # noqa - cand_items = self._service_provider.items.list_by_names( + response = self._service_provider.items.list_by_names( project=self._project, folder=self._folder, names=search_names, - ).data + ) + if not response.ok: + raise AppValidationException(response.error) - if isinstance(cand_items, dict): - continue + cand_items = response.data existing_items += cand_items - if not existing_items: raise AppValidationException(self.ERROR_MESSAGE) if existing_items: From 233a0b6a74ff887b1776f2768ca39910c005fbb3 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Fri, 2 Dec 2022 17:12:21 +0400 Subject: [PATCH 22/23] 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 73888e75e..7f1a61962 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -1,7 +1,7 @@ import os import sys -__version__ = "4.4.7b2" +__version__ = "4.4.7b3" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) From 2cb2a296a97d5ba7d09d7343aca6223f4867f66c Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan <84702976+VaghinakDev@users.noreply.github.com> Date: Sun, 4 Dec 2022 11:34:48 +0400 Subject: [PATCH 23/23] Update CHANGELOG.md --- CHANGELOG.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72901cf20..0809bb829 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Changelog All release highlights of this project will be documented in this file. - +## 4.4.7 - December 04, 2022 +### Updated +- `SAClient.search_folders` _method_ to add a new `status` argument for searching folders via status. +- Schemas for `Annotation Classes` and `Video Annotation` to support **text** and **numeric input** attribute group types. +### Fixed +- `SAClient.query` _method_ to address invalid exceptions. +- `SAClient.download_export` _method_ to address the issue with downloading for Windows OS. +- `SAClient.attach_items_from_integrated_storage` _method_ to address "integration not found" error. +- `SAClient.aggregate_annotations_as_df` _method_ to support files without "___objects" in their naming. +### Removed +- `SAClient.add_annotation_bbox_to_image` _method_, use `SAClient.upload_annotations` instead. +- `SAClient.add_annotation_point_to_image` _method_, use `SAClient.upload_annotations` instead. +- `SAClient.add_annotation_comment_to_image` _method_, use `SAClient.upload_annotations` instead. +### ## 4.4.6 - November 23, 2022 ### Updated - `SAClient.aggregate_annotations_as_df` method to aggregate "comment" type instances.