Skip to content

Commit ee7b776

Browse files
Vaghinak BasentsyanVaghinak Basentsyan
authored andcommitted
Merge branch 're-design-sdk' of https://github.com/superannotateai/superannotate-python-sdk into re-design-sdk
2 parents e008d22 + 77784c1 commit ee7b776

File tree

11 files changed

+3455
-2
lines changed

11 files changed

+3455
-2
lines changed

src/superannotate/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
from superannotate.lib.app.interface.sdk_interface import create_project
6161
from superannotate.lib.app.interface.sdk_interface import create_project_from_metadata
6262
from superannotate.lib.app.interface.sdk_interface import delete_annotation_class
63+
from superannotate.lib.app.interface.sdk_interface import delete_annotations
6364
from superannotate.lib.app.interface.sdk_interface import (
6465
delete_contributor_to_team_invitation,
6566
)
@@ -235,6 +236,7 @@
235236
"assign_images",
236237
"unassign_images",
237238
"download_image_annotations",
239+
"delete_annotations",
238240
"upload_image_to_project",
239241
"upload_image_annotations",
240242
"upload_images_from_public_urls_to_project",

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1498,7 +1498,7 @@ def get_image_annotations(project: Union[str, dict], image_name: str):
14981498
project_name=project_name, folder_name=folder_name, image_name=image_name
14991499
)
15001500
if res.errors:
1501-
raise AppValidationException(res)
1501+
raise AppException(res)
15021502
return res.data
15031503

15041504

@@ -3593,3 +3593,24 @@ def aggregate_annotations_as_df(
35933593
verbose,
35943594
folder_names,
35953595
)
3596+
3597+
3598+
@Trackable
3599+
@validate_input
3600+
def delete_annotations(project: str, image_names: List[str] = None):
3601+
"""
3602+
Delete image annotations from a given list of images.
3603+
3604+
:param project: project name or folder path (e.g., "project1/folder1")
3605+
:type project: str
3606+
:param image_names: image names. If None, all image annotations from a given project/folder will be deleted.
3607+
:type image_names: list of strs
3608+
"""
3609+
3610+
project_name, folder_name = extract_project_folder(project)
3611+
3612+
response = controller.delete_annotations(
3613+
project_name=project, folder_name=folder_name, image_names=image_names
3614+
)
3615+
if response.errors:
3616+
raise AppException(response.errors)

src/superannotate/lib/core/serviceproviders.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,17 @@ def run_prediction(
296296
self, team_id: int, project_id: int, ml_model_id: int, image_ids: list
297297
):
298298
raise NotImplementedError
299+
300+
def delete_image_annotations(
301+
self,
302+
team_id: int,
303+
project_id: int,
304+
folder_id: int = None,
305+
image_names: List[str] = None,
306+
) -> int:
307+
raise NotImplementedError
308+
309+
def get_annotations_delete_progress(
310+
self, team_id: int, project_id: int, poll_id: int
311+
):
312+
raise NotImplementedError

src/superannotate/lib/core/usecases.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from pathlib import Path
1616
from typing import Iterable
1717
from typing import List
18+
from typing import Optional
1819

1920
import boto3
2021
import cv2
@@ -2084,6 +2085,9 @@ def execute(self):
20842085
file_postfix = "___objects.json"
20852086
else:
20862087
file_postfix = "___pixel.json"
2088+
data["annotation_mask_filename"] = f"{self._image_name}___save.png"
2089+
data["annotation_json_filename"] = f"{self._image_name}{file_postfix}"
2090+
20872091
response = requests.get(
20882092
url=credentials["annotation_json_path"]["url"],
20892093
headers=credentials["annotation_json_path"]["headers"],
@@ -2101,7 +2105,6 @@ def execute(self):
21012105
headers=annotation_blue_map_creds["headers"],
21022106
)
21032107
data["annotation_mask"] = io.BytesIO(response.content)
2104-
data["annotation_mask_filename"] = f"{self._image_name}___save.png"
21052108

21062109
self._response.data = data
21072110

@@ -4485,3 +4488,56 @@ def execute(self):
44854488

