supervisely 6.73.355__py3-none-any.whl → 6.73.357__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
supervisely/api/api.py CHANGED
@@ -45,6 +45,8 @@ import supervisely.api.image_api as image_api
45
45
  import supervisely.api.import_storage_api as import_stoarge_api
46
46
  import supervisely.api.issues_api as issues_api
47
47
  import supervisely.api.labeling_job_api as labeling_job_api
48
+ import supervisely.api.labeling_queue_api as labeling_queue_api
49
+ import supervisely.api.entities_collection_api as entities_collection_api
48
50
  import supervisely.api.neural_network_api as neural_network_api
49
51
  import supervisely.api.object_class_api as object_class_api
50
52
  import supervisely.api.plugin_api as plugin_api
@@ -353,6 +355,7 @@ class Api:
353
355
  self.role = role_api.RoleApi(self)
354
356
  self.user = user_api.UserApi(self)
355
357
  self.labeling_job = labeling_job_api.LabelingJobApi(self)
358
+ self.labeling_queue = labeling_queue_api.LabelingQueueApi(self)
356
359
  self.video = video_api.VideoApi(self)
357
360
  # self.project_class = project_class_api.ProjectClassApi(self)
358
361
  self.object_class = object_class_api.ObjectClassApi(self)
@@ -370,6 +373,7 @@ class Api:
370
373
  self.github = github_api.GithubApi(self)
371
374
  self.volume = volume_api.VolumeApi(self)
372
375
  self.issues = issues_api.IssuesApi(self)
376
+ self.entities_collection = entities_collection_api.EntitiesCollectionApi(self)
373
377
 
374
378
  self.retry_count = retry_count
375
379
  self.retry_sleep_sec = retry_sleep_sec
@@ -1446,7 +1446,7 @@ class AppApi(TaskApi):
1446
1446
  return response.json()
1447
1447
 
1448
1448
  def get_ecosystem_module_info(
1449
- self, module_id: int, version: Optional[str] = None
1449
+ self, module_id: int = None, version: Optional[str] = None, slug: Optional[str] = None
1450
1450
  ) -> ModuleInfo:
1451
1451
  """Returns ModuleInfo object by module id and version.
1452
1452
 
@@ -1454,6 +1454,10 @@ class AppApi(TaskApi):
1454
1454
  :type module_id: int
1455
1455
  :param version: version of the module, e.g. "v1.0.0"
1456
1456
  :type version: Optional[str]
1457
+ :param slug: slug of the module, e.g. "supervisely-ecosystem/export-to-supervisely-format"
1458
+ :type slug: Optional[str]
1459
+ :raises ValueError: if both module_id and slug are None
1460
+ :raises ValueError: if both module_id and slug are provided
1457
1461
  :return: ModuleInfo object
1458
1462
  :rtype: ModuleInfo
1459
1463
  :Usage example:
@@ -1473,7 +1477,13 @@ class AppApi(TaskApi):
1473
1477
  module_id = 81
1474
1478
  module_info = api.app.get_ecosystem_module_info(module_id)
1475
1479
  """
1476
- data = {ApiField.ID: module_id}
1480
+ if module_id is None and slug is None:
1481
+ raise ValueError("Either module_id or slug must be provided")
1482
+ if module_id is not None:
1483
+ data = {ApiField.ID: module_id}
1484
+ else:
1485
+ data = {ApiField.SLUG: slug}
1486
+
1477
1487
  if version is not None:
1478
1488
  data[ApiField.VERSION] = version
1479
1489
  response = self._api.post("ecosystem.info", data)
@@ -1,3 +1,24 @@
1
1
  # coding: utf-8
2
2
 
3
3
  DOWNLOAD_BATCH_SIZE = None
