Skip to content

Commit 0c02124

Browse files
committed
merge
2 parents 0f88447 + ee7b776 commit 0c02124

File tree

15 files changed

+3572
-50
lines changed

15 files changed

+3572
-50
lines changed

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
minversion = 3.0
33
log_cli=true
44
python_files = test_*.py
5-
addopts = -n32 --dist=loadscope
5+
;addopts = -n32 --dist=loadscope

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: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,7 +1503,7 @@ def get_image_annotations(project: Union[str, dict], image_name: str):
15031503
project_name=project_name, folder_name=folder_name, image_name=image_name
15041504
)
15051505
if res.errors:
1506-
raise AppValidationException(res)
1506+
raise AppException(res)
15071507
return res.data
15081508

15091509

@@ -1561,7 +1561,7 @@ def upload_images_from_folder_to_project(
15611561
)
15621562
images_to_upload, _ = use_case.images_to_upload
15631563
if use_case.is_valid():
1564-
with tqdm(total=len(images_to_upload)) as progress_bar:
1564+
with tqdm(total=len(images_to_upload), desc="Uploading images") as progress_bar:
15651565
for _ in use_case.execute():
15661566
progress_bar.update(1)
15671567
return use_case.data
@@ -1866,7 +1866,7 @@ def upload_videos_from_folder_to_project(
18661866
)
18671867

