Skip to content

Commit 85dcdf7

Browse files
authored
Merge pull request #736 from superannotateai/FRIDAY_2985
Update get/upload annotations interface
2 parents d86a365 + b7df5a7 commit 85dcdf7

File tree

4 files changed

+79
-37
lines changed

4 files changed

+79
-37
lines changed

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ minversion = 3.7
33
log_cli=true
44
python_files = test_*.py
55
;pytest_plugins = ['pytest_profiling']
6-
;addopts = -n 4 --dist loadscope
6+
addopts = -n 4 --dist loadscope

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

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1941,41 +1941,54 @@ def download_image(
19411941
logger.info(f"Downloaded image {image_name} to {local_dir_path} ")
19421942
return response.data
19431943

1944+
19441945
def upload_annotations(
1945-
self, project: NotEmptyStr, annotations: List[dict], keep_status: bool = None
1946+
self,
1947+
project: NotEmptyStr,
1948+
annotations: List[dict],
1949+
keep_status: bool = None,
1950+
*,
1951+
data_spec: Literal['default', 'multimodal'] = 'default'
19461952
):
1947-
"""Uploads a list of annotation dicts as annotations to the SuperAnnotate directory.
1953+
"""Uploads a list of annotation dictionaries to the specified SuperAnnotate project or folder.
19481954
1949-
:param project: project name or folder path (e.g., "project1/folder1")
1950-
:type project: str or dict
1955+
:param project: The project name or folder path where annotations will be uploaded
1956+
(e.g., "project1/folder1").
1957+
:type project: str
19511958
1952-
:param annotations: list of annotation dictionaries corresponding to SuperAnnotate format
1953-
:type annotations: list of dicts
1959+
:param annotations: A list of annotation dictionaries formatted according to the SuperAnnotate standards.
1960+
:type annotations: list of dict
19541961
1955-
:param keep_status: If False, the annotation status will be automatically
1956-
updated to "InProgress," otherwise the current status will be kept.
1957-
:type keep_status: bool
1962+
:param keep_status: If False, the annotation status will be automatically updated to "InProgress."
1963+
If True, the current status will remain unchanged.
1964+
:type keep_status: bool, optional
1965+
1966+
:param data_spec: Specifies the format for processing and transforming annotations before upload.
19581967
1968+
Options are:
1969+
- default: Retains the annotations in their original format.
1970+
- multimodal: Converts annotations for multimodal projects, optimizing for
1971+
compact and modality-specific data representation.
1972+
:type data_spec: str, optional
19591973
1960-
:return: a dictionary containing lists of successfully uploaded, failed and skipped name
1974+
:return: A dictionary containing the results of the upload, categorized into successfully uploaded,
1975+
failed, and skipped annotations.
19611976
:rtype: dict
19621977
1963-
Response Example:
1964-
::
1978+
Response Example::
19651979
19661980
{
19671981
"succeeded": [],
1968-
"failed":[],
1982+
"failed": [],
19691983
"skipped": []
19701984
}
19711985
1972-
Example Usage with JSONL Upload:
1973-
::
1986+
Example Usage with JSONL Upload for Multimodal Projects::
1987+
19741988
import json
19751989
from pathlib import Path
19761990
from superannotate import SAClient
19771991
1978-
# Assuming annotations are stored in JSONL format
19791992
annotations_path = Path("annotations.jsonl")
19801993
annotations = []
19811994
@@ -1984,16 +1997,16 @@ def upload_annotations(
19841997
for line in f:
19851998
annotations.append(json.loads(line))
19861999
1987-
# Initialize the Superannotate client
2000+
# Initialize the SuperAnnotate client
19882001
sa = SAClient()
19892002
19902003
# Call the upload_annotations function
1991-
response = client.upload_annotations(
2004+
response = sa.upload_annotations(
19922005
project="project1/folder1",
19932006
annotations=annotations,
1994-
keep_status=True
2007+
keep_status=True,
2008+
data_spec='multimodal'
19952009
)
1996-
19972010
"""
19982011
if keep_status is not None:
19992012
warnings.warn(
@@ -2009,6 +2022,7 @@ def upload_annotations(
20092022
annotations=annotations,
20102023
keep_status=keep_status,
20112024
user=self.controller.current_user,
2025+
output_format=data_spec
20122026
)
20132027
if response.errors:
20142028
raise AppException(response.errors)
@@ -2483,6 +2497,8 @@ def get_annotations(
24832497
self,
24842498
project: Union[NotEmptyStr, int],
24852499
items: Optional[Union[List[NotEmptyStr], List[int]]] = None,
2500+
*,
2501+
data_spec: Literal['default', 'multimodal'] = 'default'
24862502
):
24872503
"""Returns annotations for the given list of items.
24882504
@@ -2492,6 +2508,29 @@ def get_annotations(
24922508
:param items: item names. If None, all the items in the specified directory will be used.
24932509
:type items: list of strs or list of ints
24942510
2511+
:param data_spec: Specifies the format for processing and transforming annotations before upload.
2512+
2513+
Options are:
2514+
- default: Retains the annotations in their original format.
2515+
- multimodal: Converts annotations for multimodal projects, optimizing for
2516+
compact and modality-specific data representation.
2517+
2518+
:type data_spec: str, optional
2519+
2520+
Example Usage of Multimodal Projects::
2521+
2522+
from superannotate import SAClient
2523+
2524+
2525+
sa = SAClient()
2526+
2527+
# Call the upload_annotations function
2528+
response = sa.upload_annotations(
2529+
project="project1/folder1",
2530+
items=["item_1", "item_2"],
2531+
data_spec='multimodal'
2532+
)
2533+
24952534
:return: list of annotations
24962535
:rtype: list of dict
24972536
"""
@@ -2502,7 +2541,10 @@ def get_annotations(
25022541
folder = self.controller.get_folder_by_id(
25032542
project_id=project.id, folder_id=project.folder_id
25042543
).data
2505-
response = self.controller.annotations.list(project, folder, items)
2544+
response = self.controller.annotations.list(
2545+
project, folder, items,
2546+
transform_version='llmJsonV2' if data_spec == 'multimodal' else None
2547+
)
25062548
if response.errors:
25072549
raise AppException(response.errors)
25082550
return response.data
@@ -2825,7 +2867,7 @@ def list_items(
28252867
project: Union[NotEmptyStr, int],
28262868
folder: Optional[Union[NotEmptyStr, int]] = None,
28272869
*,
2828-
include: List[Literal["custom_metadata"]] = None,
2870+
include: List[Literal["custom_metadata", "category"]] = None,
28292871
**filters,
28302872
):
28312873
"""

src/superannotate/lib/infrastructure/controller.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -780,9 +780,9 @@ def upload_multiple(
780780
annotations: List[dict],
781781
keep_status: bool,
782782
user: UserEntity,
783-
transform_version: str = None,
783+
output_format: str = None,
784784
):
785-
if project.type == ProjectType.MULTIMODAL:
785+
if project.type == ProjectType.MULTIMODAL and output_format == 'multimodal':
786786
use_case = usecases.UploadMultiModalAnnotationsUseCase(
787787
reporter=Reporter(),
788788
project=project,
@@ -791,7 +791,7 @@ def upload_multiple(
791791
service_provider=self.service_provider,
792792
keep_status=keep_status,
793793
user=user,
794-
transform_version=transform_version,
794+
transform_version='llmJsonV2',
795795
)
796796
else:
797797
use_case = usecases.UploadAnnotationsUseCase(
@@ -802,7 +802,7 @@ def upload_multiple(
802802
service_provider=self.service_provider,
803803
keep_status=keep_status,
804804
user=user,
805-
transform_version=transform_version,
805+
transform_version=None,
806806
)
807807
return use_case.execute()
808808

tests/integration/annotations/test_upload_annotations.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -179,21 +179,21 @@ def test_upload_to_root(self):
179179
data[0]["metadata"]["folder_name"] = ""
180180
data[0]["metadata"]["folder_name"] = "root"
181181
del data[0]["metadata"]["folder_name"]
182-
response = sa.upload_annotations(self.PROJECT_NAME, annotations=data)
182+
response = sa.upload_annotations(self.PROJECT_NAME, annotations=data, data_spec='multimodal')
183183
assert len(response["succeeded"]) == 3
184-
annotations = sa.get_annotations(f"{self.PROJECT_NAME}")
185-
assert all([len(i["instances"]) == 3 for i in annotations]) is True
184+
annotations = sa.get_annotations(f"{self.PROJECT_NAME}", data_spec='multimodal')
185+
assert all([len(i["data"].keys()) == 3 for i in annotations]) is True
186186

187187
def test_upload_from_root_to_folder(self):
188188
with open(self.JSONL_ANNOTATIONS_PATH) as f:
189189
data = [json.loads(line) for line in f]
190-
response = sa.upload_annotations(self.PROJECT_NAME, annotations=data)
190+
response = sa.upload_annotations(self.PROJECT_NAME, annotations=data, data_spec='multimodal')
191191
assert len(response["succeeded"]) == 3
192-
annotations = sa.get_annotations(f"{self.PROJECT_NAME}/test_folder")
193-
assert all([len(i["instances"]) == 3 for i in annotations]) is True
192+
annotations = sa.get_annotations(f"{self.PROJECT_NAME}/test_folder", data_spec='multimodal')
193+
assert all([len(i["data"].keys()) == 3 for i in annotations]) is True
194194
folders = sa.search_folders(self.PROJECT_NAME)
195195
assert len(folders) == 1
196-
sa.upload_annotations(self.PROJECT_NAME, annotations=data)
196+
sa.upload_annotations(self.PROJECT_NAME, annotations=data, data_spec='multimodal')
197197
assert len(folders) == 1
198198

199199
def test_error_upload_from_folder_to_folder_(self):
@@ -204,11 +204,11 @@ def test_error_upload_from_folder_to_folder_(self):
204204
AppException,
205205
"You can't include a folder when uploading from within a folder.",
206206
):
207-
sa.upload_annotations(f"{self.PROJECT_NAME}/tmp", annotations=data)
207+
sa.upload_annotations(f"{self.PROJECT_NAME}/tmp", annotations=data, data_spec='multimodal')
208208

209209
def test_upload_with_categories(self):
210210
with open(self.JSONL_ANNOTATIONS_WITH_CATEGORIES_PATH) as f:
211211
data = [json.loads(line) for line in f]
212-
sa.upload_annotations(f"{self.PROJECT_NAME}", annotations=data)
213-
annotations = sa.get_annotations(f"{self.PROJECT_NAME}/test_folder")
212+
sa.upload_annotations(f"{self.PROJECT_NAME}", annotations=data, data_spec='multimodal')
213+
annotations = sa.get_annotations(f"{self.PROJECT_NAME}/test_folder", data_spec='multimodal')
214214
assert len(annotations) == 3

0 commit comments

Comments
 (0)