4
+
5
+ # for Video Labeling Toolbox
6
+ PLAYBACK_RATE_POSSIBLE_VALUES = [
7
+ 0.1,
8
+ 0.3,
9
+ 0.5,
10
+ 0.6,
11
+ 0.7,
12
+ 0.8,
13
+ 0.9,
14
+ 1,
15
+ 1.1,
16
+ 1.2,
17
+ 1.3,
18
+ 1.5,
19
+ 2,
20
+ 4,
21
+ 8,
22
+ 16,
23
+ 32,
24
+ ]
@@ -0,0 +1,304 @@
1
+ # coding: utf-8
2
+ """create or manipulate already existing Entities Collection in Supervisely"""
3
+
4
+ # docs
5
+ from __future__ import annotations
6
+
7
+ from typing import Dict, List, NamedTuple, Optional
8
+
9
+ from supervisely.api.module_api import (
10
+ ApiField,
11
+ ModuleApi,
12
+ RemoveableModuleApi,
13
+ UpdateableModule,
14
+ )
15
+ from supervisely.sly_logger import logger
16
+
17
+
18
+ class EntitiesCollectionInfo(NamedTuple):
19
+ id: int
20
+ name: str
21
+ team_id: int
22
+ project_id: int
23
+ description: str
24
+ created_at: str
25
+ updated_at: str
26
+
27
+
28
+ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
29
+ """
30
+ API for working with Entities Collection. :class:`EntitiesCollectionApi<EntitiesCollectionApi>` object is immutable.
31
+
32
+ :param api: API connection to the server.
33
+ :type api: Api
34
+ :Usage example:
35
+
36
+ .. code-block:: python
37
+
38
+ import os
39
+ from dotenv import load_dotenv
40
+
41
+ import supervisely as sly
42
+
43
+ # Load secrets and create API object from .env file (recommended)
44
+ # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
45
+ if sly.is_development():
46
+ load_dotenv(os.path.expanduser("~/supervisely.env"))
47
+ api = sly.Api.from_env()
48
+
49
+ # Pass values into the API constructor (optional, not recommended)
50
+ # api = sly.Api(server_address="https://app.supervise.ly", token="4r47N...xaTatb")
51
+
52
+ collection = api.entities_collection.get_list(9) # api usage example
53
+ """
54
+
55
+ @staticmethod
56
+ def info_sequence():
57
+ """
58
+ NamedTuple EntitiesCollectionInfo information about Entities Collection.
59
+
60
+ :Example:
61
+
62
+ .. code-block:: python
63
+
64
+ EntitiesCollectionInfo(
65
+ id=2,
66
+ name='Enitites Collections #1',
67
+ team_id=4,
68
+ project_id=58,
69
+ description='',
70
+ created_at='2020-04-08T15:10:12.618Z',
71
+ updated_at='2020-04-08T15:10:19.833Z',
72
+ )
73
+ """
74
+ return [
75
+ ApiField.ID,
76
+ ApiField.NAME,
77
+ ApiField.TEAM_ID,
78
+ ApiField.PROJECT_ID,
79
+ ApiField.DESCRIPTION,
80
+ ApiField.CREATED_AT,
81
+ ApiField.UPDATED_AT,
82
+ ]
83
+
84
+ @staticmethod
85
+ def info_tuple_name():
86
+ """
87
+ NamedTuple name - **EntitiesCollectionInfo**.
88
+ """
89
+ return "EntitiesCollectionInfo"
90
+
91
+ def __init__(self, api):
92
+ ModuleApi.__init__(self, api)
93
+
94
+ def create(
95
+ self, project_id: int, name: str, description: Optional[str] = None
96
+ ) -> EntitiesCollectionInfo:
97
+ """
98
+ Creates Entities Collections.
99
+
100
+ :param project_id: Project ID in Supervisely.
101
+ :type project_id: int
102
+ :param name: Entities Collection name in Supervisely.
103
+ :type name: str
104
+ :param description: Entities Collection description.
105
+ :type description: str
106
+ :return: Information about new Entities Collection
107
+ :rtype: :class:`EntitiesCollectionInfo`
108
+ :Usage example:
109
+
110
+ .. code-block:: python
111
+
112
+ import supervisely as sly
113
+
114
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
115
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
116
+ api = sly.Api.from_env()
117
+
118
+ user_name = 'Collection #1'
119
+ project_id = 602
120
+ new_collection = api.entities_collection.create(name, project_id)
121
+ print(new_collection)
122
+ """
123
+
124
+ data = {ApiField.NAME: name, ApiField.PROJECT_ID: project_id}
125
+ if description is not None:
126
+ data[ApiField.DESCRIPTION] = description
127
+ response = self._api.post("entities-collections.add", data)
128
+ return self._convert_json_info(response.json())
129
+
130
+ def get_list(
131
+ self,
132
+ project_id: int,
133
+ filters: Optional[List[Dict[str, str]]] = None,
134
+ ) -> List[EntitiesCollectionInfo]:
135
+ """
136
+ Get list of information about Entities Collection for the given project.
137
+
138
+ :param project_id: Project ID in Supervisely.
139
+ :type project_id: int, optional
140
+ :return: List of information about Entities Collections.
141
+ :rtype: :class:`List[EntitiesCollectionInfo]`
142
+ :Usage example:
143
+
144
+ .. code-block:: python
145
+
146
+ import supervisely as sly
147
+
148
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
149
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
150
+ api = sly.Api.from_env()
151
+
152
+ collections = api.entities_collection.get_list(4)
153
+ """
154
+ if filters is None:
155
+ filters = []
156
+ return self.get_list_all_pages(
157
+ "entities-collections.list",
158
+ {ApiField.PROJECT_ID: project_id, ApiField.FILTER: filters},
159
+ )
160
+
161
+ def get_info_by_id(self, id: int) -> EntitiesCollectionInfo:
162
+ """
163
+ Get information about Entities Collection with given ID.
164
+
165
+ :param id: Entities Collection ID in Supervisely.
166
+ :type id: int
167
+ :return: Information about Entities Collection.
168
+ :rtype: :class:`EntitiesCollectionInfo`
169
+ :Usage example:
170
+
171
+ .. code-block:: python
172
+
173
+ import supervisely as sly
174
+
175
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
176
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
177
+ api = sly.Api.from_env()
178
+
179
+ collection = api.entities_collection.get_info_by_id(2)
180
+ print(collection)
181
+ # Output:
182
+ # {
183
+ # "id": 1,
184
+ # "teamId": 1,
185
+ # "projectId": 1,
186
+ # "name": "ds",
187
+ # "description": "",
188
+ # "createdAt": "2018-08-21T14:25:56.140Z",
189
+ # "updatedAt": "2018-08-21T14:25:56.140Z"
190
+ # }
191
+ """
192
+ return self._get_info_by_id(id, "entities-collections.info")
193
+
194
+ def _get_update_method(self):
195
+ """ """
196
+ return "entities-collections.editInfo"
197
+
198
+ def _remove_api_method_name(self):
199
+ """ """
200
+ return "entities-collections.remove"
201
+
202
+ def _get_update_method(self):
203
+ """ """
204
+ return "entities-collections.editInfo"
205
+
206
+ def add_items(self, id: int, items: List[int]) -> List[Dict[str, int]]:
207
+ """
208
+ Add items to Entities Collection.
209
+
210
+ :param id: Entities Collection ID in Supervisely.
211
+ :type id: int
212
+ :param items: List of item IDs in Supervisely.
213
+ :type items: List[int]
214
+ :Usage example:
215
+
216
+ .. code-block:: python
217
+
218
+ import supervisely as sly
219
+
220
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
221
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
222
+ api = sly.Api.from_env()
223
+
224
+ collection_id = 2
225
+ item_ids = [525, 526]
226
+ new_items = api.entities_collection.add_items(collection_id, item_ids)
227
+ print(new_items)
228
+ # Output: [
229
+ # {"id": 1, "entityId": 525, 'createdAt': '2025-04-10T08:49:41.852Z'},
230
+ # {"id": 2, "entityId": 526, 'createdAt': '2025-04-10T08:49:41.852Z'}
231
+ ]
232
+ """
233
+ data = {ApiField.COLLECTION_ID: id, ApiField.ENTITY_IDS: items}
234
+ response = self._api.post("entities-collections.items.bulk.add", data)
235
+ response = response.json()
236
+ if len(response["missing"]) > 0:
237
+ raise RuntimeError(
238
+ f"Failed to add items to Entities Collection. IDs: {response['missing']}. "
239
+ )
240
+ return response["items"]
241
+
242
+ def get_items(self, id: int, project_id: Optional[int] = None) -> List[int]:
243
+ """
244
+ Get items from Entities Collection.
245
+
246
+ :param id: Entities Collection ID in Supervisely.
247
+ :type id: int
248
+ :param project_id: Project ID in Supervisely.
249
+ :type project_id: int, optional
250
+ :return: List of item IDs in Supervisely.
251
+ :rtype: List[int]
252
+ :raises RuntimeError: If Entities Collection with given ID not found.
253
+ :Usage example:
254
+
255
+ .. code-block:: python
256
+
257
+ import supervisely as sly
258
+
259
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
260
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
261
+ api = sly.Api.from_env()
262
+
263
+ collection_id = 2
264
+ project_id = 4
265
+ item_ids = api.entities_collection.get_items(collection_id, project_id)
266
+ print(item_ids)
267
+ """
268
+ if project_id is None:
269
+ info = self.get_info_by_id(id)
270
+ if info is None:
271
+ raise RuntimeError(f"Entities Collection with id={id} not found.")
272
+ project_id = info.project_id
273
+
274
+ return self._api.image.get_list(project_id=project_id, entities_collection_id=id)
275
+
276
+ def remove_items(self, id: int, items: List[int]) -> List[Dict[str, int]]:
277
+ """
278
+ Remove items from Entities Collection.
279
+
280
+ :param id: Entities Collection ID in Supervisely.
281
+ :type id: int
282
+ :param items: List of item IDs in Supervisely.
283
+ :type items: List[int]
284
+ :Usage example:
285
+
286
+ .. code-block:: python
287
+
288
+ import supervisely as sly
289
+ api = sly.Api.from_env()
290
+
291
+ collection_id = 2
292
+ item_ids = [525, 526, 527]
293
+ removed_items = api.entities_collection.remove_items(collection_id, item_ids)
294
+ # print(removed_items)
295
+ # Output: [{"id": 1, "entityId": 525}, {"id": 2, "entityId": 526}]
296
+ """
297
+ data = {ApiField.COLLECTION_ID: id, ApiField.ENTITY_IDS: items}
298
+ response = self._api.post("entities-collections.items.bulk.remove", data)
299
+ response = response.json()
300
+ if len(response["missing"]) > 0:
301
+ logger.warning(
302
+ f"Failed to remove items from Entities Collection. IDs: {response['missing']}. "
303
+ )
304
+ return response["items"]
@@ -601,6 +601,7 @@ class ImageApi(RemoveableBulkModuleApi):
601
601
  only_labelled: Optional[bool] = False,
