Skip to content

Commit bf09d14

Browse files
authored
Merge pull request #643 from superannotateai/develop
Develop
2 parents 4eb73ce + 469514d commit bf09d14

File tree

14 files changed

+208
-78
lines changed

14 files changed

+208
-78
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ All release highlights of this project will be documented in this file.
99
4.4.15 - August 20, 2023
1010
_______________________
1111

12+
**Added**
13+
14+
- Support for `relationship` class types in the document project.
15+
16+
17+
4.4.14 - August 20, 2023
18+
_______________________
19+
1220
**Added**
1321

1422
- New project type support `CustomEditor`.

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ email-validator~=2.0
88
pandas~=1.3
99
ffmpeg-python~=0.2
1010
pillow~=9.5
11+
tqdm~=4.66.1
12+
requests~=2.31.0
1113
aiofiles==23.1.0
12-
requests==2.31.0
13-
tqdm==4.64.0
1414
fire==0.4.0
1515
mixpanel==4.8.3
1616
jsonschema==3.2.0

src/superannotate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44

55

6-
__version__ = "4.4.14"
6+
__version__ = "4.4.15"
77

88
sys.path.append(os.path.split(os.path.realpath(__file__))[0])
99

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
from lib.core.entities.classes import AttributeGroup
5656
from lib.core.entities.integrations import IntegrationEntity
5757
from lib.core.enums import ImageQuality
58+
from lib.core.enums import ProjectType
59+
from lib.core.enums import ClassTypeEnum
5860
from lib.core.exceptions import AppException
5961
from lib.core.types import MLModel
6062
from lib.core.types import PriorityScoreEntity
@@ -72,11 +74,16 @@
7274
PROJECT_STATUS = Literal["NotStarted", "InProgress", "Completed", "OnHold"]
7375

7476
PROJECT_TYPE = Literal[
75-
"Vector", "Pixel", "Video", "Document", "Tiled", "Other", "PointCloud", "CustomEditor"
77+
"Vector",
78+
"Pixel",
79+
"Video",
80+
"Document",
81+
"Tiled",
82+
"Other",
83+
"PointCloud",
84+
"CustomEditor",
7685
]
7786