44864489
self._response.data = uploaded, failed_images, duplications
44874490
return self._response
4491+
4492+
4493+
class DeleteAnnotations(BaseUseCase):
4494+
POLL_AWAIT_TIME = 2
4495+
4496+
def __init__(
4497+
self,
4498+
project: ProjectEntity,
4499+
folder: FolderEntity,
4500+
backend_service: SuerannotateServiceProvider,
4501+
image_names: Optional[List[str]] = None,
4502+
):
4503+
super().__init__()
4504+
self._project = project
4505+
self._folder = folder
4506+
self._image_names = image_names
4507+
self._backend_service = backend_service
4508+
4509+
def execute(self) -> Response:
4510+
4511+
if self._folder.name == "root" and not self._image_names:
4512+
poll_id = self._backend_service.delete_image_annotations(
4513+
project_id=self._project.uuid, team_id=self._project.team_id,
4514+
)
4515+
else:
4516+
poll_id = self._backend_service.delete_image_annotations(
4517+
project_id=self._project.uuid,
4518+
team_id=self._project.team_id,
4519+
folder_id=self._folder.uuid,
4520+
image_names=self._image_names,
4521+
)
4522+
4523+
if poll_id:
4524+
timeout_start = time.time()
4525+
while time.time() < timeout_start + self.POLL_AWAIT_TIME:
4526+
progress = int(
4527+
self._backend_service.get_annotations_delete_progress(
4528+
project_id=self._project.uuid,
4529+
team_id=self._project.team_id,
4530+
poll_id=poll_id,
4531+
).get("process", -1)
4532+
)
4533+
if 0 < progress < 100:
4534+
logger.info(f"Delete annotations in progress {progress}/100")
4535+
elif 0 > progress:
4536+
self._response.errors = "Annotations delete fails."
4537+
break
4538+
else:
4539+
logger.info(f"Annotations deleted")
4540+
break
4541+
else:
4542+
self._response.errors = AppException("Invalid image names.")
4543+
return self._response

src/superannotate/lib/infrastructure/controller.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,3 +1446,19 @@ def search_models(
14461446
ml_models_repo=ml_models_repo, condition=condition
14471447
)
14481448
return use_case.execute()
1449+
1450+
def delete_annotations(
1451+
self,
1452+
project_name: str,
1453+
folder_name: str,
1454+
image_names: Optional[List[str]] = None,
1455+
):
1456+
project = self._get_project(project_name)
1457+
folder = self._get_folder(project, folder_name)
1458+
use_case = usecases.DeleteAnnotations(
1459+
project=project,
1460+
folder=folder,
1461+
backend_service=self._backend_client,
1462+
image_names=image_names,
1463+
)
1464+
return use_case.execute()

src/superannotate/lib/infrastructure/services.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ class SuperannotateBackendService(BaseBackendService):
181181
URL_SEGMENTATION = "images/segmentation"
182182
URL_PREDICTION = "images/prediction"
183183
URL_SET_IMAGES_STATUSES_BULK = "image/updateAnnotationStatusBulk"
184+
URL_DELETE_ANNOTATIONS = "annotations/remove"
185+
URL_DELETE_ANNOTATIONS_PROGRESS = "annotations/getRemoveStatus"
184186