602
602
  fields: Optional[List[str]] = None,
603
603
  recursive: Optional[bool] = False,
604
+ entities_collection_id: Optional[int] = None,
604
605
  ) -> List[ImageInfo]:
605
606
  """
606
607
  List of Images in the given :class:`Dataset<supervisely.project.project.Dataset>`.
@@ -627,6 +628,8 @@ class ImageApi(RemoveableBulkModuleApi):
627
628
  :type fields: List[str], optional
628
629
  :param recursive: If True, returns all images from dataset recursively (including images in nested datasets).
629
630
  :type recursive: bool, optional
631
+ :param entities_collection_id: :class:`EntitiesCollection<supervisely.api.entities_collection_api.EntitiesCollectionApi>` ID to which the images belong. Can be used to filter images by specific entities collection.
632
+ :type entities_collection_id: int, optional
630
633
  :return: Objects with image information from Supervisely.
631
634
  :rtype: :class:`List[ImageInfo]<ImageInfo>`
632
635
  :Usage example:
@@ -700,6 +703,18 @@ class ImageApi(RemoveableBulkModuleApi):
700
703
  },
701
704
  }
702
705
  ]
706
+ if entities_collection_id is not None:
707
+ if ApiField.FILTERS not in data:
708
+ data[ApiField.FILTERS] = []
709
+ data[ApiField.FILTERS].append(
710
+ {
711
+ "type": "entities_collection",
712
+ "data": {
713
+ ApiField.COLLECTION_ID: entities_collection_id,
714
+ ApiField.INCLUDE: True,
715
+ },
716
+ }
717
+ )
703
718
  if fields is not None:
704
719
  data[ApiField.FIELDS] = fields
705
720
  return self.get_list_all_pages(
@@ -88,6 +88,7 @@ class LabelingJobInfo(NamedTuple):
88
88
  include_images_with_tags: list
89
89
  exclude_images_with_tags: list
90
90
  entities: list
91
+ priority: int
91
92
 
92
93
 
93
94
  class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
@@ -179,7 +180,8 @@ class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
179
180
  filter_images_by_tags=[],
180
181
  include_images_with_tags=[],
181
182
  exclude_images_with_tags=[],
182
- entities=None)
183
+ entities=None,
184
+ priority=2)
183
185
  """
184
186
  return [
185
187
  ApiField.ID,
@@ -220,6 +222,7 @@ class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
220
222
  ApiField.INCLUDE_IMAGES_WITH_TAGS,
221
223
  ApiField.EXCLUDE_IMAGES_WITH_TAGS,
222
224
  ApiField.ENTITIES,
225
+ ApiField.PRIORITY,
223
226
  ]
224
227
 
225
228
  @staticmethod
