Skip to content

Commit 9e5b29a

Browse files
authored
Merge pull request #797 from superannotateai/develop
Develop
2 parents 5553428 + 40ba873 commit 9e5b29a

File tree

20 files changed

+650
-137
lines changed

20 files changed

+650
-137
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ History
66

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

9+
4.4.36 - June 05, 2025
10+
______________________
11+
12+
**Updated**
13+
14+
- ``SAClient.get_project_steps`` and ``SAClient.get_project_steps`` now support keypoint workflows, enabling structured step configuration with class IDs, attributes, and step connections.
15+
- ``SAClient.list_users`` now returns user-specific permission states for paused, allow_orchestrate, allow_run_explore, and allow_view_sdk_token.
16+
917

1018
4.4.35 - May 2, 2025
1119
____________________

docs/source/api_reference/api_project.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ Projects
2424
.. automethod:: superannotate.SAClient.add_contributors_to_project
2525
.. automethod:: superannotate.SAClient.get_project_settings
2626
.. automethod:: superannotate.SAClient.set_project_default_image_quality_in_editor
27-
.. automethod:: superannotate.SAClient.set_project_steps
2827
.. automethod:: superannotate.SAClient.get_project_steps
28+
.. automethod:: superannotate.SAClient.set_project_steps
2929
.. automethod:: superannotate.SAClient.get_component_config

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.35"
6+
__version__ = "4.4.36"
77

88

99
os.environ.update({"sa_version": __version__})

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

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
from lib.core.entities.work_managament import WMUserTypeEnum
7575
from lib.core.jsx_conditions import EmptyQuery
7676

77-
7877
logger = logging.getLogger("sa")
7978