78-
CLASS_TYPE = Literal["object", "tag"]
79-
8087
ANNOTATION_STATUS = Literal[
8188
"NotStarted", "InProgress", "QualityCheck", "Returned", "Completed", "Skipped"
8289
]
@@ -1310,7 +1317,7 @@ def create_annotation_class(
13101317
name: NotEmptyStr,
13111318
color: NotEmptyStr,
13121319
attribute_groups: Optional[List[AttributeGroup]] = None,
1313-
class_type: CLASS_TYPE = "object",
1320+
class_type: str = "object",
13141321
):
13151322
"""Create annotation class in project
13161323
@@ -1331,7 +1338,7 @@ def create_annotation_class(
13311338
- "name"
13321339
:type attribute_groups: list of dicts
13331340
1334-
:param class_type: class type. Should be either "object" or "tag"
1341+
:param class_type: class type. Should be either "object" or "tag". Document project type can also have "relationship" type of classes.
13351342
:type class_type: str
13361343
13371344
:return: new class metadata
@@ -1405,6 +1412,13 @@ def create_annotation_class(
14051412
except ValidationError as e:
14061413
raise AppException(wrap_error(e))
14071414
project = self.controller.projects.get_by_name(project).data
1415+
if (
1416+
project.type != ProjectType.DOCUMENT
1417+
and annotation_class.type == ClassTypeEnum.RELATIONSHIP
1418+
):
1419+
raise AppException(
1420+
f"{annotation_class.type.name} class type is not supported in {project.type.name} project."
1421+
)
14081422
response = self.controller.annotation_classes.create(
14091423
project=project, annotation_class=annotation_class
14101424
)
@@ -1514,6 +1528,7 @@ def download_export(
15141528
to_s3_bucket=None,
15151529
):
15161530
"""Download prepared export.
1531+
15171532
:param project: project name
15181533
:type project: str
15191534
:param export: export name
@@ -2520,6 +2535,9 @@ def attach_items(
25202535
“Completed”
25212536
“Skipped”
25222537
:type annotation_status: str
2538+
2539+
:return: uploaded, failed and duplicated item names
2540+
:rtype: tuple of list of strs
25232541
"""
25242542

25252543
project_name, folder_name = extract_project_folder(project)

src/superannotate/lib/core/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ class AnnotationStatus(BaseTitledEnum):
157157
class ClassTypeEnum(BaseTitledEnum):
158158
OBJECT = "object", 1
159159
TAG = "tag", 2
160+
RELATIONSHIP = "relationship", 3
160161

161162
@classmethod
162163
def get_value(cls, name):

src/superannotate/lib/core/serviceproviders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ async def upload_small_annotations(
367367
self,
368368
project: entities.ProjectEntity,
369369
folder: entities.FolderEntity,
370-
items_name_file_map: Dict[str, io.StringIO],
370+
items_name_data_map: Dict[str, dict],
371371
) -> UploadAnnotationsResponse:
372372
raise NotImplementedError
373373

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

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
from lib.core.types import PriorityScoreEntity
4848
from lib.core.usecases.base import BaseReportableUseCase
4949
from lib.core.video_convertor import VideoFrameGenerator
50+
from lib.infrastructure.utils import divide_to_chunks
5051
from pydantic import BaseModel
5152

5253
logger = logging.getLogger("sa")
@@ -119,11 +120,6 @@ def get_or_raise(response: ServiceResponse):
119120
raise AppException(response.error)
120121

121122

122-
def divide_to_chunks(it, size):
123-
it = iter(it)
124-
return iter(lambda: tuple(islice(it, size)), ())
125-
126-
127123
def log_report(
128124
report: Report,
129125
):
@@ -148,7 +144,6 @@ class ItemToUpload(BaseModel):
148144
item: BaseItemEntity
149145
annotation_json: Optional[dict]
150146
path: Optional[str]
151-
file: Optional[io.StringIO]
152147
file_size: Optional[int]
153148
mask: Optional[io.BytesIO]
154149

@@ -186,7 +181,7 @@ async def upload_small_annotations(
186181
report: Report,
187182
callback: Callable = None,
188183
):
189-
async def upload(_chunk):
184+
async def upload(_chunk: List[ItemToUpload]):
190185
failed_annotations, missing_classes, missing_attr_groups, missing_attrs = (
191186
[],
192187
[],
@@ -197,7 +192,7 @@ async def upload(_chunk):
197192
response = await service_provider.annotations.upload_small_annotations(
198193
project=project,
199194
folder=folder,
200-
items_name_file_map={i.item.name: i.file for i in chunk},
195+
items_name_data_map={i.item.name: i.annotation_json for i in chunk},
201196
)
202197
if response.ok:
203198
if response.data.failed_items: # noqa
@@ -221,9 +216,9 @@ async def upload(_chunk):
221216
reporter.update_progress(len(chunk))
222217

223218
_size = 0
224-
chunk = []
219+
chunk: List[ItemToUpload] = []
225220
while True:
226-
item_data = await queue.get()
221+
item_data: ItemToUpload = await queue.get()
227222
queue.task_done()
228223
if not item_data:
229224
queue.put_nowait(None)
@@ -253,11 +248,14 @@ async def upload_big_annotations(
253248
):
254249
async def _upload_big_annotation(item_data: ItemToUpload) -> Tuple[str, bool]:
255250
try:
251+
buff = io.StringIO()
252+
json.dump(item_data.annotation_json, buff, allow_nan=False)
253+
buff.seek(0)
256254
is_uploaded = await service_provider.annotations.upload_big_annotation(
257255
project=project,
258256
folder=folder,
259257
item_id=item_data.item.id,
260-
data=item_data.file,
258+
data=buff,
261259
chunk_size=5 * 1024 * 1024,
262260
)
263261
if is_uploaded and callback:
@@ -335,15 +333,14 @@ async def distribute_queues(self, items_to_upload: List[ItemToUpload]):
335333
for idx, (item_to_upload, processed) in enumerate(data):
336334
if not processed:
337335
try:
338-
item_to_upload.file = io.StringIO()
336+
file = io.StringIO()
339337
json.dump(
340338
item_to_upload.annotation_json,
341-
item_to_upload.file,
339+
file,
342340
allow_nan=False,
343341
)
344-
item_to_upload.file.seek(0, os.SEEK_END)
345-
item_to_upload.file_size = item_to_upload.file.tell()
346-
item_to_upload.file.seek(0)
342+
file.seek(0, os.SEEK_END)
343+
item_to_upload.file_size = file.tell()
347344
while True:
348345
if item_to_upload.file_size > BIG_FILE_THRESHOLD:
349346
if self._big_files_queue.qsize() > 32:
@@ -723,13 +720,10 @@ async def distribute_queues(self, items_to_upload: List[ItemToUpload]):
723720
if not processed:
724721
try:
725722
(
726-
annotation,
723+
item_to_upload.annotation_json,
727724
item_to_upload.mask,
728725
item_to_upload.file_size,
729726
) = await self.get_annotation(item_to_upload.path)
730-
item_to_upload.file = io.StringIO()
731-
json.dump(annotation, item_to_upload.file, allow_nan=False)
732-
item_to_upload.file.seek(0)
733727
while True:
734728
if item_to_upload.file_size > BIG_FILE_THRESHOLD:
735729
if self._big_files_queue.qsize() > 32:
@@ -1018,7 +1012,7 @@ def execute(self):
10181012
self._service_provider.annotations.upload_small_annotations(
10191013
project=self._project,
10201014
folder=self._folder,
1021-
items_name_file_map={self._image.name: annotation_file},
1015+
items_name_data_map={self._image.name: annotation_json},
10221016
)
10231017
)
10241018
if response.ok:

0 commit comments

Comments
 (0)