@@ -0,0 +1,735 @@
1
+ # coding: utf-8
2
+ """create or manipulate already existing labeling queues"""
3
+
4
+ # docs
5
+ from __future__ import annotations
6
+
7
+ from typing import Dict, List, Literal, NamedTuple, Optional, Union
8
+
9
+ from supervisely.api.constants import PLAYBACK_RATE_POSSIBLE_VALUES
10
+ from supervisely.api.labeling_job_api import LabelingJobApi
11
+ from supervisely.api.module_api import (
12
+ ApiField,
13
+ ModuleApi,
14
+ ModuleWithStatus,
15
+ RemoveableBulkModuleApi,
16
+ )
17
+ from supervisely.project.project_meta import ProjectMeta
18
+ from supervisely.project.project_type import ProjectType
19
+
20
+
21
+ class LabelingQueueInfo(NamedTuple):
22
+ id: int
23
+ name: str
24
+ team_id: int
25
+ project_id: int
26
+ dataset_id: int
27
+ created_by_id: int
28
+ labelers: list
29
+ reviewers: list
30
+ created_at: str
31
+ finished_at: str
32
+ status: str
33
+ jobs: list
34
+ entities_count: int
35
+ accepted_count: int
36
+ annotated_count: int
37
+ in_progress_count: int
38
+ pending_count: int
39
+ meta: dict
40
+
41
+
42
+ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
43
+ """
44
+ API for working with Labeling Queues. :class:`LabelingQueueApi<LabelingQueueApi>` object is immutable.
45
+
46
+ :param api: API connection to the server.
47
+ :type api: Api
48
+ :Usage example:
49
+
50
+ .. code-block:: python
51
+
52
+ import os
53
+ from dotenv import load_dotenv
54
+
55
+ import supervisely as sly
56
+
57
+ # Load secrets and create API object from .env file (recommended)
58
+ # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
59
+ if sly.is_development():
60
+ load_dotenv(os.path.expanduser("~/supervisely.env"))
61
+ api = sly.Api.from_env()
62
+
63
+ # Pass values into the API constructor (optional, not recommended)
64
+ # api = sly.Api(server_address="https://app.supervise.ly", token="4r47N...xaTatb")
65
+
66
+ queue = api.labeling_queues.get_info_by_id(2) # api usage example
67
+ """
68
+
69
+ @staticmethod
70
+ def info_sequence():
71
+ """
72
+ NamedTuple LabelingQueueInfo information about Labeling Queue.
73
+
74
+ :Example:
75
+
76
+ .. code-block:: python
77
+
78
+ LabelingQueueInfo(
79
+ id=2,
80
+ name='Annotation Queue (#1)',
81
+ team_id=4,
82
+ project_id=58,
83
+ dataset_id=54,
84
+ created_by_id=4,
85
+ labelers=[4],
86
+ reviewers=[4],
87
+ created_at='2020-04-08T15:10:12.618Z',
88
+ finished_at='2020-04-08T15:13:39.788Z',
89
+ status='completed',
90
+ jobs=[283, 282, 281],
91
+ entities_count=3,
92
+ accepted_count=2,
93
+ annotated_count=3,
94
+ in_progress_count=2,
95
+ pending_count=1,
96
+ meta={}
97
+ )
98
+ """
99
+ return [
100
+ ApiField.ID,
101
+ ApiField.NAME,
102
+ ApiField.TEAM_ID,
103
+ ApiField.PROJECT_ID,
104
+ ApiField.DATASET_ID,
105
+ ApiField.CREATED_BY_ID,
106
+ ApiField.LABELERS,
107
+ ApiField.REVIEWERS,
108
+ ApiField.CREATED_AT,
109
+ ApiField.FINISHED_AT,
110
+ ApiField.STATUS,
111
+ ApiField.JOBS,
112
+ ApiField.ENTITIES_COUNT,
113
+ ApiField.ACCEPTED_COUNT,
114
+ ApiField.ANNOTATED_COUNT,
115
+ ApiField.IN_PROGRESS_COUNT,
116
+ ApiField.PENDING_COUNT,
117
+ ApiField.META,
118
+ ]
119
+
120
+ @staticmethod
121
+ def info_tuple_name():
122
+ """
123
+ NamedTuple name - **LabelingQueueInfo**.
124
+ """
125
+ return "LabelingQueueInfo"
126
+
127
+ def _convert_json_info(self, info: Dict, skip_missing: Optional[bool] = True):
128
+ """ """
129
+
130
+ def _get_value(dict, field_name, skip_missing):
131
+ if skip_missing is True:
132
+ return dict.get(field_name, None)
133
+ else:
134
+ return dict[field_name]
135
+
136
+ def _get_ids(data, skip_missing):
137
+ if skip_missing is True:
138
+ return [job.get(ApiField.ID, None) for job in data]
139
+ else:
140
+ return [job[ApiField.ID] for job in data]
141
+
142
+ if info is None:
143
+ return None
144
+ else:
145
+ field_values = []
146
+ for field_name in self.info_sequence():
147
+ if type(field_name) is str:
148
+ if field_name in [ApiField.JOBS, ApiField.LABELERS, ApiField.REVIEWERS]:
149
+ field_values.append(_get_ids(info[field_name], skip_missing))
150
+ continue
151
+ else:
152
+ field_values.append(_get_value(info, field_name, skip_missing))
153
+ elif type(field_name) is tuple:
154
+ value = None
155
+ for sub_name in field_name[0]:
156
+ if value is None:
157
+ value = _get_value(info, sub_name, skip_missing)
158
+ else:
159
+ value = _get_value(value, sub_name, skip_missing)
160
+ field_values.append(value)
161
+ else:
162
+ raise RuntimeError("Can not parse field {!r}".format(field_name))
163
+ res = self.InfoType(*field_values)
164
+ return LabelingQueueInfo(**res._asdict())
165
+
166
+ def _check_membership(self, ids: List[int], team_id: int) -> None:
167
+ """Check if user is a member of the team in which the Labeling Queue is created."""
168
+ for user_id in ids:
169
+ memberships = self._api.user.get_teams(user_id)
170
+ team_ids = [team.id for team in memberships]
171
+ if team_id not in team_ids:
172
+ raise RuntimeError(
173
+ f"User with id {user_id} is not a member of the team with id {team_id}"
174
+ )
175
+
176
+ def create(
177
+ self,
178
+ name: str,
179
+ user_ids: List[int],
180
+ reviewer_ids: List[int],
181
+ dataset_id: Optional[int] = None,
182
+ collection_id: Optional[int] = None,
183
+ readme: Optional[str] = None,
184
+ classes_to_label: Optional[List[str]] = None,
185
+ objects_limit_per_image: Optional[int] = None,
186
+ tags_to_label: Optional[List[str]] = None,
187
+ tags_limit_per_image: Optional[int] = None,
188
+ include_images_with_tags: Optional[List[str]] = None,
189
+ exclude_images_with_tags: Optional[List[str]] = None,
190
+ images_range: Optional[List[int, int]] = None,
191
+ reviewer_id: Optional[int] = None,
192
+ images_ids: Optional[List[int]] = None,
193
+ dynamic_classes: Optional[bool] = False,
194
+ dynamic_tags: Optional[bool] = False,
195
+ disable_confirm: Optional[bool] = None,
196
+ disable_submit: Optional[bool] = None,
197
+ toolbox_settings: Optional[Dict] = None,
198
+ hide_figure_author: Optional[bool] = False,
199
+ allow_review_own_annotations: Optional[bool] = False,
200
+ skip_complete_job_on_empty: Optional[bool] = False,
201
+ ) -> int:
202
+ """
203
+ Creates Labeling Queue and assigns given Users to it.
204
+
205
+ :param name: Labeling Queue name in Supervisely.
206
+ :type name: str
207
+ :param dataset_id: Dataset ID in Supervisely.
208
+ :type dataset_id: int
209
+ :param collection_id: Entities Collection ID in Supervisely.
210
+ :type collection_id: int, optional
211
+ :param user_ids: User IDs in Supervisely to assign Users as labelers to Labeling Queue.
212
+ :type user_ids: List[int]
213
+ :param readme: Additional information about Labeling Queue.
214
+ :type readme: str, optional
215
+ :param description: Description of Labeling Queue.
216
+ :type description: str, optional
217
+ :param classes_to_label: List of classes to label in Dataset.
218
+ :type classes_to_label: List[str], optional
219
+ :param objects_limit_per_image: Limit the number of objects that the labeler can create on each image.
220
+ :type objects_limit_per_image: int, optional
221
+ :param tags_to_label: List of tags to label in Dataset.
222
+ :type tags_to_label: List[str], optional
223
+ :param tags_limit_per_image: Limit the number of tags that the labeler can create on each image.
224
+ :type tags_limit_per_image: int, optional
225
+ :param include_images_with_tags: Include images with given tags for processing by labeler.
226
+ :type include_images_with_tags: List[str], optional
227
+ :param exclude_images_with_tags: Exclude images with given tags for processing by labeler.
228
+ :type exclude_images_with_tags: List[str], optional
229
+ :param images_range: Limit number of images to be labeled for each labeler.
230
+ :type images_range: List[int, int], optional
231
+ :param reviewer_id: User ID in Supervisely to assign User as Reviewer to Labeling Queue.
232
+ :type reviewer_id: int, optional
233
+ :param images_ids: List of images ids to label in dataset
234
+ :type images_ids: List[int], optional
235
+ :param dynamic_classes: If True, classes created after creating the queue will be available for annotators
236
+ :type dynamic_classes: bool, optional
237
+ :param dynamic_tags: If True, tags created after creating the queue will be available for annotators
238
+ :type dynamic_tags: bool, optional
239
+ :param disable_confirm: If True, the Confirm button will be disabled in the labeling tool. It will remain disabled until the next API call sets the parameter to False, re-enabling the button.
240
+ :type disable_confirm: bool, optional
241
+ :param disable_submit: If True, the Submit button will be disabled in the labeling tool. It will remain disabled until the next API call sets the parameter to False, re-enabling the button.
242
+ :type disable_submit: bool, optional
243
+ :param toolbox_settings: Settings for the labeling tool. Only video projects are supported.
244
+ :type toolbox_settings: Dict, optional
245
+ :return: Labeling Queue ID in Supervisely.
246
+ :rtype: int
247
+ :Usage example:
248
+
249
+ .. code-block:: python
250
+
251
+ import supervisely as sly
252
+
253
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
254
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
255
+ api = sly.Api.from_env()
256
+
257
+ user_name = 'alex'
258
+ dataset_id = 602
259
+ new_labeling_queue_id = api.labeling_queue.create(
260
+ user_name,
261
+ dataset_id,
262
+ user_ids=[111, 222],
263
+ readme='Readmy text',
264
+ description='Work for labelers',
265
+ objects_limit_per_image=5,
266
+ tags_limit_per_image=3
267
+ )
268
+ print(new_labeling_queue_id)
269
+
270
+ # >>> 2
271
+
272
+ # Create video labeling job with toolbox settings
273
+
274
+ user_id = 4
275
+ dataset_id = 277
276
+ video_id = 24897
277
+ toolbox_settings = {"playbackRate": 32, "skipFramesSize": 15, "showVideoTime": True}
278
+
279
+ new_labeling_queue_id = api.labeling_queue.create(
280
+ name="Labeling Queue name",
281
+ dataset_id=dataset_id,
282
+ user_ids=[user_id],
283
+ readme="Labeling Queue readme",
284
+ description="Some description",
285
+ classes_to_label=["car", "animal"],
286
+ tags_to_label=["animal_age_group"],
287
+ images_ids=[video_id],
288
+ toolbox_settings=toolbox_settings,
289
+ )
290
+ print(new_labeling_queue_id)
291
+
292
+ # >>> 3
293
+ """
294
+ if dataset_id is None and collection_id is None:
295
+ raise RuntimeError("Either dataset_id or collection_id must be provided")
296
+ if classes_to_label is None:
297
+ classes_to_label = []
298
+ if tags_to_label is None:
299
+ tags_to_label = []
300
+
301
+ filter_images_by_tags = []
302
+ if include_images_with_tags is not None:
303
+ for tag_name in include_images_with_tags:
304
+ filter_images_by_tags.append({"name": tag_name, "positive": True})
305
+
306
+ if exclude_images_with_tags is not None:
307
+ for tag_name in exclude_images_with_tags:
308
+ filter_images_by_tags.append({"name": tag_name, "positive": False})
309
+
310
+ if objects_limit_per_image is None:
311
+ objects_limit_per_image = 0
312
+
313
+ if tags_limit_per_image is None:
314
+ tags_limit_per_image = 0
315
+
316
+ meta = {
317
+ "classes": classes_to_label,
318
+ "projectTags": tags_to_label,
319
+ "imageTags": filter_images_by_tags,
320
+ "imageFiguresLimit": objects_limit_per_image,
321
+ "imageTagsLimit": tags_limit_per_image,
322
+ "dynamicClasses": dynamic_classes,
323
+ "dynamicTags": dynamic_tags,
324
+ "hideFigureAuthor": hide_figure_author,
325
+ }
326
+ if images_ids is not None:
327
+ meta["entityIds"] = images_ids
328
+
329
+ if toolbox_settings is not None:
330
+ if dataset_id is not None:
331
+ dataset_info = self._api.dataset.get_info_by_id(dataset_id)
332
+ project_id = dataset_info.project_id
333
+ else:
334
+ collection_info = self._api.entities_collection.get_info_by_id(collection_id)
335
+ project_id = collection_info.project_id
336
+ project_info = self._api.project.get_info_by_id(project_id)
337
+ project_type = project_info.type
338
+ if project_type == ProjectType.VIDEOS.value:
339
+ playback_rate = toolbox_settings.get("playbackRate", None)
340
+ if playback_rate is not None:
341
+ if playback_rate not in PLAYBACK_RATE_POSSIBLE_VALUES:
342
+ raise ValueError(
343
+ f"'playbackRate' must be one of: '{','.join(PLAYBACK_RATE_POSSIBLE_VALUES)}'"
344
+ )
345
+ meta["toolboxSettings"] = toolbox_settings
346
+
347
+ if disable_confirm is not None:
348
+ meta.update({"disableConfirm": disable_confirm})
349
+ if disable_submit is not None:
350
+ meta.update({"disableSubmit": disable_submit})
351
+
352
+ queue_meta = {}
353
+ if allow_review_own_annotations is True:
354
+ queue_meta["reviewOwnAnnotationsAvailable"] = True
355
+
356
+ if skip_complete_job_on_empty is True:
357
+ queue_meta["skipCompleteAnnotationJobOnEmpty"] = True
358
+
359
+ data = {
360
+ ApiField.NAME: name,
361
+ ApiField.USER_IDS: user_ids,
362
+ ApiField.REVIEWER_IDS: reviewer_ids,
363
+ ApiField.META: meta,
364
+ }
365
+ if dataset_id is not None:
366
+ data[ApiField.DATASET_ID] = dataset_id
367
+ if collection_id is not None:
368
+ data[ApiField.COLLECTION_ID] = collection_id
369
+
370
+ if len(queue_meta) > 0:
371
+ data[ApiField.QUEUE_META] = queue_meta
372
+
373
+ if readme is not None:
374
+ data[ApiField.README] = str(readme)
375
+
376
+ if images_range is not None and images_range != (None, None):
377
+ if len(images_range) != 2:
378
+ raise RuntimeError("images_range has to contain 2 elements (start, end)")
379
+ images_range = {"start": images_range[0], "end": images_range[1]}
380
+ data[ApiField.META]["range"] = images_range
381
+
382
+ if reviewer_id is not None:
383
+ data[ApiField.REVIEWER_ID] = reviewer_id
384
+
385
+ response = self._api.post("labeling-queues.add", data)
386
+ return response.json()["id"] # {"success": true}
387
+
388
+ def get_list(
389
+ self,
390
+ team_id: int,
391
+ dataset_id: Optional[int] = None,
392
+ project_id: Optional[int] = None,
393
+ ids: Optional[List[int]] = None,
394
+ names: Optional[List[str]] = None,
395
+ show_disabled: Optional[bool] = False,
396
+ ) -> List[LabelingQueueInfo]:
397
+ """
398
+ Get list of information about Labeling Queues in the given Team.
399
+
400
+ :param team_id: Team ID in Supervisely.
401
+ :type team_id: int
402
+ :param dataset_id: Dataset ID in Supervisely.
403
+ :type dataset_id: int, optional
404
+ :param project_id: Project ID in Supervisely.
405
+ :type project_id: int, optional
406
+ :param ids: List of Labeling Queue IDs in Supervisely.
407
+ :type ids: List[int], optional
408
+ :param names: List of Labeling Queue names in Supervisely.
409
+ :type names: List[str], optional
410
+ :param show_disabled: Show disabled Labeling Queues.
411
+ :type show_disabled: bool, optional
412
+ :return: List of information about Labeling Queues. See :class:`info_sequence<info_sequence>`
413
+ :rtype: :class:`List[LabelingQueueInfo]`
414
+ :Usage example:
415
+
416
+ .. code-block:: python
417
+
418
+ import supervisely as sly
419
+
420
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
421
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
422
+ api = sly.Api.from_env()
423
+
424
+ label_jobs = api.labeling_queue.get_list(4)
425
+ """
426
+
427
+ filters = []
428
+ if project_id is not None:
429
+ filters.append({"field": ApiField.PROJECT_ID, "operator": "=", "value": project_id})
430
+ if dataset_id is not None:
431
+ filters.append({"field": ApiField.DATASET_ID, "operator": "=", "value": dataset_id})
432
+ if names is not None:
433
+ filters.append({"field": ApiField.NAME, "operator": "in", "value": names})
434
+ if ids is not None:
435
+ filters.append({"field": ApiField.ID, "operator": "in", "value": ids})
436
+ return self.get_list_all_pages(
437
+ "labeling-queues.list",
438
+ {ApiField.TEAM_ID: team_id, "showDisabled": show_disabled, ApiField.FILTER: filters},
439
+ )
440
+
441
+ def get_info_by_id(self, id: int) -> LabelingQueueInfo:
442
+ """
443
+ Get Labeling Queue information by ID.
444
+
445
+ :param id: Labeling Queue ID in Supervisely.
446
+ :type id: int
447
+ :return: Information about Labeling Queue. See :class:`info_sequence<info_sequence>`
448
+ :rtype: :class:`LabelingJobInfo`
449
+ :Usage example:
450
+
451
+ .. code-block:: python
452
+
453
+ import supervisely as sly
454
+
455
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
456
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
457
+ api = sly.Api.from_env()
458
+
459
+ label_job_info = api.labeling_queue.get_info_by_id(2)
460
+ print(label_job_info)
461
+ # Output: [
462
+ # 2,
463
+ # "Annotation Queue (#1) (#1) (dataset_01)",
464
+ # "",
465
+ # "",
466
+ # 4,
467
+ # 8,
468
+ # "First Workspace",
469
+ # 58,
470
+ # "tutorial_project",
471
+ # 54,
472
+ # "dataset_01",
473
+ # 4,
474
+ # "anna",
475
+ # 4,
476
+ # "anna",
477
+ # 4,
478
+ # "anna",
479
+ # "2020-04-08T15:10:12.618Z",
480
+ # "2020-04-08T15:10:19.833Z",
481
+ # "2020-04-08T15:13:39.788Z",
482
+ # "completed",
483
+ # false,
484
+ # 3,
485
+ # 0,
486
+ # 1,
487
+ # 2,
488
+ # 2,
489
+ # [],
490
+ # [],
491
+ # [
492
+ # 1,
493
+ # 5
494
+ # ],
495
+ # null,
496
+ # null,
497
+ # [],
498
+ # [],
499
+ # [],
500
+ # [
501
+ # {
502
+ # "reviewStatus": "rejected",
503
+ # "id": 283,
504
+ # "name": "image_03"
505
+ # },
506
+ # {
507
+ # "reviewStatus": "accepted",
508
+ # "id": 282,
509
+ # "name": "image_02"
510
+ # },
511
+ # {
512
+ # "reviewStatus": "accepted",
513
+ # "id": 281,
514
+ # "name": "image_01"
515
+ # }
516
+ # ]
517
+ # ]
518
+ """
519
+ return self._get_info_by_id(id, "labeling-queues.info")
520
+
521
+ def get_status(self, id: int) -> LabelingJobApi.Status:
522
+ """
523
+ Get status of Labeling Job with given ID.
524
+
525
+ :param id: Labeling job ID in Supervisely.
526
+ :type id: int
527
+ :return: Labeling Job Status
528
+ :rtype: :class:`Status<supervisely.api.labeling_job_api.LabelingJobApi.Status>`
529
+ :Usage example:
530
+
531
+ .. code-block:: python
532
+
533
+ import supervisely as sly
534
+
535
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
536
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
537
+ api = sly.Api.from_env()
538
+
539
+ job_status = api.labeling_queue.get_status(4)
540
+ print(job_status) # pending
541
+ """
542
+ status_str = self.get_info_by_id(id).status
543
+ return LabelingJobApi.Status(status_str)
544
+
545
+ def set_status(self, id: int, status: str) -> None:
546
+ """
547
+ Sets Labeling Queue status.
548
+
549
+ :param id: Labeling Queue ID in Supervisely.
550
+ :type id: int
551
+ :param status: New Labeling Queue status
552
+ :type status: str
553
+ :return: None
554
+ :rtype: :class:`NoneType`
555
+ :Usage example:
556
+
557
+ .. code-block:: python
558
+
559
+ import supervisely as sly
560
+ from supervisely.api.labeling_job_api.LabelingJobApi.Status import COMPLETED
561
+
562
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
563
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
564
+ api = sly.Api.from_env()
565
+
566
+ api.labeling_queue.set_status(id=9, status="completed")
567
+ """
568
+ self._api.post("labeling-queues.set-status", {ApiField.ID: id, ApiField.STATUS: status})
569
+
570
+ def get_project_meta(self, id: int) -> ProjectMeta:
571
+ """
572
+ Returns project meta with classes and tags used in the labeling queue with given id.
573
+
574
+ :param id: Labeling Queue ID in Supervisely.
575
+ :type id: int
576
+ :return: Project meta of the labeling queue with given id.
577
+ :rtype: :class:`ProjectMeta`
578
+ """
579
+ queue_info = self.get_info_by_id(id)
580
+ project_meta_json = self._api.project.get_meta(queue_info.project_id, with_settings=True)
581
+ project_meta = ProjectMeta.from_json(project_meta_json)
582
+
583
+ jobs = [self._api.labeling_job.get_info_by_id(job_id) for job_id in queue_info.jobs]
584
+ job_classes = set()
585
+ job_tags = set()
586
+ for job in jobs:
587
+ job_classes.update(job.classes_to_label)
588
+ job_tags.update(job.tags_to_label)
589
+
590
+ filtered_classes = [
591
+ obj_cls for obj_cls in project_meta.obj_classes if obj_cls.name in job_classes
592
+ ]
593
+ filtered_tags = [
594
+ tag_meta for tag_meta in project_meta.tag_metas if tag_meta.name in job_tags
595
+ ]
596
+ queue_meta = ProjectMeta(obj_classes=filtered_classes, tag_metas=filtered_tags)
597
+ return queue_meta
598
+
599
+ def get_entities_all_pages(
600
+ self,
601
+ id: int,
602
+ collection_id: Optional[int] = None,
603
+ per_page: int = 25,
604
+ sort: str = "name",
605
+ sort_order: str = "asc",
606
+ status: Optional[Union[List, Literal["none", "done", "accepted", "null"]]] = None,
607
+ limit: int = None,
608
+ filter_by: List[Dict] = None,
609
+ ) -> Dict[str, Union[List[Dict], int]]:
610
+ """
611
+ Get list of all or limited quantity entities from the Supervisely server.
612
+
613
+ :param id: Labeling Queue ID in Supervisely.
614
+ :type id: int
615
+ :param collection_id: Collection ID in Supervisely.
616
+ :type collection_id: int, optional
617
+ :param per_page: Number of entities per page.
618
+ :type per_page: int, optional
619
+ :param sort: Sorting field.
620
+ :type sort: str, optional
621
+ :param sort_order: Sorting order.
622
+ :type sort_order: str, optional
623
+ :param status: Status of entities to filter.
624
+ "null" - pending (in queue).
625
+ "none" - annotating (not in queue),
626
+ "done" - on review,
627
+ "accepted" - accepted,
628
+ :type status: str or List[str], optional
629
+ :param limit: Limit the number of entities to return. If limit is None, all entities will be returned.
630
+ :type limit: int, optional
631
+ :param filter_by: Filter for entities.
632
+ e.g. [{"field": "name", "operator": "in", "value": ["image_01", "image_02"]}]
633
+ - field - field name to filter by ("id", "name", "reviewedAt")
634
+ - operator - operator to use for filtering ("=", ">", "<", ">=", "<=")
635
+ - value - value to filter by
636
+ :type filter_by: List[Dict], optional
637
+ :param return_first_response: Specify if return first response
638
+ :type return_first_response: bool, optional
639
+ """
640
+
641
+ method = "labeling-queues.stats.entities"
642
+ data = {
643
+ ApiField.ID: id,
644
+ ApiField.PAGE: 1,
645
+ ApiField.PER_PAGE: per_page,
646
+ ApiField.SORT: sort,
647
+ ApiField.SORT_ORDER: sort_order,
648
+ }
649
+ if collection_id is not None:
650
+ data[ApiField.FILTERS] = [
651
+ {
652
+ "type": "entities_collection",
653
+ "data": {ApiField.COLLECTION_ID: collection_id, ApiField.INCLUDE: True},
654
+ }
655
+ ]
656
+ if filter_by is not None:
657
+ data[ApiField.FILTER] = filter_by
658
+ if status is not None:
659
+ if type(status) is str:
660
+ status = [status]
661
+ status = [None if s == "null" else s.lower() for s in status]
662
+ data["entityStatus"] = status
663
+
664
+ first_response = self._api.post(method, data).json()
665
+ total = first_response["total"]
666
+ pages_count = int(total / per_page) + 1 if total % per_page != 0 else int(total / per_page)
667
+ if pages_count in [0, 1]:
668
+ return first_response
669
+
670
+ limit_exceeded = False
671
+ results = first_response["images"]
672
+ if limit is not None and len(results) > limit:
673
+ limit_exceeded = True
674
+
675
+ if (pages_count == 1 and len(results) == total) or limit_exceeded is True:
676
+ pass
677
+ else:
678
+ for page_idx in range(2, pages_count + 1):
679
+ temp_resp = self._api.post(method, {**data, "page": page_idx, "per_page": per_page})
680
+ temp_items = temp_resp.json()["images"]
681
+ results.extend(temp_items)
682
+ if limit is not None and len(results) > limit:
683
+ limit_exceeded = True
684
+ break
685
+
686
+ if len(results) != total and limit is None:
687
+ raise RuntimeError(
688
+ "Method {!r}: error during pagination, some items are missed".format(method)
689
+ )
690
+
691
+ if limit is not None:
692
+ results = results[:limit]
693
+ return {"images": results, "total": total}
694
+
695
+ def get_entities_count_by_status(
696
+ self,
697
+ id: int,
698
+ status: Optional[Union[List, Literal["none", "done", "accepted", "null"]]] = None,
699
+ filter_by: List[Dict] = None,
700
+ ) -> int:
701
+ """
702
+ Get count of entities in the given Labeling Queue with given status.
703
+ :param id: Labeling Queue ID in Supervisely.
704
+ :type id: int
705
+ :param status: Status of entities to filter.
706
+ "null" - pending (in queue).
707
+ "none" - annotating (not in queue),
708
+ "done" - on review,
709
+ "accepted" - accepted,
710
+ :type status: str or List[str], optional
711
+ :param filter_by: Filter for entities.
712
+ e.g. [{"field": "name", "operator": "in", "value": ["image_01", "image_02"]}]
713
+ - field - field name to filter by ("id", "name", "reviewedAt")
714
+ - operator - operator to use for filtering ("=", ">", "<", ">=", "<=")
715
+ - value - value to filter by
716
+ :type filter_by: List[Dict], optional
717
+ :return: Count of entities in the Labeling Queue with given status.
718
+ :rtype: int
719
+ :Usage example:
720
+
721
+ .. code-block:: python
722
+
723
+ import supervisely as sly
724
+
725
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
726
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
727
+ api = sly.Api.from_env()
728
+
729
+ entities_count = api.labeling_queue.get_entities_count_by_status(4, status="none")
730
+ print(entities_count)
731
+ # Output: 3
732
+ """
733
+ return self.get_entities_all_pages(id, status=status, limit=1, filter_by=filter_by).get(
734
+ "total", 0
735
+ )
@@ -637,6 +637,30 @@ class ApiField:
637
637
  """"""