8079
NotEmptyStr = constr(strict=True, min_length=1)
@@ -1493,10 +1492,11 @@ def get_project_steps(self, project: Union[str, dict]):
14931492
:param project: project name or metadata
14941493
:type project: str or dict
14951494
1496-
:return: project steps
1497-
:rtype: list of dicts
1495+
:return: A list of step dictionaries,
1496+
or a dictionary containing both steps and their connections (for Keypoint workflows).
1497+
:rtype: list of dicts or dict
14981498
1499-
Response Example:
1499+
Response Example for General Annotation Project:
15001500
::
15011501
15021502
[
@@ -1515,6 +1515,34 @@ def get_project_steps(self, project: Union[str, dict]):
15151515
}
15161516
]
15171517
1518+
Response Example for Keypoint Annotation Project:
1519+
::
1520+
1521+
{
1522+
"steps": [
1523+
{
1524+
"step": 1,
1525+
"className": "Left Shoulder",
1526+
"class_id": "1",
1527+
"attribute": [
1528+
{
1529+
"attribute": {
1530+
"id": 123,
1531+
"group_id": 12
1532+
}
1533+
}
1534+
]
1535+
},
1536+
{
1537+
"step": 2,
1538+
"class_id": "2",
1539+
"className": "Right Shoulder",
1540+
}
1541+
],
1542+
"connections": [
1543+
[1, 2]
1544+
]
1545+
}
15181546
"""
15191547
project_name, _ = extract_project_folder(project)
15201548
project = self.controller.get_project(project_name)
@@ -2511,7 +2539,12 @@ def download_export(
25112539
if response.errors:
25122540
raise AppException(response.errors)
25132541

2514-
def set_project_steps(self, project: Union[NotEmptyStr, dict], steps: List[dict]):
2542+
def set_project_steps(
2543+
self,
2544+
project: Union[NotEmptyStr, dict],
2545+
steps: List[dict],
2546+
connections: List[List[int]] = None,
2547+
):
25152548
"""Sets project's steps.
25162549
25172550
:param project: project name or metadata
@@ -2520,7 +2553,11 @@ def set_project_steps(self, project: Union[NotEmptyStr, dict], steps: List[dict]
25202553
:param steps: new workflow list of dicts
25212554
:type steps: list of dicts
25222555
2523-
Request Example:
2556+
:param connections: Defines connections between keypoint annotation steps.
2557+
Each inner list specifies a pair of step IDs indicating a connection.
2558+
:type connections: list of list
2559+
2560+
Request Example for General Annotation Project:
25242561
::
25252562
25262563
sa.set_project_steps(
@@ -2541,10 +2578,40 @@ def set_project_steps(self, project: Union[NotEmptyStr, dict], steps: List[dict]
25412578
}
25422579
]
25432580
)
2581+
2582+
Request Example for Keypoint Annotation Project:
2583+
::
2584+
2585+
sa.set_project_steps(
2586+
project="Pose Estimation Project",
2587+
steps=[
2588+
{
2589+
"step": 1,
2590+
"class_id": 12,
2591+
"attribute": [
2592+
{
2593+
"attribute": {
2594+
"id": 123,
2595+
"group_id": 12
2596+
}
2597+
}
2598+
]
2599+
},
2600+
{
2601+
"step": 2,
2602+
"class_id": 13
2603+
}
2604+
],
2605+
connections=[
2606+
[1, 2]
2607+
]
2608+
)
25442609
"""
25452610
project_name, _ = extract_project_folder(project)
25462611
project = self.controller.get_project(project_name)
2547-
response = self.controller.projects.set_steps(project, steps=steps)
2612+
response = self.controller.projects.set_steps(
2613+
project, steps=steps, connections=connections
2614+
)
25482615
if response.errors:
25492616
raise AppException(response.errors)
25502617

src/superannotate/lib/core/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from lib.core.enums import ImageQuality
1111
from lib.core.enums import ProjectStatus
1212
from lib.core.enums import ProjectType
13+
from lib.core.enums import StepsType
1314
from lib.core.enums import TrainingStatus
1415
from lib.core.enums import UploadState
1516
from lib.core.enums import UserRole
@@ -186,6 +187,7 @@ def setup_logging(level=DEFAULT_LOGGING_LEVEL, file_path=LOG_FILE_LOCATION):
186187
FolderStatus,
187188
ProjectStatus,
188189
ProjectType,
190+
StepsType,
189191
UserRole,
190192
UploadState,
191193
TrainingStatus,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ class WMProjectUserEntity(TimedBaseModel):
133133
email: Optional[str]
134134
state: Optional[WMUserStateEnum]
135135
custom_fields: Optional[dict] = Field(dict(), alias="customField")
136+
permissions: Optional[dict]
136137

137138
class Config:
138139
extra = Extra.ignore

src/superannotate/lib/core/enums.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ def images(self):
117117
return self.VECTOR.value, self.PIXEL.value, self.TILED.value
118118

119119

120+
class StepsType(Enum):
121+
INITIAL = 1
122+
BASIC = 2
123+
KEYPOINT = 3
124+
125+
120126
class UserRole(BaseTitledEnum):
121127
CONTRIBUTOR = "Contributor", 4
122128
ADMIN = "Admin", 7

src/superannotate/lib/core/serviceproviders.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,10 +264,18 @@ def set_settings(
264264
def list_steps(self, project: entities.ProjectEntity):
265265
raise NotImplementedError
266266

267+
@abstractmethod
268+
def list_keypoint_steps(self, project: entities.ProjectEntity):
269+
raise NotImplementedError
270+
267271
@abstractmethod
268272
def set_step(self, project: entities.ProjectEntity, step: entities.StepEntity):
269273
raise NotImplementedError
270274

275+
@abstractmethod
276+
def set_keypoint_steps(self, project: entities.ProjectEntity, steps, connections):
277+
raise NotImplementedError
278+
271279
@abstractmethod
272280
def set_steps(self, project: entities.ProjectEntity, steps: list):
273281
raise NotImplementedError

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ def log_report(
107107

108108
class ItemToUpload(BaseModel):
109109
item: BaseItemEntity
110-
annotation_json: Optional[dict]
111-
path: Optional[str]
112-
file_size: Optional[int]
113-
mask: Optional[io.BytesIO]
110+
annotation_json: Optional[dict] = None
111+
path: Optional[str] = None
112+
file_size: Optional[int] = None
113+
mask: Optional[io.BytesIO] = None
114114

115115
class Config:
116116
arbitrary_types_allowed = True
@@ -282,7 +282,7 @@ def validate_project_type(self):
282282
raise AppException("Unsupported project type.")
283283

284284
def _validate_json(self, json_data: dict) -> list:
285-
if self._project.type >= constants.ProjectType.PIXEL.value:
285+
if self._project.type >= int(constants.ProjectType.PIXEL):
286286
return []
287287
use_case = ValidateAnnotationUseCase(
288288
reporter=self.reporter,
@@ -2101,16 +2101,16 @@ def execute(self):
21012101
if categorization_enabled:
21022102
item_id_category_map = {}
21032103
for item_name in uploaded_annotations:
2104-
category = (
2105-
name_annotation_map[item_name]["metadata"]
2106-
.get("item_category", {})
2107-
.get("value")
2104+
category = name_annotation_map[item_name]["metadata"].get(
2105+
"item_category", None
21082106
)
21092107
if category:
21102108
item_id_category_map[name_item_map[item_name].id] = category
2111-
self._attach_categories(
2112-
folder_id=folder.id, item_id_category_map=item_id_category_map
2113-
)
2109+
if item_id_category_map:
2110+
self._attach_categories(
2111+
folder_id=folder.id,
2112+
item_id_category_map=item_id_category_map,
2113+
)
21142114
workflow = self._service_provider.work_management.get_workflow(
21152115
self._project.workflow_id
21162116
)
@@ -2149,7 +2149,7 @@ def _attach_categories(self, folder_id: int, item_id_category_map: Dict[int, str
21492149
)
21502150
response.raise_for_status()
21512151
categories = response.data
2152-
self._category_name_to_id_map = {c.name: c.id for c in categories}
2152+
self._category_name_to_id_map = {c.value: c.id for c in categories}
21532153
for item_id in list(item_id_category_map.keys()):
21542154
category_name = item_id_category_map[item_id]
21552155
if category_name not in self._category_name_to_id_map:

0 commit comments

Comments
 (0)