Skip to content

Commit f9df021

Browse files
committed
tod
1 parent 6f580a0 commit f9df021

File tree

8 files changed

+142
-49
lines changed

8 files changed

+142
-49
lines changed

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2205,20 +2205,28 @@ def invite_contributors_to_team(
22052205
return response.data
22062206

22072207
def get_annotations(
2208-
self, project: NotEmptyStr, items: Optional[List[NotEmptyStr]] = None
2208+
self,
2209+
project: Union[NotEmptyStr, int],
2210+
items: Optional[Union[List[NotEmptyStr], List[int]]] = None,
22092211
):
22102212
"""Returns annotations for the given list of items.
22112213
2212-
:param project: project name or folder path (e.g., “project1/folder1”).
2213-
:type project: str
2214+
:param project: project id or project name or folder path (e.g., “project1/folder1”).
2215+
:type project: str or int
22142216
22152217
:param items: item names. If None, all the items in the specified directory will be used.
2216-
:type items: list of strs
2218+
:type items: list of strs or list of ints
22172219
22182220
:return: list of annotations
2219-
:rtype: list of strs
2221+
:rtype: list of dict
22202222
"""
2221-
project, folder = self.controller.get_project_folder_by_path(project)
2223+
if isinstance(project, str):
2224+
project, folder = self.controller.get_project_folder_by_path(project)
2225+
else:
2226+
project, folder = (
2227+
self.controller.get_project_by_id(project_id=project).data,
2228+
None,
2229+
)
22222230
response = self.controller.annotations.list(project, folder, items)
22232231
if response.errors:
22242232
raise AppException(response.errors)

src/superannotate/lib/core/serviceproviders.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ def list_by_names(
246246
) -> ServiceResponse:
247247
raise NotImplementedError
248248

249+
@abstractmethod
250+
def list_by_ids(
251+
self,
252+
project: entities.ProjectEntity,
253+
ids: List[int],
254+
) -> ServiceResponse:
255+
raise NotImplementedError
256+
249257
@abstractmethod
250258
def attach(
251259
self,

src/superannotate/lib/core/usecases/annotations.py

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from typing import Optional
2323
from typing import Set
2424
from typing import Tuple
25+
from typing import Union
2526

2627
import aiofiles
2728
import boto3
@@ -191,10 +192,13 @@ async def upload(_chunk: List[ItemToUpload]):
191192
[],
192193
)
193194
try:
195+
items_name_data_map = {i.item.name: i.annotation_json for i in chunk}
196+
if not items_name_data_map:
197+
return
194198
response = await service_provider.annotations.upload_small_annotations(
195199
project=project,
196200
folder=folder,
197-
items_name_data_map={i.item.name: i.annotation_json for i in chunk},
201+
items_name_data_map=items_name_data_map,
198202
)
199203
if response.ok:
200204
if response.data.failed_items: # noqa
@@ -1450,44 +1454,56 @@ def __init__(
14501454
config: ConfigEntity,
14511455
reporter: Reporter,
14521456
project: ProjectEntity,
1453-
folder: FolderEntity,
1454-
item_names: Optional[List[str]],
14551457
service_provider: BaseServiceProvider,
1458+
folder: FolderEntity = None,
1459+
items: Optional[Union[List[str], List[int]]] = None,
14561460
):
14571461
super().__init__(reporter)
14581462
self._config = config
14591463
self._project = project
14601464
self._folder = folder
14611465
self._service_provider = service_provider
1462-
self._item_names = item_names
1466+
self._items = items
1467+
self._item_name_id_map = {}
14631468
self._item_names_provided = True
14641469
self._big_annotations_queue = None
14651470

14661471
def validate_project_type(self):
14671472
if self._project.type == constants.ProjectType.PIXEL.value:
14681473
raise AppException("The function is not supported for Pixel projects.")
14691474

1470-
def validate_item_names(self):
1471-
if self._item_names: # if names provided
1472-
unique_item_names = list(set(self._item_names))
1473-
len_unique_items, len_items = len(unique_item_names), len(self._item_names)
1474-
if len_unique_items < len_items:
1475-
self.reporter.log_info(
1476-
f"Dropping duplicates. Found {len_unique_items}/{len_items} unique items."
1477-
)
1475+
@staticmethod
1476+
def items_duplication_validation(
1477+
reporter, items: Union[List[str], List[int]]
1478+
) -> Union[List[str], List[int]]:
1479+
unique_items = list(set(items))
1480+
len_unique_items, len_items = len(unique_items), len(items)
1481+
if len_unique_items < len_items:
1482+
reporter.log_info(
1483+
f"Dropping duplicates. Found {len_unique_items}/{len_items} unique items."
1484+
)
1485+
return unique_items
1486+
1487+
def validate_items(self):
1488+
if self._items: # if items provided:
1489+
self.items_duplication_validation(self.reporter, self._items)
14781490
# Keep order required
14791491
seen = set()
1480-
self._item_names = [
1481-
i for i in self._item_names if not (i in seen or seen.add(i))
1482-
]
1492+
self._items = [i for i in self._items if not (i in seen or seen.add(i))]
14831493

14841494
def _prettify_annotations(self, annotations: List[dict]):
14851495
re_struct = {}
1486-
if self._item_names:
1496+
if self._items:
1497+
names_provided = isinstance(self._items[0], str)
14871498
for annotation in annotations:
1488-
re_struct[annotation["metadata"]["name"]] = annotation
1499+
if names_provided:
1500+
re_struct[annotation["metadata"]["name"]] = annotation
1501+
else:
1502+
re_struct[
1503+
self._item_name_id_map[annotation["metadata"]["name"]]
1504+
] = annotation
14891505
try:
1490-
return [re_struct[x] for x in self._item_names if x in re_struct]
1506+
return [re_struct[x] for x in self._items if x in re_struct]
14911507
except KeyError:
14921508
raise AppException("Broken data.")
14931509

@@ -1558,18 +1574,28 @@ async def run_workers(
15581574

15591575
def execute(self):
15601576
if self.is_valid():
1561-
if self._item_names:
1562-
items = get_or_raise(
1563-
self._service_provider.items.list_by_names(
1564-
self._project, self._folder, self._item_names
1577+
if self._items:
1578+
if isinstance(self._items[0], str):
1579+
items: List[BaseItemEntity] = get_or_raise(
1580+
self._service_provider.items.list_by_names(
1581+
self._project, self._folder, self._items
1582+
)
15651583
)
1566-
)
1567-
len_items, len_provided_items = len(items), len(self._item_names)
1584+
else:
1585+
response = self._service_provider.items.list_by_ids(
1586+
project=self._project,
1587+
ids=self._items,
1588+
)
1589+
if not response.ok:
1590+
raise AppException(response.error)
1591+
items: List[BaseItemEntity] = response.data
1592+
self._item_name_id_map = {i.name: i.id for i in items}
1593+
len_items, len_provided_items = len(items), len(self._items)
15681594
if len_items != len_provided_items:
15691595
self.reporter.log_warning(
15701596
f"Could not find annotations for {len_provided_items - len_items}/{len_provided_items} items."
15711597
)
1572-
elif self._item_names is None:
1598+
elif self._items is None:
15731599
condition = Condition("project_id", self._project.id, EQ) & Condition(
15741600
"folder_id", self._folder.id, EQ
15751601
)
@@ -1583,7 +1609,7 @@ def execute(self):
15831609
items_count = len(items)
15841610
self.reporter.log_info(
15851611
f"Getting {items_count} annotations from "
1586-
f"{self._project.name}{f'/{self._folder.name}' if self._folder.name != 'root' else ''}."
1612+
f"{self._project.name}{f'/{self._folder.name}' if self._folder and self._folder.name != 'root' else ''}."
15871613
)
15881614
id_item_map = {i.id: i for i in items}
15891615
self.reporter.start_progress(
@@ -1602,11 +1628,13 @@ def execute(self):
16021628
try:
16031629
annotations = run_async(self.run_workers(large_items, small_items))
16041630
except Exception as e:
1631+
# todo remove
1632+
raise e
16051633
logger.error(e)
16061634
self._response.errors = AppException("Can't get annotations.")
16071635
return self._response
16081636
self.reporter.finish_progress()
1609-
self._response.data = self._prettify_annotations(annotations)
1637+
self._response.data = self._prettify_annotations(annotations) # noqa
16101638
return self._response
16111639

16121640

@@ -1634,15 +1662,10 @@ def __init__(
16341662
self._callback = callback
16351663
self._big_file_queue = None
16361664

1637-
def validate_item_names(self):
1638-
if self._item_names:
1639-
item_names = list(dict.fromkeys(self._item_names))
1640-
len_unique_items, len_items = len(item_names), len(self._item_names)
1641-
if len_unique_items < len_items:
1642-
self.reporter.log_info(
1643-
f"Dropping duplicates. Found {len_unique_items}/{len_items} unique items."
1644-
)
1645-
self._item_names = item_names
1665+
def validate_items(self):
1666+
self._item_names = GetAnnotations.items_duplication_validation(
1667+
self.reporter, self._item_names
1668+
)
16461669

16471670
@property
16481671
def destination(self) -> str:

src/superannotate/lib/infrastructure/controller.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -496,16 +496,16 @@ def __init__(self, service_provider: ServiceProvider, config: ConfigEntity):
496496
def list(
497497
self,
498498
project: ProjectEntity,
499-
folder: FolderEntity,
500-
item_names: List[str],
499+
folder: FolderEntity = None,
500+
items: Union[List[str], List[int]] = None,
501501
verbose=True,
502502
):
503503
use_case = usecases.GetAnnotations(
504504
config=self._config,
505505
reporter=Reporter(log_info=verbose, log_warning=verbose),
506506
project=project,
507507
folder=folder,
508-
item_names=item_names,
508+
items=items,
509509
service_provider=self.service_provider,
510510
)
511511
return use_case.execute()
@@ -879,8 +879,7 @@ def get_folder_by_id(self, folder_id: int, project_id: int):
879879
def get_project_by_id(self, project_id: int):
880880
response = self.projects.get_by_id(project_id=project_id)
881881
if response.errors:
882-
raise AppException(response.errors)
883-
882+
raise AppException("Project not found.")
884883
return response
885884

886885
def get_item_by_id(self, item_id: int, project: ProjectEntity):

src/superannotate/lib/infrastructure/services/annotation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ async def list_small_annotations(
142142
query_params = {
143143
"team_id": project.team_id,
144144
"project_id": project.id,
145-
"folder_id": folder.id,
145+
# "folder_id": folder.id,
146146
}
147147

148148
handler = StreamedAnnotations(

src/superannotate/lib/infrastructure/services/item.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from lib.core import entities
66
from lib.core.conditions import Condition
7+
from lib.core.entities import BaseItemEntity
78
from lib.core.enums import ProjectType
89
from lib.core.exceptions import AppException
910
from lib.core.exceptions import BackendError
@@ -12,6 +13,7 @@
1213
from lib.core.service_types import ImageResponse
1314
from lib.core.service_types import ItemListResponse
1415
from lib.core.service_types import PointCloudResponse
16+
from lib.core.service_types import ServiceResponse
1517
from lib.core.service_types import TiledResponse
1618
from lib.core.service_types import VideoResponse
1719
from lib.core.serviceproviders import BaseItemService
@@ -27,6 +29,7 @@ class ItemService(BaseItemService):
2729
URL_MOVE_MULTIPLE = "image/move"
2830
URL_SET_ANNOTATION_STATUSES = "image/updateAnnotationStatusBulk"
2931
URL_LIST_BY_NAMES = "images/getBulk"
32+
URL_LIST_BY_IDS = "images/getImagesByIds"
3033
URL_COPY_MULTIPLE = "images/copy-image-or-folders"
3134
URL_COPY_PROGRESS = "images/copy-image-progress"
3235
URL_DELETE_ITEMS = "image/delete/images"
@@ -75,6 +78,30 @@ def update(self, project: entities.ProjectEntity, item: entities.BaseItemEntity)
7578
params={"project_id": project.id},
7679
)
7780

81+
def list_by_ids(
82+
self,
83+
project: entities.ProjectEntity,
84+
ids: List[int],
85+
):
86+
chunk_size = 2000
87+
items = []
88+
response = None
89+
for i in range(0, len(ids), chunk_size):
90+
response = self.client.request(
91+
self.URL_LIST_BY_IDS,
92+
"post",
93+
data={
94+
"image_ids": ids[i : i + chunk_size], # noqa
95+
},
96+
params={"project_id": project.id},
97+
content_type=ServiceResponse,
98+
)
99+
if not response.ok:
100+
return response
101+
items.extend(response.data["images"])
102+
response.res_data = [BaseItemEntity(**i) for i in items]
103+
return response
104+
78105
def list_by_names(
79106
self,
80107
project: entities.ProjectEntity,

src/superannotate/lib/infrastructure/stream_data_handler.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ async def fetch(
4545
data: dict = None,
4646
params: dict = None,
4747
):
48-
kwargs = {"params": params, "json": {"folder_id": params.pop("folder_id")}}
48+
kwargs = {"params": params, "json": {}}
49+
if "folder_id" in kwargs["params"]:
50+
kwargs["json"] = {"folder_id": kwargs["params"].pop("folder_id")}
4951
if data:
5052
kwargs["json"].update(data)
5153
response = await session.request(method, url, **kwargs, timeout=TIMEOUT) # noqa

tests/integration/annotations/test_get_annotations.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,32 @@ def test_get_annotations(self):
4242
len(annotation_data["instances"]), len(annotations[0]["instances"])
4343
)
4444

45+
def test_get_annotations_by_ids(self):
46+
self._attach_items(count=4) # noqa
47+
48+
sa.create_annotation_classes_from_classes_json(
49+
self.PROJECT_NAME, f"{self.folder_path}/classes/classes.json"
50+
)
51+
_, _, _ = sa.upload_annotations_from_folder_to_project(
52+
self.PROJECT_NAME, self.folder_path
53+
)
54+
items = sa.search_items(self.PROJECT_NAME)
55+
56+
annotations = sa.get_annotations(self._project["id"], [i["id"] for i in items])
57+
58+
self.assertEqual(len(annotations), 4)
59+
60+
def test_get_annotations_by_wrong_item_ids(self):
61+
annotations = sa.get_annotations(self._project["id"], [1, 2, 3])
62+
63+
self.assertEqual(len(annotations), 0)
64+
65+
def test_get_annotations_by_wrong_project_ids(self):
66+
try:
67+
sa.get_annotations(1, [1, 2, 3])
68+
except Exception as e:
69+
self.assertEqual(str(e), "Project not found.")
70+
4571
@pytest.mark.flaky(reruns=3)
4672
def test_get_annotations_order(self):
4773
sa.upload_images_from_folder_to_project(

0 commit comments

Comments
 (0)