Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ History

All release highlights of this project will be documented in this file.

4.4.26 - Oct 29, 2024
________________________

**Added**

- ``SAClient.copy_items/move_items`` method, added the ability to copy/move categories and duplicate strategies ("skip", "replace", "replace_annotations_only").
**Updated**

- Fixed `SAClient.get_annotations() To handle annotations that contain all UTF-8 characters.`
- Renamed project type GenAI to Multimodal



4.4.25 - Oct 7, 2024
________________________

Expand Down
4 changes: 2 additions & 2 deletions src/superannotate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys


__version__ = "4.4.25"
__version__ = "4.4.26"

os.environ.update({"sa_version": __version__})
sys.path.append(os.path.split(os.path.realpath(__file__))[0])
Expand All @@ -18,7 +18,7 @@
from lib.app.input_converters import convert_project_type
from lib.app.input_converters import export_annotation
from lib.app.input_converters import import_annotation
from lib.app.interface.sdk_interface import SAClient
from superannotate.lib.app.interface.sdk_interface import SAClient


SESSIONS = {}
Expand Down
80 changes: 59 additions & 21 deletions src/superannotate/lib/app/interface/sdk_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"Document",
"Tiled",
"PointCloud",
"GenAI",
"Multimodal",
]


Expand Down Expand Up @@ -307,7 +307,7 @@ def create_project(
:param project_description: the new project's description
:type project_description: str

:param project_type: the new project type, Vector, Pixel, Video, Document, Tiled, PointCloud, GenAI.
:param project_type: the new project type, Vector, Pixel, Video, Document, Tiled, PointCloud, Multimodal.
:type project_type: str

:param settings: list of settings objects
Expand Down Expand Up @@ -1215,11 +1215,11 @@ def prepare_export(
:param only_pinned: enable only pinned output in export. This option disables all other types of output.
:type only_pinned: bool

:param kwargs:
Arbitrary kwargs:
:param kwargs: Arbitrary keyword arguments:

- integration_name: can be provided which will be used as a storage to store export file
- format: can be CSV for the Gen AI projects
- integration_name: The name of the integration within the platform that is being used.
- format: The format in which the data will be exported in multimodal projects.
It can be either CSV or JSON. If None, the data will be exported in the default JSON format.
:return: metadata object of the prepared export
:rtype: dict

Expand All @@ -1232,7 +1232,7 @@ def prepare_export(
project = "Project Name",
folder_names = ["Folder 1", "Folder 2"],
annotation_statuses = ["Completed","QualityCheck"],
export_type = "CSV"
format = "CSV"
)

client.download_export("Project Name", export, "path_to_download")
Expand Down Expand Up @@ -2318,7 +2318,7 @@ def invite_contributors_to_team(

def get_annotations(
self,
project: Union[int, NotEmptyStr],
project: Union[NotEmptyStr, int],
items: Optional[Union[List[NotEmptyStr], List[int]]] = None,
):
"""Returns annotations for the given list of items.
Expand Down Expand Up @@ -2663,23 +2663,23 @@ def search_items(

def list_items(
self,
project: Union[int, str],
folder: Optional[Union[int, str]] = None,
project: Union[NotEmptyStr, int],
folder: Optional[Union[NotEmptyStr, int]] = None,
*,
include: List[Literal["custom_metadata"]] = None,
**filters,
):
"""
Search items by filtering criteria.

:param project: The project name, project ID, or folder path (e.g., "project1/folder1") to search within.
:param project: The project name, project ID, or folder path (e.g., "project1") to search within.
This can refer to the root of the project or a specific subfolder.
:type project: Union[int, str]
:type project: Union[NotEmptyStr, int]

:param folder: The folder name or ID to search within. If None, the search will be done in the root folder of
the project. If both “project” and “folder” specify folders, the “project”
value will take priority.
:type folder: Union[int, str], optional
:type folder: Union[NotEmptyStr, int], optional

:param include: Specifies additional fields to include in the response.

Expand All @@ -2694,8 +2694,8 @@ def list_items(


Supported operations:
- __ne: Value is in the list.
- __in: Value is not equal.
- __ne: Value is not equal.
- __in: Value is in the list.
- __notin: Value is not in the list.
- __contains: Value has the substring.
- __starts: Value starts with the prefix.
Expand All @@ -2710,6 +2710,9 @@ def list_items(
- name__contains: str
- name__starts: str
- name__ends: str
- annotation_status: str
- annotation_status__in: list[str]
- annotation_status__ne: list[str]
- approval_status: Literal["Approved", "Disapproved", None]
- assignments__user_id: str
- assignments__user_id__ne: str
Expand Down Expand Up @@ -2927,26 +2930,46 @@ def copy_items(
source: Union[NotEmptyStr, dict],
destination: Union[NotEmptyStr, dict],
items: Optional[List[NotEmptyStr]] = None,
include_annotations: Optional[bool] = True,
include_annotations: bool = True,
duplicate_strategy: Literal[
"skip", "replace", "replace_annotations_only"
] = "skip",
):
"""Copy images in bulk between folders in a project

:param source: project name or folder path to select items from (e.g., “project1/folder1”).
:param source: project name (root) or folder path to pick items from (e.g., “project1/folder1”).
:type source: str

:param destination: project name (root) or folder path to place copied items.
:param destination: project name (root) or folder path to place copied items (e.g., “project1/folder2”).
:type destination: str

:param items: names of items to copy. If None, all items from the source directory will be copied.
:type items: list of str

:param include_annotations: enables annotations copy
:param include_annotations: enables the copying of item data, including annotations, status, priority score,
approval state, and category. If set to False, only the items will be copied without additional data.
:type include_annotations: bool

:param duplicate_strategy: Specifies the strategy for handling duplicate items in the destination.
The default value is "skip".

- "skip": skips duplicate items in the destination and continues with the next item.
- "replace": replaces the annotations, status, priority score, approval state, and category of duplicate items.
- "replace_annotations_only": replaces only the annotations of duplicate items,
leaving other data (status, priority score, approval state, and category) unchanged.

:type duplicate_strategy: Literal["skip", "replace", "replace_annotations_only"]

:return: list of skipped item names
:rtype: list of strs
"""

if not include_annotations and duplicate_strategy != "skip":
duplicate_strategy = "skip"
logger.warning(
"Copy operation continuing without annotations and metadata due to include_annotations=False."
)

project_name, source_folder = extract_project_folder(source)
to_project_name, destination_folder = extract_project_folder(destination)
if project_name != to_project_name:
Expand All @@ -2960,6 +2983,7 @@ def copy_items(
to_folder=to_folder,
item_names=items,
include_annotations=include_annotations,
duplicate_strategy=duplicate_strategy,
)
if response.errors:
raise AppException(response.errors)
Expand All @@ -2971,18 +2995,31 @@ def move_items(
source: Union[NotEmptyStr, dict],
destination: Union[NotEmptyStr, dict],
items: Optional[List[NotEmptyStr]] = None,
duplicate_strategy: Literal[
"skip", "replace", "replace_annotations_only"
] = "skip",
):
"""Move images in bulk between folders in a project

:param source: project name or folder path to pick items from (e.g., “project1/folder1”).
:param source: project name (root) or folder path to pick items from (e.g., “project1/folder1”).
:type source: str

:param destination: project name (root) or folder path to move items to.
:param destination: project name (root) or folder path to move items to (e.g., “project1/folder2”).
:type destination: str

:param items: names of items to move. If None, all items from the source directory will be moved.
:type items: list of str

:param duplicate_strategy: Specifies the strategy for handling duplicate items in the destination.
The default value is "skip".

- "skip": skips duplicate items in the destination and continues with the next item.
- "replace": replaces the annotations, status, priority score, approval state, and category of duplicate items.
- "replace_annotations_only": replaces only the annotations of duplicate items,
leaving other data (status, priority score, approval state, and category) unchanged.

:type duplicate_strategy: Literal["skip", "replace", "replace_annotations_only"]

:return: list of skipped item names
:rtype: list of strs
"""
Expand All @@ -3000,6 +3037,7 @@ def move_items(
from_folder=source_folder,
to_folder=destination_folder,
item_names=items,
duplicate_strategy=duplicate_strategy,
)
if response.errors:
raise AppException(response.errors)
Expand Down
1 change: 1 addition & 0 deletions src/superannotate/lib/core/entities/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class TeamEntity(BaseModel):
users: Optional[List[UserEntity]]
pending_invitations: Optional[List[Any]]
creator_id: Optional[str]
owner_id: Optional[str]

class Config:
extra = Extra.ignore
Expand Down
2 changes: 1 addition & 1 deletion src/superannotate/lib/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class ProjectType(BaseTitledEnum):
TILED = "Tiled", 5
OTHER = "Other", 6
POINT_CLOUD = "PointCloud", 7
GEN_AI = "GenAI", 8
MULTIMODAL = "Multimodal", 8
UNSUPPORTED_TYPE_1 = "UnsupportedType", 9
UNSUPPORTED_TYPE_2 = "UnsupportedType", 10

Expand Down
2 changes: 1 addition & 1 deletion src/superannotate/lib/core/service_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,5 @@ class SettingsListResponse(ServiceResponse):
ProjectType.PIXEL: ImageResponse,
ProjectType.DOCUMENT: DocumentResponse,
ProjectType.POINT_CLOUD: PointCloudResponse,
ProjectType.GEN_AI: ImageResponse,
ProjectType.MULTIMODAL: ImageResponse,
}
21 changes: 21 additions & 0 deletions src/superannotate/lib/core/serviceproviders.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Callable
from typing import Dict
from typing import List
from typing import Literal

from lib.core import entities
from lib.core.conditions import Condition
Expand Down Expand Up @@ -287,10 +288,30 @@ def copy_multiple(
) -> ServiceResponse:
raise NotImplementedError

@abstractmethod
def copy_move_multiple(
self,
project: entities.ProjectEntity,
from_folder: entities.FolderEntity,
to_folder: entities.FolderEntity,
item_names: List[str],
duplicate_strategy: Literal["skip", "replace", "replace_annotations_only"],
operation: Literal["copy", "move"],
include_annotations: bool = True,
include_pin: bool = False,
) -> ServiceResponse:
raise NotImplementedError

@abstractmethod
def await_copy(self, project: entities.ProjectEntity, poll_id: int, items_count):
raise NotImplementedError

@abstractmethod
def await_copy_move(
self, project: entities.ProjectEntity, poll_id: int, items_count
):
raise NotImplementedError

@abstractmethod
def set_statuses(
self,
Expand Down
Loading