185187
def get_project(self, uuid: int, team_id: int):
186188
get_project_url = urljoin(self.api_url, self.URL_GET_PROJECT.format(uuid))
@@ -945,3 +947,35 @@ def run_prediction(
945947
},
946948
)
947949
return res.json()
950+
951+
def delete_image_annotations(
952+
self,
953+
team_id: int,
954+
project_id: int,
955+
folder_id: int = None,
956+
image_names: List[str] = None,
957+
) -> int:
958+
delete_annotations_url = urljoin(self.api_url, self.URL_DELETE_ANNOTATIONS)
959+
params = {"team_id": team_id, "project_id": project_id}
960+
data = {}
961+
if folder_id:
962+
params["folder_id"] = folder_id
963+
if image_names:
964+
data["image_names"] = image_names
965+
response = self._request(
966+
delete_annotations_url, "post", params=params, data=data
967+
)
968+
if response.ok:
969+
return response.json()["poll_id"]
970+
971+
def get_annotations_delete_progress(
972+
self, team_id: int, project_id: int, poll_id: int
973+
):
974+
get_progress_url = urljoin(self.api_url, self.URL_DELETE_ANNOTATIONS_PROGRESS)
975+
976+
response = self._request(
977+
get_progress_url,
978+
"get",
979+
params={"team_id": team_id, "project_id": project_id, "poll_id": poll_id},
980+
)
981+
return response.json()
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
[
2+
{
3+
"id": 55917,
4+
"project_id": 11979,
5+
"name": "Personal vehicle",
6+
"color": "#ecb65f",
7+
"count": 25,
8+
"createdAt": "2020-10-12T11:35:20.000Z",
9+
"updatedAt": "2020-10-12T11:48:19.000Z",
10+
"attribute_groups": [
11+
{
12+
"id": 17245,
13+
"class_id": 55917,
14+
"name": "Num doors",
15+
"is_multiselect": 0,
16+
"createdAt": "2020-10-12T11:35:20.000Z",
17+
"updatedAt": "2020-10-12T11:35:20.000Z",
18+
"attributes": [
19+
{
20+
"id": 62792,
21+
"group_id": 17245,
22+
"project_id": 11979,
23+
"name": "2",
24+
"count": 1,
25+
"createdAt": "2020-10-12T11:35:20.000Z",
26+
"updatedAt": "2020-10-12T11:46:28.000Z"
27+
},
28+
{
29+
"id": 62793,
30+
"group_id": 17245,
31+
"project_id": 11979,
32+
"name": "4",
33+
"count": 1,
34+
"createdAt": "2020-10-12T11:35:20.000Z",
35+
"updatedAt": "2020-10-12T11:35:20.000Z"
36+
}
37+
]
38+
}
39+
]
40+
},
41+
{
42+
"id": 55918,
43+
"project_id": 11979,
44+
"name": "Large vehicle",
45+
"color": "#737b28",
46+
"count": 1,
47+
"createdAt": "2020-10-12T11:35:20.000Z",
48+
"updatedAt": "2020-10-12T11:48:19.000Z",
49+
"attribute_groups": [
50+
{
51+
"id": 17246,
52+
"class_id": 55918,
53+
"name": "swedish",
54+
"is_multiselect": 0,
55+
"createdAt": "2020-10-12T11:35:20.000Z",
56+
"updatedAt": "2020-10-12T11:35:20.000Z",
57+
"attributes": [
58+
{
59+
"id": 62794,
60+
"group_id": 17246,
61+
"project_id": 11979,
62+
"name": "yes",
63+
"count": 0,
64+
"createdAt": "2020-10-12T11:35:20.000Z",
65+
"updatedAt": "2020-10-12T11:35:20.000Z"
66+
},
67+
{
68+
"id": 62795,
69+
"group_id": 17246,
70+
"project_id": 11979,
71+
"name": "no",
72+
"count": 1,
73+
"createdAt": "2020-10-12T11:35:20.000Z",
74+
"updatedAt": "2020-10-12T11:46:28.000Z"
75+
}
76+
]
77+
},
78+
{
79+
"id": 17247,
80+
"class_id": 55918,
81+
"name": "Num doors",
82+
"is_multiselect": 0,
83+
"createdAt": "2020-10-12T11:35:20.000Z",
84+
"updatedAt": "2020-10-12T11:35:20.000Z",
85+
"attributes": [
86+
{
87+
"id": 62796,
88+
"group_id": 17247,
89+
"project_id": 11979,
90+
"name": "2",
91+
"count": 0,
92+
"createdAt": "2020-10-12T11:35:20.000Z",
93+
"updatedAt": "2020-10-12T11:35:20.000Z"
94+
},
95+
{
96+
"id": 62797,
97+
"group_id": 17247,
98+
"project_id": 11979,
99+
"name": "4",
100+
"count": 1,
101+
"createdAt": "2020-10-12T11:35:20.000Z",
102+
"updatedAt": "2020-10-12T11:46:28.000Z"
103+
}
104+
]
105+
}
106+
]
107+
},
108+
{
109+
"id": 55919,
110+
"project_id": 11979,
111+
"name": "Human",
112+
"color": "#e4542b",
113+
"count": 9,
114+
"createdAt": "2020-10-12T11:35:20.000Z",
115+
"updatedAt": "2020-10-12T11:48:14.000Z",
116+
"attribute_groups": [
117+
{
118+
"id": 17248,
119+
"class_id": 55919,
120+
"name": "Height",
121+
"is_multiselect": 0,
122+
"createdAt": "2020-10-12T11:35:20.000Z",
123+
"updatedAt": "2020-10-12T11:35:20.000Z",
124+
"attributes": [
125+
{
126+
"id": 62798,
127+
"group_id": 17248,
128+
"project_id": 11979,
129+
"name": "Tall",
130+
"count": 0,
131+
"createdAt": "2020-10-12T11:35:20.000Z",
132+
"updatedAt": "2020-10-12T11:35:20.000Z"
133+
},
134+
{
135+
"id": 62799,
136+
"group_id": 17248,
137+
"project_id": 11979,
138+
"name": "Short",
139+
"count": 0,
140+
"createdAt": "2020-10-12T11:35:20.000Z",
141+
"updatedAt": "2020-10-12T11:35:20.000Z"
142+
}
143+
]
144+
}
145+
]
146+
},
147+
{
148+
"id": 55920,
149+
"project_id": 11979,
150+
"name": "Plant",
151+
"color": "#46ccb2",
152+
"count": 0,
153+
"createdAt": "2020-10-12T11:35:20.000Z",
154+
"updatedAt": "2020-10-12T11:35:20.000Z",
155+
"attribute_groups": []
156+
}
157+
]
209 KB
Loading
10.6 KB
Loading

0 commit comments

Comments
 (0)