Skip to content

Commit b7df5a7

Browse files
committed
Update get/upload annotations interface
1 parent 9b393bb commit b7df5a7

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
@@ -1937,41 +1937,54 @@ def download_image(
19371937
logger.info(f"Downloaded image {image_name} to {local_dir_path} ")
19381938
return response.data
19391939

1940+
19401941
def upload_annotations(
1941-
self, project: NotEmptyStr, annotations: List[dict], keep_status: bool = None
1942+
self,
1943+
project: NotEmptyStr,
1944+
annotations: List[dict],
1945+
keep_status: bool = None,
1946+
*,
1947+
data_spec: Literal['default', 'multimodal'] = 'default'
19421948
):
1943-
"""Uploads a list of annotation dicts as annotations to the SuperAnnotate directory.
1949+
"""Uploads a list of annotation dictionaries to the specified SuperAnnotate project or folder.
19441950
1945-
:param project: project name or folder path (e.g., "project1/folder1")
1946-
:type project: str or dict
1951+
:param project: The project name or folder path where annotations will be uploaded
1952+
(e.g., "project1/folder1").
1953+
:type project: str
19471954
1948-
:param annotations: list of annotation dictionaries corresponding to SuperAnnotate format
1949-
:type annotations: list of dicts
1955+
:param annotations: A list of annotation dictionaries formatted according to the SuperAnnotate standards.
1956+
:type annotations: list of dict
19501957
1951-
:param keep_status: If False, the annotation status will be automatically
1952-
updated to "InProgress," otherwise the current status will be kept.
1953-
:type keep_status: bool
1958+
:param keep_status: If False, the annotation status will be automatically updated to "InProgress."
1959+
If True, the current status will remain unchanged.
1960+
:type keep_status: bool, optional
1961+
1962+
:param data_spec: Specifies the format for processing and transforming annotations before upload.
19541963
1964+
Options are:
1965+
- default: Retains the annotations in their original format.
1966+
- multimodal: Converts annotations for multimodal projects, optimizing for
1967+
compact and modality-specific data representation.
1968+
:type data_spec: str, optional
19551969
1956-
:return: a dictionary containing lists of successfully uploaded, failed and skipped name
1970+
:return: A dictionary containing the results of the upload, categorized into successfully uploaded,
1971+
failed, and skipped annotations.
19571972
:rtype: dict
19581973
1959-
Response Example:
1960-
::
1974+
Response Example::
19611975
19621976
{
19631977
"succeeded": [],
1964-
"failed":[],
1978+
"failed": [],
19651979
"skipped": []
19661980
}
19671981
1968-
Example Usage with JSONL Upload:
1969-
::
1982+
Example Usage with JSONL Upload for Multimodal Projects::
1983+
19701984
import json
19711985
from pathlib import Path
19721986
from superannotate import SAClient
19731987
1974-
# Assuming annotations are stored in JSONL format
19751988
annotations_path = Path("annotations.jsonl")
19761989
annotations = []
19771990
@@ -1980,16 +1993,16 @@ def upload_annotations(
19801993
for line in f:
19811994
annotations.append(json.loads(line))
19821995
1983-
# Initialize the Superannotate client
1996+
# Initialize the SuperAnnotate client
19841997
sa = SAClient()
19851998
19861999
# Call the upload_annotations function
1987-
response = client.upload_annotations(
2000+
response = sa.upload_annotations(
19882001
project="project1/folder1",
19892002
annotations=annotations,
1990-
keep_status=True
2003+
keep_status=True,
2004+
data_spec='multimodal'
19912005
)
1992-
19932006
"""
19942007
if keep_status is not None:
19952008
warnings.warn(
@@ -2005,6 +2018,7 @@ def upload_annotations(
20052018
annotations=annotations,
20062019
keep_status=keep_status,
20072020
user=self.controller.current_user,
2021+
output_format=data_spec
20082022
)
20092023
if response.errors:
20102024
raise AppException(response.errors)
@@ -2479,6 +2493,8 @@ def get_annotations(
24792493
self,
24802494
project: Union[NotEmptyStr, int],
24812495
items: Optional[Union[List[NotEmptyStr], List[int]]] = None,
2496+
*,
2497+
data_spec: Literal['default', 'multimodal'] = 'default'
24822498
):
24832499
"""Returns annotations for the given list of items.
24842500
@@ -2488,6 +2504,29 @@ def get_annotations(
24882504
:param items: item names. If None, all the items in the specified directory will be used.
24892505
:type items: list of strs or list of ints
24902506
2507+
:param data_spec: Specifies the format for processing and transforming annotations before upload.
2508+
2509+
Options are:
2510+
- default: Retains the annotations in their original format.
2511+
- multimodal: Converts annotations for multimodal projects, optimizing for
2512+
compact and modality-specific data representation.
2513+
2514+
:type data_spec: str, optional
2515+
2516+
Example Usage of Multimodal Projects::
2517+
2518+
from superannotate import SAClient
2519+
2520+
2521+
sa = SAClient()
2522+
2523+
# Call the upload_annotations function
2524+
response = sa.upload_annotations(
2525+
project="project1/folder1",
2526+
items=["item_1", "item_2"],
2527+
data_spec='multimodal'
2528+
)
2529+
24912530
:return: list of annotations
24922531
:rtype: list of dict
24932532
"""
@@ -2498,7 +2537,10 @@ def get_annotations(
24982537
folder = self.controller.get_folder_by_id(
24992538
project_id=project.id, folder_id=project.folder_id
25002539
).data
2501-
response = self.controller.annotations.list(project, folder, items)
2540+
response = self.controller.annotations.list(
2541+
project, folder, items,
2542+
transform_version='llmJsonV2' if data_spec == 'multimodal' else None
2543+
)
25022544
if response.errors:
25032545
raise AppException(response.errors)
25042546
return response.data
@@ -2821,7 +2863,7 @@ def list_items(
28212863
project: Union[NotEmptyStr, int],
28222864
folder: Optional[Union[NotEmptyStr, int]] = None,
28232865
*,
2824-
include: List[Literal["custom_metadata"]] = None,
2866+
include: List[Literal["custom_metadata", "category"]] = None,
28252867
**filters,
28262868
):
28272869
"""

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)