Skip to content

Commit c19eda0

Browse files
Narek MkhitaryanNarek Mkhitaryan
authored andcommitted
resolve develop merge conflicts
2 parents 99f08ac + 85dcdf7 commit c19eda0

File tree

18 files changed

+723
-91
lines changed

18 files changed

+723
-91
lines changed

docs/source/api_reference/api_project.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Projects
55
.. _ref_search_projects:
66
.. automethod:: superannotate.SAClient.create_project
77
.. automethod:: superannotate.SAClient.search_projects
8+
.. automethod:: superannotate.SAClient.list_projects
89
.. automethod:: superannotate.SAClient.clone_project
910
.. automethod:: superannotate.SAClient.rename_project
1011
.. automethod:: superannotate.SAClient.delete_project

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.29"
6+
__version__ = "4.4.30b2"
77

88
os.environ.update({"sa_version": __version__})
99
sys.path.append(os.path.split(os.path.realpath(__file__))[0])

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

Lines changed: 104 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -298,16 +298,25 @@ def get_component_config(self, project: Union[NotEmptyStr, int], component_id: s
298298
def retrieve_context(
299299
component_data: List[dict], component_pk: str
300300
) -> Tuple[bool, typing.Any]:
301-
for component in component_data:
302-
if (
303-
component["type"] == "webComponent"
304-
and component["id"] == component_pk
305-
):
306-
return True, component.get("context")
307-
if component["type"] == "group" and "children" in component:
308-
found, val = retrieve_context(component["children"], component_pk)
309-
if found:
310-
return found, val
301+
try:
302+
for component in component_data:
303+
if (
304+
component["type"] == "webComponent"
305+
and component["id"] == component_pk
306+
):
307+
return True, component.get("context")
308+
if (
309+
component["type"] in ("group", "grid")
310+
and "children" in component
311+
):
312+
found, val = retrieve_context(
313+
component["children"], component_pk
314+
)
315+
if found:
316+
return found, val
317+
except KeyError as e:
318+
logger.debug("Got key error:", component_data)
319+
raise e
311320
return False, None
312321

313322
project = (
@@ -1968,33 +1977,70 @@ def download_image(
19681977
return response.data
19691978

19701979
def upload_annotations(
1971-
self, project: NotEmptyStr, annotations: List[dict], keep_status: bool = None
1980+
self,
1981+
project: NotEmptyStr,
1982+
annotations: List[dict],
1983+
keep_status: bool = None,
1984+
*,
1985+
data_spec: Literal["default", "multimodal"] = "default",
19721986
):
1973-
"""Uploads a list of annotation dicts as annotations to the SuperAnnotate directory.
1987+
"""Uploads a list of annotation dictionaries to the specified SuperAnnotate project or folder.
19741988
1975-
:param project: project name or folder path (e.g., "project1/folder1")
1976-
:type project: str or dict
1989+
:param project: The project name or folder path where annotations will be uploaded
1990+
(e.g., "project1/folder1").
1991+
:type project: str
19771992
1978-
:param annotations: list of annotation dictionaries corresponding to SuperAnnotate format
1979-
:type annotations: list of dicts
1993+
:param annotations: A list of annotation dictionaries formatted according to the SuperAnnotate standards.
1994+
:type annotations: list of dict
19801995
1981-
:param keep_status: If False, the annotation status will be automatically
1982-
updated to "InProgress," otherwise the current status will be kept.
1983-
:type keep_status: bool
1996+
:param keep_status: If False, the annotation status will be automatically updated to "InProgress."
1997+
If True, the current status will remain unchanged.
1998+
:type keep_status: bool, optional
1999+
2000+
:param data_spec: Specifies the format for processing and transforming annotations before upload.
19842001
2002+
Options are:
2003+
- default: Retains the annotations in their original format.
2004+
- multimodal: Converts annotations for multimodal projects, optimizing for
2005+
compact and modality-specific data representation.
2006+
:type data_spec: str, optional
19852007
1986-
:return: a dictionary containing lists of successfully uploaded, failed and skipped name
2008+
:return: A dictionary containing the results of the upload, categorized into successfully uploaded,
2009+
failed, and skipped annotations.
19872010
:rtype: dict
19882011
1989-
Response Example:
1990-
::
2012+
Response Example::
19912013
19922014
{
19932015
"succeeded": [],
1994-
"failed":[],
2016+
"failed": [],
19952017
"skipped": []
19962018
}
19972019
2020+
Example Usage with JSONL Upload for Multimodal Projects::
2021+
2022+
import json
2023+
from pathlib import Path
2024+
from superannotate import SAClient
2025+
2026+
annotations_path = Path("annotations.jsonl")
2027+
annotations = []
2028+
2029+
# Reading the JSONL file and converting it into a list of dictionaries
2030+
with annotations_path.open("r", encoding="utf-8") as f:
2031+
for line in f:
2032+
annotations.append(json.loads(line))
2033+
2034+
# Initialize the SuperAnnotate client
2035+
sa = SAClient()
2036+
2037+
# Call the upload_annotations function
2038+
response = sa.upload_annotations(
2039+
project="project1/folder1",
2040+
annotations=annotations,
2041+
keep_status=True,
2042+
data_spec='multimodal'
2043+
)
19982044
"""
19992045
if keep_status is not None:
20002046
warnings.warn(
@@ -2010,6 +2056,7 @@ def upload_annotations(
20102056
annotations=annotations,
20112057
keep_status=keep_status,
20122058
user=self.controller.current_user,
2059+
output_format=data_spec,
20132060
)
20142061
if response.errors:
20152062
raise AppException(response.errors)
@@ -2484,6 +2531,8 @@ def get_annotations(
24842531
self,
24852532
project: Union[NotEmptyStr, int],
24862533
items: Optional[Union[List[NotEmptyStr], List[int]]] = None,
2534+
*,
2535+
data_spec: Literal["default", "multimodal"] = "default",
24872536
):
24882537
"""Returns annotations for the given list of items.
24892538
@@ -2493,6 +2542,29 @@ def get_annotations(
24932542
:param items: item names. If None, all the items in the specified directory will be used.
24942543
:type items: list of strs or list of ints
24952544
2545+
:param data_spec: Specifies the format for processing and transforming annotations before upload.
2546+
2547+
Options are:
2548+
- default: Retains the annotations in their original format.
2549+
- multimodal: Converts annotations for multimodal projects, optimizing for
2550+
compact and modality-specific data representation.
2551+
2552+
:type data_spec: str, optional
2553+
2554+
Example Usage of Multimodal Projects::
2555+
2556+
from superannotate import SAClient
2557+
2558+
2559+
sa = SAClient()
2560+
2561+
# Call the upload_annotations function
2562+
response = sa.upload_annotations(
2563+
project="project1/folder1",
2564+
items=["item_1", "item_2"],
2565+
data_spec='multimodal'
2566+
)
2567+
24962568
:return: list of annotations
24972569
:rtype: list of dict
24982570
"""
@@ -2503,7 +2575,12 @@ def get_annotations(
25032575
folder = self.controller.get_folder_by_id(
25042576
project_id=project.id, folder_id=project.folder_id
25052577
).data
2506-
response = self.controller.annotations.list(project, folder, items)
2578+
response = self.controller.annotations.list(
2579+
project,
2580+
folder,
2581+
items,
2582+
transform_version="llmJsonV2" if data_spec == "multimodal" else None,
2583+
)
25072584
if response.errors:
25082585
raise AppException(response.errors)
25092586
return response.data
@@ -2826,7 +2903,7 @@ def list_items(
28262903
project: Union[NotEmptyStr, int],
28272904
folder: Optional[Union[NotEmptyStr, int]] = None,
28282905
*,
2829-
include: List[Literal["custom_metadata"]] = None,
2906+
include: List[Literal["custom_metadata", "category"]] = None,
28302907
**filters,
28312908
):
28322909
"""
@@ -2963,9 +3040,8 @@ def list_items(
29633040
for i in res:
29643041
i.custom_metadata = item_custom_fields[i.id]
29653042
exclude = {"meta", "annotator_email", "qa_email"}
2966-
if include:
2967-
if "custom_metadata" not in include:
2968-
exclude.add("custom_metadata")
3043+
if not include_custom_metadata:
3044+
exclude.add("custom_metadata")
29693045
return BaseSerializer.serialize_iterable(res, exclude=exclude)
29703046

29713047
def list_projects(

src/superannotate/lib/core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ def setup_logging(level=DEFAULT_LOGGING_LEVEL, file_path=LOG_FILE_LOCATION):
179179
"Tokenization",
180180
"ImageAutoAssignEnable",
181181
"TemplateState",
182+
"CategorizeItems",
182183
]
183184

184185
__alL__ = (

src/superannotate/lib/core/entities/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from lib.core.entities.items import TiledEntity
1313
from lib.core.entities.items import VideoEntity
1414
from lib.core.entities.project import AttachmentEntity
15+
from lib.core.entities.project import CategoryEntity
1516
from lib.core.entities.project import ContributorEntity
1617
from lib.core.entities.project import ProjectEntity
1718
from lib.core.entities.project import SettingEntity
@@ -41,6 +42,7 @@
4142
# project
4243
"ProjectEntity",
4344
"WorkflowEntity",
45+
"CategoryEntity",
4446
"ContributorEntity",
4547
"ConfigEntity",
4648
"StepEntity",

src/superannotate/lib/core/entities/project.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,8 @@ def is_system(self):
180180

181181
class Config:
182182
extra = Extra.ignore
183+
184+
185+
class CategoryEntity(BaseModel):
186+
id: Optional[int]
187+
name: Optional[str]

src/superannotate/lib/core/service_types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,10 @@ class ProjectResponse(ServiceResponse):
227227
res_data: entities.ProjectEntity = None
228228

229229

230+
class ListCategoryResponse(ServiceResponse):
231+
res_data: List[entities.CategoryEntity] = None
232+
233+
230234
class WorkflowResponse(ServiceResponse):
231235
res_data: entities.WorkflowEntity = None
232236

src/superannotate/lib/core/serviceproviders.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from lib.core.service_types import FolderListResponse
1616
from lib.core.service_types import FolderResponse
1717
from lib.core.service_types import IntegrationListResponse
18+
from lib.core.service_types import ListCategoryResponse
1819
from lib.core.service_types import ProjectListResponse
1920
from lib.core.service_types import ProjectResponse
2021
from lib.core.service_types import ServiceResponse
@@ -125,6 +126,16 @@ def list_projects(
125126
) -> WMProjectListResponse:
126127
raise NotImplementedError
127128

129+
@abstractmethod
130+
def list_project_categories(self, project_id: int) -> ListCategoryResponse:
131+
raise NotImplementedError
132+
133+
@abstractmethod
134+
def create_project_categories(
135+
self, project_id: int, categories: List[str]
136+
) -> ServiceResponse:
137+
raise NotImplementedError
138+
128139

129140
class BaseProjectService(SuperannotateServiceProvider):
130141
@abstractmethod
@@ -388,6 +399,12 @@ def delete_multiple(
388399
) -> ServiceResponse:
389400
raise NotImplementedError
390401

402+
@abstractmethod
403+
def bulk_attach_categories(
404+
self, project_id: int, folder_id: int, item_category_map: Dict[int, int]
405+
) -> bool:
406+
raise NotImplementedError
407+
391408

392409
class BaseAnnotationService(SuperannotateServiceProvider):
393410
@abstractmethod

0 commit comments

Comments
 (0)