Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/superannotate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
from superannotate.lib.app.interface.sdk_interface import create_project
from superannotate.lib.app.interface.sdk_interface import create_project_from_metadata
from superannotate.lib.app.interface.sdk_interface import delete_annotation_class
from superannotate.lib.app.interface.sdk_interface import delete_annotations
from superannotate.lib.app.interface.sdk_interface import (
delete_contributor_to_team_invitation,
)
Expand Down Expand Up @@ -235,6 +236,7 @@
"assign_images",
"unassign_images",
"download_image_annotations",
"delete_annotations",
"upload_image_to_project",
"upload_image_annotations",
"upload_images_from_public_urls_to_project",
Expand Down
23 changes: 22 additions & 1 deletion src/superannotate/lib/app/interface/sdk_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -1498,7 +1498,7 @@ def get_image_annotations(project: Union[str, dict], image_name: str):
project_name=project_name, folder_name=folder_name, image_name=image_name
)
if res.errors:
raise AppValidationException(res)
raise AppException(res)
return res.data


Expand Down Expand Up @@ -3593,3 +3593,24 @@ def aggregate_annotations_as_df(
verbose,
folder_names,
)


@Trackable
@validate_input
def delete_annotations(project: str, image_names: List[str] = None):
"""
Delete image annotations from a given list of images.

:param project: project name or folder path (e.g., "project1/folder1")
:type project: str
:param image_names: image names. If None, all image annotations from a given project/folder will be deleted.
:type image_names: list of strs
"""

project_name, folder_name = extract_project_folder(project)

response = controller.delete_annotations(
project_name=project, folder_name=folder_name, image_names=image_names
)
if response.errors:
raise AppException(response.errors)
14 changes: 14 additions & 0 deletions src/superannotate/lib/core/serviceproviders.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,3 +296,17 @@ def run_prediction(
self, team_id: int, project_id: int, ml_model_id: int, image_ids: list
):
raise NotImplementedError

def delete_image_annotations(
self,
team_id: int,
project_id: int,
folder_id: int = None,
image_names: List[str] = None,
) -> int:
raise NotImplementedError

def get_annotations_delete_progress(
self, team_id: int, project_id: int, poll_id: int
):
raise NotImplementedError
58 changes: 57 additions & 1 deletion src/superannotate/lib/core/usecases.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from pathlib import Path
from typing import Iterable
from typing import List
from typing import Optional

import boto3
import cv2
Expand Down Expand Up @@ -2081,6 +2082,9 @@ def execute(self):
file_postfix = "___objects.json"
else:
file_postfix = "___pixel.json"
data["annotation_mask_filename"] = f"{self._image_name}___save.png"
data["annotation_json_filename"] = f"{self._image_name}{file_postfix}"

response = requests.get(
url=credentials["annotation_json_path"]["url"],
headers=credentials["annotation_json_path"]["headers"],
Expand All @@ -2098,7 +2102,6 @@ def execute(self):
headers=annotation_blue_map_creds["headers"],
)
data["annotation_mask"] = io.BytesIO(response.content)
data["annotation_mask_filename"] = f"{self._image_name}___save.png"

self._response.data = data

Expand Down Expand Up @@ -4482,3 +4485,56 @@ def execute(self):

self._response.data = uploaded, failed_images, duplications
return self._response


class DeleteAnnotations(BaseUseCase):
POLL_AWAIT_TIME = 2

def __init__(
self,
project: ProjectEntity,
folder: FolderEntity,
backend_service: SuerannotateServiceProvider,
image_names: Optional[List[str]] = None,
):
super().__init__()
self._project = project
self._folder = folder
self._image_names = image_names
self._backend_service = backend_service

def execute(self) -> Response:

if self._folder.name == "root" and not self._image_names:
poll_id = self._backend_service.delete_image_annotations(
project_id=self._project.uuid, team_id=self._project.team_id,
)
else:
poll_id = self._backend_service.delete_image_annotations(
project_id=self._project.uuid,
team_id=self._project.team_id,
folder_id=self._folder.uuid,
image_names=self._image_names,
)

if poll_id:
timeout_start = time.time()
while time.time() < timeout_start + self.POLL_AWAIT_TIME:
progress = int(
self._backend_service.get_annotations_delete_progress(
project_id=self._project.uuid,
team_id=self._project.team_id,
poll_id=poll_id,
).get("process", -1)
)
if 0 < progress < 100:
logger.info(f"Delete annotations in progress {progress}/100")
elif 0 > progress:
self._response.errors = "Annotations delete fails."
break
else:
logger.info(f"Annotations deleted")
break
else:
self._response.errors = AppException("Invalid image names.")
return self._response
16 changes: 16 additions & 0 deletions src/superannotate/lib/infrastructure/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -1446,3 +1446,19 @@ def search_models(
ml_models_repo=ml_models_repo, condition=condition
)
return use_case.execute()