638
638
  SOURCE_BLOB = "sourceBlob"
639
639
  """"""
640
+ JOBS = "jobs"
641
+ """"""
642
+ LABELERS = "labelers"
643
+ """"""
644
+ REVIEWERS = "reviewers"
645
+ """"""
646
+ REVIEWER_IDS = "reviewerIds"
647
+ """"""
648
+ ENTITIES_COUNT = "entitiesCount"
649
+ """"""
650
+ ACCEPTED_COUNT = "acceptedCount"
651
+ """"""
652
+ ANNOTATED_COUNT = "annotatedCount"
653
+ """"""
654
+ IN_PROGRESS_COUNT = "inProgressCount"
655
+ """"""
656
+ PENDING_COUNT = "pendingCount"
657
+ """"""
658
+ QUEUE_META = "queueMeta"
659
+ """"""
660
+ ENTITY_IDS = "entityIds"
661
+ """"""
662
+ COLLECTION_ID = "collectionId"
663
+ """"""
640
664
 
641
665
 
642
666
  def _get_single_item(items):
@@ -529,6 +529,7 @@ class VolumeFigure(VideoFigure):
529
529
  else:
530
530
  geometry_json = data[ApiField.GEOMETRY]
531
531
  geometry = shape.from_json(geometry_json)
532
+ geometry.sly_id = data.get(ID, None)
532
533
 
533
534
  key = uuid.UUID(data[KEY]) if KEY in data else uuid.uuid4()
534
535
 
@@ -636,10 +637,15 @@ class VolumeFigure(VideoFigure):
636
637
  """
637
638
  if isinstance(geometry_data, str):
638
639
  mask_3d = Mask3D.create_from_file(geometry_data)
639
- if isinstance(geometry_data, ndarray):
640
+ elif isinstance(geometry_data, ndarray):
640
641
  mask_3d = Mask3D(geometry_data)
641
- if isinstance(geometry_data, bytes):
642
+ elif isinstance(geometry_data, bytes):
642
643
  mask_3d = Mask3D.from_bytes(geometry_data)
644
+ else:
645
+ raise TypeError(
646
+ f"geometry_data must be str, ndarray, or bytes, but got {type(geometry_data)}"
647
+ )
648
+
643
649
  return cls(
644
650
  volume_object,
645
651
  mask_3d,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: supervisely
3
- Version: 6.73.355
3
+ Version: 6.73.357
4
4
  Summary: Supervisely Python SDK.
5
5
  Home-page: https://github.com/supervisely/supervisely
6
6
  Author: Supervisely
@@ -22,18 +22,20 @@ supervisely/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  supervisely/api/advanced_api.py,sha256=Nd5cCnHFWc3PSUrCtENxTGtDjS37_lCHXsgXvUI3Ti8,2054
23
23
  supervisely/api/agent_api.py,sha256=ShWAIlXcWXcyI9fqVuP5GZVCigCMJmjnvdGUfLspD6Y,8890
24
24
  supervisely/api/annotation_api.py,sha256=XE3Sr_oQOMraZ9NlWs9ZqUsff1j47XRDh9Fy8rW4ubw,82763
25
- supervisely/api/api.py,sha256=D90G6ivwyHHK7WVo4ByKtv65lSED_Sm1qlp_TpyCeAc,67398
26
- supervisely/api/app_api.py,sha256=RsbVej8WxWVn9cNo5s3Fqd1symsCdsfOaKVBKEUapRY,71927
27
- supervisely/api/constants.py,sha256=qwK9Q3mHGz5Ny5WuBmPQgawBWfzCT2DRiw3IcaCDRmo,44
25
+ supervisely/api/api.py,sha256=zAYool7iuceIaCVl_CGflp9KVGwnKf5z6BUagxEZA_w,67695
26
+ supervisely/api/app_api.py,sha256=5lU3EU2NIwcatB6Uj3JUg_y-Qohsm6tfWK6M7MqEYKU,72442
27
+ supervisely/api/constants.py,sha256=WfqIcEpRnU4Mcfb6q0njeRs2VVSoTAJaIyrqBkBjP8I,253
28
28
  supervisely/api/dataset_api.py,sha256=c6rEVySSxbyDB5fUCilsc5AKk0pWN_5vVN6OceDbEvc,47918
29
+ supervisely/api/entities_collection_api.py,sha256=lIF10PVgdUOEjcG7dJuxgpN30cnmxw9-UNc6tEmukKo,9957
29
30
  supervisely/api/file_api.py,sha256=EX_Cam93QkR5SOOIkIznkzERIr0u7N7GHVGK27iOm20,92952
30
31
  supervisely/api/github_api.py,sha256=NIexNjEer9H5rf5sw2LEZd7C1WR-tK4t6IZzsgeAAwQ,623
31
32
  supervisely/api/image_annotation_tool_api.py,sha256=YcUo78jRDBJYvIjrd-Y6FJAasLta54nnxhyaGyanovA,5237
32
- supervisely/api/image_api.py,sha256=sE2lIPPYLv1tJsuFSjc9IvLVB07kmq9PnPodU3J6aEs,223637
33
+ supervisely/api/image_api.py,sha256=8qs1AD8h1gyoibp9nZQ3b_A1Vid44Q103tmwRC-2hwQ,224419
33
34
  supervisely/api/import_storage_api.py,sha256=BDCgmR0Hv6OoiRHLCVPKt3iDxSVlQp1WrnKhAK_Zl84,460
34
35
  supervisely/api/issues_api.py,sha256=BqDJXmNoTzwc3xe6_-mA7FDFC5QQ-ahGbXk_HmpkSeQ,17925
35
- supervisely/api/labeling_job_api.py,sha256=VM1pjM65cUTeer1hrI7cSmCUOHJb7fzK6gJ-abA07hg,55097
36
- supervisely/api/module_api.py,sha256=Lou7blSb1wMwMnuZ0emyhHALdgRreJczOlGRJ0ZqLjE,44763
36
+ supervisely/api/labeling_job_api.py,sha256=5xorizIYJlyBi-XtPiMyFz-COKR0V_KR5zVhd1kutSM,55187
37
+ supervisely/api/labeling_queue_api.py,sha256=svQKbHtt8304FCFw_ZUoAHrCj3Yetj2RTdgTP_xTGks,28424
38
+ supervisely/api/module_api.py,sha256=8lhsLIvZluHCB23MerHGdENWlfsj5gHIVwUVzMAkwgU,45283
37
39
  supervisely/api/neural_network_api.py,sha256=ktPVRO4Jeulougio8F0mioJJHwRJcX250Djp1wBoQ9c,7620
38
40
  supervisely/api/object_class_api.py,sha256=hBcQuKi9ouoiR7f99eot6vXHvqbzmal3IrHjJxuoFXg,10396
39
41
  supervisely/api/plugin_api.py,sha256=TlfrosdRuYG4NUxk92QiQoVaOdztFspPpygyVa3M3zk,5283
@@ -1067,7 +1069,7 @@ supervisely/volume_annotation/constants.py,sha256=BdFIh56fy7vzLIjt0gH8xP01EIU-qg
1067
1069
  supervisely/volume_annotation/plane.py,sha256=wyezAcc8tLp38O44CwWY0wjdQxf3VjRdFLWooCrk-Nw,16301
1068
1070
  supervisely/volume_annotation/slice.py,sha256=9m3jtUYz4PYKV3rgbeh2ofDebkyg4TomNbkC6BwZ0lA,4635
1069
1071
  supervisely/volume_annotation/volume_annotation.py,sha256=T-50ZmfhQDUtoXkSB9ur3LySCp9xZ1AmUT9KqjPy1DA,30179
1070
- supervisely/volume_annotation/volume_figure.py,sha256=CWb5VePfJWeShwsCay4RDyylHQ0kg5zY6x4JUodxuro,25127
1072
+ supervisely/volume_annotation/volume_figure.py,sha256=3_X2drnt_zCGBLWPanW8_O-jM-tI9-34rSacUWqTflk,25329
1071
1073
  supervisely/volume_annotation/volume_object.py,sha256=rWzOnycoSJ4-CvFgDOP_rPortU4CdcYR26txe5wJHNo,3577
1072
1074
  supervisely/volume_annotation/volume_object_collection.py,sha256=Tc4AovntgoFj5hpTLBv7pCQ3eL0BjorOVpOh2nAE_tA,5706
1073
1075
  supervisely/volume_annotation/volume_tag.py,sha256=N2eOhAlbRDVVdSVQ83dzg7URDGtb1xHjxL2g9BW6ljU,9488
@@ -1083,9 +1085,9 @@ supervisely/worker_proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
1083
1085
  supervisely/worker_proto/worker_api_pb2.py,sha256=VQfi5JRBHs2pFCK1snec3JECgGnua3Xjqw_-b3aFxuM,59142
1084
1086
  supervisely/worker_proto/worker_api_pb2_grpc.py,sha256=3BwQXOaP9qpdi0Dt9EKG--Lm8KGN0C5AgmUfRv77_Jk,28940
1085
1087
  supervisely_lib/__init__.py,sha256=7-3QnN8Zf0wj8NCr2oJmqoQWMKKPKTECvjH9pd2S5vY,159
1086
- supervisely-6.73.355.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
1087
- supervisely-6.73.355.dist-info/METADATA,sha256=DE9HbZdlEcZ9MLqTf-5GBkvaY6kYoomrBKAAK_8Od60,33598
1088
- supervisely-6.73.355.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
1089
- supervisely-6.73.355.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
1090
- supervisely-6.73.355.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
1091
- supervisely-6.73.355.dist-info/RECORD,,
1088
+ supervisely-6.73.357.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
1089
+ supervisely-6.73.357.dist-info/METADATA,sha256=DdNugcuaOhnUNQMeJOHhj7x80PAPBkxnyKQencTMt-Q,33598
1090
+ supervisely-6.73.357.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
1091
+ supervisely-6.73.357.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
1092
+ supervisely-6.73.357.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
1093
+ supervisely-6.73.357.dist-info/RECORD,,