18681868
uploaded_images, failed_images = [], []
1869-
for path in tqdm(video_paths):
1869+
for path in tqdm(video_paths, desc="Uploading videos"):
18701870
with tempfile.TemporaryDirectory() as temp_path:
18711871
res = controller.extract_video_frames(
18721872
project_name=project_name,
@@ -1960,7 +1960,9 @@ def upload_video_to_project(
19601960
)
19611961
images_to_upload, _ = use_case.images_to_upload
19621962
if use_case.is_valid():
1963-
with tqdm(total=len(images_to_upload)) as progress_bar:
1963+
with tqdm(
1964+
total=len(images_to_upload), desc="Uploading frames."
1965+
) as progress_bar:
19641966
for _ in use_case.execute():
19651967
progress_bar.update(1)
19661968
return use_case.data[0]
@@ -2495,8 +2497,10 @@ def upload_annotations_from_folder_to_project(
24952497
uploaded_annotations = []
24962498
failed_annotations = []
24972499
missing_annotations = []
2498-
chunk_size = 10
2499-
with tqdm(total=len(annotation_paths)) as progress_bar:
2500+
chunk_size = 50
2501+
with tqdm(
2502+
total=len(annotation_paths), desc="Uploading annotations"
2503+
) as progress_bar:
25002504
for i in range(0, len(annotation_paths), chunk_size):
25012505
response = controller.upload_annotations_from_folder(
25022506
project_name=project_name,
@@ -2556,7 +2560,9 @@ def upload_preannotations_from_folder_to_project(
25562560
failed_annotations = []
25572561
missing_annotations = []
25582562
chunk_size = 10
2559-
with tqdm(total=len(annotation_paths)) as progress_bar:
2563+
with tqdm(
2564+
total=len(annotation_paths), desc="Uploading pre annotations"
2565+
) as progress_bar:
25602566
for i in range(0, len(annotation_paths), chunk_size):
25612567
response = controller.upload_annotations_from_folder(
25622568
project_name=project_name,
@@ -3518,7 +3524,7 @@ def _upload_s3_image(image_path: str):
35183524
executor.submit(upload_method, image_path)
35193525
for image_path in images_to_upload
35203526
]
3521-
with tqdm(total=len(images_to_upload)) as progress_bar:
3527+
with tqdm(total=len(images_to_upload), desc="Uploading images") as progress_bar:
35223528
for future in concurrent.futures.as_completed(results):
35233529
processed_image = future.result()
35243530
if processed_image.uploaded and processed_image.entity:
@@ -3594,3 +3600,24 @@ def aggregate_annotations_as_df(
35943600
verbose,
35953601
folder_names,
35963602
)
3603+
3604+
3605+
@Trackable
3606+
@validate_input
3607+
def delete_annotations(project: str, image_names: List[str] = None):
3608+
"""
3609+
Delete image annotations from a given list of images.
3610+
3611+
:param project: project name or folder path (e.g., "project1/folder1")
3612+
:type project: str
3613+
:param image_names: image names. If None, all image annotations from a given project/folder will be deleted.
3614+
:type image_names: list of strs
3615+
"""
3616+
3617+
project_name, folder_name = extract_project_folder(project)
3618+
3619+
response = controller.delete_annotations(
3620+
project_name=project, folder_name=folder_name, image_names=image_names
3621+
)
3622+
if response.errors:
3623+
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: 79 additions & 17 deletions
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
@@ -363,6 +364,9 @@ def execute(self):
363364
] = new_project_annotation_classes.insert(annotation_class_copy)
364365

365366
if self._include_contributors:
367+
self._project = self._projects.get_one(
368+
uuid=self._project.uuid, team_id=self._project.team_id
369+
)
366370
for user in self._project.users:
367371
self._backend_service.share_project(
368372
project.uuid,
@@ -504,7 +508,7 @@ class UploadImageS3UseCase(BaseUseCase):
504508
def __init__(
505509
self,
506510
project: ProjectEntity,
507-
project_settings: BaseReadOnlyRepository,
511+
project_settings: List[ProjectSettingEntity],
508512
image_path: str,
509513
image: io.BytesIO,
510514
s3_repo: BaseManageableRepository,
@@ -535,7 +539,7 @@ def execute(self):
535539
huge_image, huge_width, huge_height = image_processor.generate_huge()
536540
quality = 60
537541
if not self._image_quality_in_editor:
538-
for setting in self._project_settings.get_all():
542+
for setting in self._project_settings:
539543
if setting.attribute == "ImageQuality":
540544
quality = setting.value
541545
else:
@@ -679,7 +683,11 @@ def execute(self):
679683
folder_id=self._folder.uuid,
680684
images=[image.name for image in self._attachments],
681685
)
682-
duplications = [image["name"] for image in duplications]
686+
try:
687+
duplications = [image["name"] for image in duplications]
688+
except Exception:
689+
print(duplications)
690+
raise
683691
meta = {}
684692
to_upload = []
685693
for image in self._attachments:
@@ -1062,7 +1070,7 @@ def execute(self):
10621070
headers=annotations["annotation_json_path"]["headers"],
10631071
)
10641072
if not response.ok:
1065-
raise AppException(f"Couldn't load annotations.")
1073+
raise AppException("Couldn't load annotations.")
10661074

10671075
image_annotations = response.json()
10681076
from_project_annotation_classes = (
@@ -1153,7 +1161,7 @@ def execute(self):
11531161
headers=annotations["annotation_bluemap_path"]["headers"],
11541162
)
11551163
if not response.ok:
1156-
raise AppException(f"Couldn't load annotations.")
1164+
raise AppException("Couldn't load annotations.")
11571165
self.to_project_s3_repo.insert(
11581166
S3FileEntity(
11591167
auth_data["annotation_bluemap_path"]["filePath"], response.content
@@ -2077,12 +2085,15 @@ def execute(self):
20772085
file_postfix = "___objects.json"
20782086
else:
20792087
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+
20802091
response = requests.get(
20812092
url=credentials["annotation_json_path"]["url"],
20822093
headers=credentials["annotation_json_path"]["headers"],
20832094
)
20842095
if not response.ok:
2085-
logger.warning(f"Couldn't load annotations.")
2096+
logger.warning("Couldn't load annotations.")
20862097
self._response.data = data
20872098
return self._response
20882099
data["annotation_json"] = response.json()
@@ -2094,7 +2105,6 @@ def execute(self):
20942105
headers=annotation_blue_map_creds["headers"],
20952106
)
20962107
data["annotation_mask"] = io.BytesIO(response.content)
2097-
data["annotation_mask_filename"] = f"{self._image_name}___save.png"
20982108

20992109
self._response.data = data
21002110

@@ -2177,7 +2187,7 @@ def execute(self):
21772187
url=annotation_json_creds["url"], headers=annotation_json_creds["headers"],
21782188
)
21792189
if not response.ok:
2180-
raise AppException(f"Couldn't load annotations.")
2190+
raise AppException("Couldn't load annotations.")
21812191
data["preannotation_json"] = response.json()
21822192
data["preannotation_json_filename"] = f"{self._image_name}{file_postfix}"
21832193
if self._project.project_type == constances.ProjectType.PIXEL.value:
@@ -2254,7 +2264,7 @@ def execute(self):
22542264
headers=annotation_json_creds["headers"],
22552265
)
22562266
if not response.ok:
2257-
logger.warning(f"Couldn't load annotations.")
2267+
logger.warning("Couldn't load annotations.")
22582268
self._response.data = (None, None)
22592269
return self._response
22602270
data["annotation_json"] = response.json()
@@ -2333,7 +2343,7 @@ def execute(self):
23332343
url=annotation_json_creds["url"], headers=annotation_json_creds["headers"],
23342344
)
23352345
if not response.ok:
2336-
raise AppException(f"Couldn't load annotations.")
2346+
raise AppException("Couldn't load annotations.")
23372347
data["preannotation_json"] = response.json()
23382348
data["preannotation_json_filename"] = f"{self._image_name}{file_postfix}"
23392349
mask_path = None
@@ -3277,10 +3287,11 @@ def __init__(
32773287
self,
32783288
project: ProjectEntity,
32793289
folder: FolderEntity,
3280-
annotation_classes: BaseReadOnlyRepository,
3290+
annotation_classes: List[AnnotationClassEntity],
32813291
folder_path: str,
32823292
annotation_paths: List[str],
32833293
backend_service_provider: SuerannotateServiceProvider,
3294+
templates: List[dict],
32843295
pre_annotation: bool = False,
32853296
client_s3_bucket=None,
32863297
):
@@ -3293,6 +3304,7 @@ def __init__(
32933304
self._annotation_paths = annotation_paths
32943305
self._client_s3_bucket = client_s3_bucket
32953306
self._pre_annotation = pre_annotation
3307+
self._templates = templates
32963308

32973309
@property
32983310
def s3_client(self):
@@ -3301,7 +3313,7 @@ def s3_client(self):
33013313
@property
33023314
def annotation_classes_name_map(self) -> dict:
33033315
classes_data = defaultdict(dict)
3304-
annotation_classes = self._annotation_classes.get_all()
3316+
annotation_classes = self._annotation_classes
33053317
for annotation_class in annotation_classes:
33063318
class_info = {"id": annotation_class.uuid}
33073319
if annotation_class.attribute_groups:
@@ -3327,11 +3339,8 @@ def annotation_postfix(self):
33273339
)
33283340

33293341
def get_templates_mapping(self):
3330-
templates = self._backend_service.get_templates(
3331-
team_id=self._project.team_id
3332-
).get("data", [])
33333342
templates_map = {}
3334-
for template in templates:
3343+
for template in self._templates:
33353344
templates_map[template["name"]] = template["id"]
33363345
return templates_map
33373346

@@ -4276,7 +4285,7 @@ def __init__(
42764285
self._project = project
42774286
self._folder = folder
42784287
self._folder_path = folder_path
4279-
self._settings = settings
4288+
self._settings = settings.get_all()
42804289
self._s3_repo = s3_repo
42814290
self._backend_client = backend_client
42824291
self._image_quality_in_editor = image_quality_in_editor
@@ -4486,6 +4495,59 @@ def execute(self):
44864495
return self._response
44874496

44884497

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

44904552
class GetDuplicateImages(BaseUseCase):
44914553
def __init__(self,

0 commit comments

Comments
 (0)