def delete_annotations(
self,
project_name: str,
folder_name: str,
image_names: Optional[List[str]] = None,
):
project = self._get_project(project_name)
folder = self._get_folder(project, folder_name)
use_case = usecases.DeleteAnnotations(
project=project,
folder=folder,
backend_service=self._backend_client,
image_names=image_names,
)
return use_case.execute()
34 changes: 34 additions & 0 deletions src/superannotate/lib/infrastructure/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ class SuperannotateBackendService(BaseBackendService):
URL_SEGMENTATION = "images/segmentation"
URL_PREDICTION = "images/prediction"
URL_SET_IMAGES_STATUSES_BULK = "image/updateAnnotationStatusBulk"
URL_DELETE_ANNOTATIONS = "annotations/remove"
URL_DELETE_ANNOTATIONS_PROGRESS = "annotations/getRemoveStatus"

def get_project(self, uuid: int, team_id: int):
get_project_url = urljoin(self.api_url, self.URL_GET_PROJECT.format(uuid))
Expand Down Expand Up @@ -945,3 +947,35 @@ def run_prediction(
},
)
return res.json()

def delete_image_annotations(
self,
team_id: int,
project_id: int,
folder_id: int = None,
image_names: List[str] = None,
) -> int:
delete_annotations_url = urljoin(self.api_url, self.URL_DELETE_ANNOTATIONS)
params = {"team_id": team_id, "project_id": project_id}
data = {}
if folder_id:
params["folder_id"] = folder_id
if image_names:
data["image_names"] = image_names
response = self._request(
delete_annotations_url, "post", params=params, data=data
)
if response.ok:
return response.json()["poll_id"]

def get_annotations_delete_progress(
self, team_id: int, project_id: int, poll_id: int
):
get_progress_url = urljoin(self.api_url, self.URL_DELETE_ANNOTATIONS_PROGRESS)

response = self._request(
get_progress_url,
"get",
params={"team_id": team_id, "project_id": project_id, "poll_id": poll_id},
)
return response.json()
157 changes: 157 additions & 0 deletions tests/data_set/sample_project_vector_single_image/classes/classes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
[
{
"id": 55917,
"project_id": 11979,
"name": "Personal vehicle",
"color": "#ecb65f",
"count": 25,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:48:19.000Z",
"attribute_groups": [
{
"id": 17245,
"class_id": 55917,
"name": "Num doors",
"is_multiselect": 0,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z",
"attributes": [
{
"id": 62792,
"group_id": 17245,
"project_id": 11979,
"name": "2",
"count": 1,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:46:28.000Z"
},
{
"id": 62793,
"group_id": 17245,
"project_id": 11979,
"name": "4",
"count": 1,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z"
}
]
}
]
},
{
"id": 55918,
"project_id": 11979,
"name": "Large vehicle",
"color": "#737b28",
"count": 1,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:48:19.000Z",
"attribute_groups": [
{
"id": 17246,
"class_id": 55918,
"name": "swedish",
"is_multiselect": 0,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z",
"attributes": [
{
"id": 62794,
"group_id": 17246,
"project_id": 11979,
"name": "yes",
"count": 0,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z"
},
{
"id": 62795,
"group_id": 17246,
"project_id": 11979,
"name": "no",
"count": 1,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:46:28.000Z"
}
]
},
{
"id": 17247,
"class_id": 55918,
"name": "Num doors",
"is_multiselect": 0,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z",
"attributes": [
{
"id": 62796,
"group_id": 17247,
"project_id": 11979,
"name": "2",
"count": 0,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z"
},
{
"id": 62797,
"group_id": 17247,
"project_id": 11979,
"name": "4",
"count": 1,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:46:28.000Z"
}
]
}
]
},
{
"id": 55919,
"project_id": 11979,
"name": "Human",
"color": "#e4542b",
"count": 9,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:48:14.000Z",
"attribute_groups": [
{
"id": 17248,
"class_id": 55919,
"name": "Height",
"is_multiselect": 0,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z",
"attributes": [
{
"id": 62798,
"group_id": 17248,
"project_id": 11979,
"name": "Tall",
"count": 0,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z"
},
{
"id": 62799,
"group_id": 17248,
"project_id": 11979,
"name": "Short",
"count": 0,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z"
}
]
}
]
},
{
"id": 55920,
"project_id": 11979,
"name": "Plant",
"color": "#46ccb2",
"count": 0,
"createdAt": "2020-10-12T11:35:20.000Z",
"updatedAt": "2020-10-12T11:35:20.000Z",
"attribute_groups": []
}
]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading