supervisely 6.73.399__py3-none-any.whl → 6.73.401__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/__init__.py +1 -0
- supervisely/api/api.py +8 -7
- supervisely/api/app_api.py +18 -1
- supervisely/api/entities_collection_api.py +382 -40
- supervisely/api/image_api.py +118 -22
- supervisely/api/module_api.py +26 -0
- supervisely/api/project_api.py +235 -16
- supervisely/api/video/video_api.py +14 -11
- supervisely/nn/inference/cache.py +6 -1
- {supervisely-6.73.399.dist-info → supervisely-6.73.401.dist-info}/METADATA +1 -1
- {supervisely-6.73.399.dist-info → supervisely-6.73.401.dist-info}/RECORD +15 -15
- {supervisely-6.73.399.dist-info → supervisely-6.73.401.dist-info}/LICENSE +0 -0
- {supervisely-6.73.399.dist-info → supervisely-6.73.401.dist-info}/WHEEL +0 -0
- {supervisely-6.73.399.dist-info → supervisely-6.73.401.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.399.dist-info → supervisely-6.73.401.dist-info}/top_level.txt +0 -0
supervisely/__init__.py
CHANGED
|
@@ -127,6 +127,7 @@ from supervisely.api.workspace_api import WorkspaceInfo
|
|
|
127
127
|
from supervisely.api.team_api import TeamInfo
|
|
128
128
|
from supervisely.api.entity_annotation.figure_api import FigureInfo
|
|
129
129
|
from supervisely.api.app_api import WorkflowSettings, WorkflowMeta
|
|
130
|
+
from supervisely.api.entities_collection_api import EntitiesCollectionInfo
|
|
130
131
|
|
|
131
132
|
from supervisely.cli import _handle_creds_error_to_console
|
|
132
133
|
|
supervisely/api/api.py
CHANGED
|
@@ -38,6 +38,7 @@ import supervisely.api.agent_api as agent_api
|
|
|
38
38
|
import supervisely.api.annotation_api as annotation_api
|
|
39
39
|
import supervisely.api.app_api as app_api
|
|
40
40
|
import supervisely.api.dataset_api as dataset_api
|
|
41
|
+
import supervisely.api.entities_collection_api as entities_collection_api
|
|
41
42
|
import supervisely.api.file_api as file_api
|
|
42
43
|
import supervisely.api.github_api as github_api
|
|
43
44
|
import supervisely.api.image_annotation_tool_api as image_annotation_tool_api
|
|
@@ -45,9 +46,8 @@ import supervisely.api.image_api as image_api
|
|
|
45
46
|
import supervisely.api.import_storage_api as import_stoarge_api
|
|
46
47
|
import supervisely.api.issues_api as issues_api
|
|
47
48
|
import supervisely.api.labeling_job_api as labeling_job_api
|
|
48
|
-
import supervisely.api.nn.neural_network_api as neural_network_api
|
|
49
49
|
import supervisely.api.labeling_queue_api as labeling_queue_api
|
|
50
|
-
import supervisely.api.
|
|
50
|
+
import supervisely.api.nn.neural_network_api as neural_network_api
|
|
51
51
|
import supervisely.api.object_class_api as object_class_api
|
|
52
52
|
import supervisely.api.plugin_api as plugin_api
|
|
53
53
|
import supervisely.api.pointcloud.pointcloud_api as pointcloud_api
|
|
@@ -297,19 +297,20 @@ class Api:
|
|
|
297
297
|
|
|
298
298
|
def __init__(
|
|
299
299
|
self,
|
|
300
|
-
server_address: str = None,
|
|
301
|
-
token: str = None,
|
|
300
|
+
server_address: Optional[str] = None,
|
|
301
|
+
token: Optional[str] = None,
|
|
302
302
|
retry_count: Optional[int] = 10,
|
|
303
303
|
retry_sleep_sec: Optional[int] = None,
|
|
304
304
|
external_logger: Optional[Logger] = None,
|
|
305
|
-
ignore_task_id:
|
|
306
|
-
api_server_address: str = None,
|
|
305
|
+
ignore_task_id: bool = False,
|
|
306
|
+
api_server_address: Optional[str] = None,
|
|
307
307
|
check_instance_version: Union[bool, str] = False,
|
|
308
308
|
):
|
|
309
309
|
self.logger = external_logger or logger
|
|
310
310
|
|
|
311
|
-
if server_address is None
|
|
311
|
+
if server_address is None:
|
|
312
312
|
server_address = os.environ.get(SERVER_ADDRESS, None)
|
|
313
|
+
if token is None:
|
|
313
314
|
token = os.environ.get(API_TOKEN, None)
|
|
314
315
|
|
|
315
316
|
if server_address is None:
|
supervisely/api/app_api.py
CHANGED
|
@@ -1723,7 +1723,7 @@ class AppApi(TaskApi):
|
|
|
1723
1723
|
users_ids = [users_id]
|
|
1724
1724
|
|
|
1725
1725
|
new_params = {}
|
|
1726
|
-
if "state" not in params:
|
|
1726
|
+
if params is not None and "state" not in params:
|
|
1727
1727
|
new_params["state"] = params
|
|
1728
1728
|
else:
|
|
1729
1729
|
new_params = params
|
|
@@ -1833,6 +1833,23 @@ class AppApi(TaskApi):
|
|
|
1833
1833
|
raise ValueError(f"Multiple serving apps found for app name {app_name}")
|
|
1834
1834
|
return modules[0]["id"]
|
|
1835
1835
|
|
|
1836
|
+
def get_session_token(self, slug: str) -> str:
|
|
1837
|
+
"""
|
|
1838
|
+
Get session token for the app with specified slug.
|
|
1839
|
+
|
|
1840
|
+
:param slug: Slug of the app, e.g. "supervisely-ecosystem/hello-world-app".
|
|
1841
|
+
:type slug: str
|
|
1842
|
+
|
|
1843
|
+
:return: Session token for the app.
|
|
1844
|
+
:rtype: str
|
|
1845
|
+
"""
|
|
1846
|
+
data = {ApiField.SLUG: slug}
|
|
1847
|
+
response = self._api.post(
|
|
1848
|
+
"instance.get-render-previews-session-token",
|
|
1849
|
+
data,
|
|
1850
|
+
)
|
|
1851
|
+
return response.text
|
|
1852
|
+
|
|
1836
1853
|
|
|
1837
1854
|
# info about app in team
|
|
1838
1855
|
# {
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
# docs
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Union
|
|
9
|
+
|
|
10
|
+
import requests
|
|
8
11
|
|
|
9
12
|
from supervisely.api.module_api import (
|
|
10
13
|
ApiField,
|
|
@@ -14,16 +17,152 @@ from supervisely.api.module_api import (
|
|
|
14
17
|
)
|
|
15
18
|
from supervisely.sly_logger import logger
|
|
16
19
|
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from supervisely.api.api import Api
|
|
22
|
+
from supervisely.api.image_api import ImageInfo
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CollectionType:
|
|
26
|
+
DEFAULT = "default"
|
|
27
|
+
AI_SEARCH = "aiSearch"
|
|
28
|
+
ALL = "all"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CollectionTypeFilter:
|
|
32
|
+
AI_SEARCH = "entities_ai_search_collection"
|
|
33
|
+
DEFAULT = "entities_collection"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AiSearchThresholdDirection:
|
|
37
|
+
ABOVE = "above"
|
|
38
|
+
BELOW = "below"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class CollectionItem:
|
|
43
|
+
"""
|
|
44
|
+
Collection item with meta information.
|
|
45
|
+
|
|
46
|
+
:param entity_id: Supervisely ID of the item.
|
|
47
|
+
:type entity_id: int
|
|
48
|
+
:param meta: Meta information about the item. Optional, defaults to None.
|
|
49
|
+
:type meta: :class:`CollectionItem.Meta`, optional
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class Meta:
|
|
54
|
+
"""
|
|
55
|
+
Meta information about the item.
|
|
56
|
+
|
|
57
|
+
:param score: Score value of the item that indicates search relevance.
|
|
58
|
+
:type score: float
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
score: Optional[float] = 0.0
|
|
62
|
+
|
|
63
|
+
def to_json(self) -> dict:
|
|
64
|
+
"""
|
|
65
|
+
Convert meta information to a JSON-compatible dictionary.
|
|
66
|
+
|
|
67
|
+
:return: Dictionary with meta information.
|
|
68
|
+
:rtype: dict
|
|
69
|
+
"""
|
|
70
|
+
return {ApiField.SCORE: self.score}
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_json(cls, data: dict) -> "CollectionItem.Meta":
|
|
74
|
+
"""
|
|
75
|
+
Create Meta from a JSON-compatible dictionary.
|
|
76
|
+
|
|
77
|
+
:param data: Dictionary with meta information.
|
|
78
|
+
:type data: dict
|
|
79
|
+
:return: Meta object.
|
|
80
|
+
:rtype: CollectionItem.Meta
|
|
81
|
+
"""
|
|
82
|
+
return cls(score=data.get(ApiField.SCORE, 0.0))
|
|
83
|
+
|
|
84
|
+
entity_id: int
|
|
85
|
+
meta: Optional[Meta] = None
|
|
86
|
+
|
|
87
|
+
def to_json(self) -> dict:
|
|
88
|
+
"""
|
|
89
|
+
Convert collection item to a JSON-compatible dictionary.
|
|
90
|
+
|
|
91
|
+
:return: Dictionary with collection item data.
|
|
92
|
+
:rtype: dict
|
|
93
|
+
"""
|
|
94
|
+
result = {ApiField.ENTITY_ID: self.entity_id}
|
|
95
|
+
if self.meta is not None:
|
|
96
|
+
result[ApiField.META] = self.meta.to_json()
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def from_json(cls, data: dict) -> "CollectionItem":
|
|
101
|
+
"""
|
|
102
|
+
Create CollectionItem from a JSON-compatible dictionary.
|
|
103
|
+
|
|
104
|
+
:param data: Dictionary with collection item data.
|
|
105
|
+
:type data: dict
|
|
106
|
+
:return: CollectionItem object.
|
|
107
|
+
:rtype: CollectionItem
|
|
108
|
+
"""
|
|
109
|
+
meta_data = data.get(ApiField.META)
|
|
110
|
+
meta = cls.Meta.from_json(meta_data) if meta_data is not None else None
|
|
111
|
+
return cls(entity_id=data[ApiField.ENTITY_ID], meta=meta)
|
|
112
|
+
|
|
17
113
|
|
|
18
114
|
class EntitiesCollectionInfo(NamedTuple):
|
|
115
|
+
"""
|
|
116
|
+
Object with entitites collection parameters from Supervisely.
|
|
117
|
+
|
|
118
|
+
:Example:
|
|
119
|
+
|
|
120
|
+
.. code-block:: python
|
|
121
|
+
|
|
122
|
+
EntitiesCollectionInfo(
|
|
123
|
+
id=1,
|
|
124
|
+
team_id=2,
|
|
125
|
+
project_id=3,
|
|
126
|
+
name="collection_name",
|
|
127
|
+
created_at="2023-01-01T00:00:00Z",
|
|
128
|
+
updated_at="2023-01-02T00:00:00Z",
|
|
129
|
+
description="This is a collection",
|
|
130
|
+
options={"key": "value"},
|
|
131
|
+
type="default",
|
|
132
|
+
ai_search_key="search_key"
|
|
133
|
+
)
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
#: ID of the collection.
|
|
19
137
|
id: int
|
|
138
|
+
|
|
139
|
+
#: Name of the collection.
|
|
20
140
|
name: str
|
|
141
|
+
|
|
142
|
+
#: ID of the team.
|
|
21
143
|
team_id: int
|
|
144
|
+
|
|
145
|
+
#: ID of the project.
|
|
22
146
|
project_id: int
|
|
23
|
-
|
|
147
|
+
|
|
148
|
+
#: Date and time when the collection was created.
|
|
24
149
|
created_at: str
|
|
150
|
+
|
|
151
|
+
#: Date and time when the collection was last updated.
|
|
25
152
|
updated_at: str
|
|
26
153
|
|
|
154
|
+
#: Description of the collection.
|
|
155
|
+
description: Optional[str] = None
|
|
156
|
+
|
|
157
|
+
#: Additional options for the collection.
|
|
158
|
+
options: Dict[str, Union[str, int, bool]] = None
|
|
159
|
+
|
|
160
|
+
#: Type of the collection.
|
|
161
|
+
type: str = CollectionType.DEFAULT
|
|
162
|
+
|
|
163
|
+
#: AI search key for the collection.
|
|
164
|
+
ai_search_key: Optional[str] = None
|
|
165
|
+
|
|
27
166
|
|
|
28
167
|
class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
29
168
|
"""
|
|
@@ -76,23 +215,72 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
76
215
|
ApiField.NAME,
|
|
77
216
|
ApiField.TEAM_ID,
|
|
78
217
|
ApiField.PROJECT_ID,
|
|
79
|
-
ApiField.DESCRIPTION,
|
|
80
218
|
ApiField.CREATED_AT,
|
|
81
219
|
ApiField.UPDATED_AT,
|
|
220
|
+
ApiField.DESCRIPTION,
|
|
221
|
+
ApiField.OPTIONS,
|
|
222
|
+
ApiField.TYPE,
|
|
223
|
+
ApiField.AI_SEARCH_KEY,
|
|
82
224
|
]
|
|
83
225
|
|
|
84
226
|
@staticmethod
|
|
85
227
|
def info_tuple_name():
|
|
86
228
|
"""
|
|
87
|
-
|
|
229
|
+
Get string name of :class:`EntitiesCollectionInfo` NamedTuple.
|
|
230
|
+
|
|
231
|
+
:return: NamedTuple name.
|
|
232
|
+
:rtype: :class:`str`
|
|
88
233
|
"""
|
|
89
234
|
return "EntitiesCollectionInfo"
|
|
90
235
|
|
|
91
|
-
def __init__(self, api):
|
|
236
|
+
def __init__(self, api: Api):
|
|
92
237
|
ModuleApi.__init__(self, api)
|
|
238
|
+
self._api = api
|
|
239
|
+
|
|
240
|
+
def _convert_json_info(self, info: dict, skip_missing=True) -> EntitiesCollectionInfo:
|
|
241
|
+
"""
|
|
242
|
+
Differs from the original method by using skip_missing equal to True by default.
|
|
243
|
+
Also unpacks 'meta' field to top level for fields in info_sequence.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
def _get_value(dict, field_name, skip_missing):
|
|
247
|
+
if skip_missing is True:
|
|
248
|
+
return dict.get(field_name, None)
|
|
249
|
+
else:
|
|
250
|
+
return dict[field_name]
|
|
251
|
+
|
|
252
|
+
if info is None:
|
|
253
|
+
return None
|
|
254
|
+
else:
|
|
255
|
+
field_values = []
|
|
256
|
+
meta = info.get("meta", {})
|
|
257
|
+
|
|
258
|
+
for field_name in self.info_sequence():
|
|
259
|
+
if type(field_name) is str:
|
|
260
|
+
# Try to get from meta first if the field exists there
|
|
261
|
+
if field_name in meta:
|
|
262
|
+
field_values.append(meta.get(field_name))
|
|
263
|
+
else:
|
|
264
|
+
field_values.append(_get_value(info, field_name, skip_missing))
|
|
265
|
+
elif type(field_name) is tuple:
|
|
266
|
+
value = None
|
|
267
|
+
for sub_name in field_name[0]:
|
|
268
|
+
if value is None:
|
|
269
|
+
value = _get_value(info, sub_name, skip_missing)
|
|
270
|
+
else:
|
|
271
|
+
value = _get_value(value, sub_name, skip_missing)
|
|
272
|
+
field_values.append(value)
|
|
273
|
+
else:
|
|
274
|
+
raise RuntimeError("Can not parse field {!r}".format(field_name))
|
|
275
|
+
return self.InfoType(*field_values)
|
|
93
276
|
|
|
94
277
|
def create(
|
|
95
|
-
self,
|
|
278
|
+
self,
|
|
279
|
+
project_id: int,
|
|
280
|
+
name: str,
|
|
281
|
+
description: Optional[str] = None,
|
|
282
|
+
type: str = CollectionType.DEFAULT,
|
|
283
|
+
ai_search_key: Optional[str] = None,
|
|
96
284
|
) -> EntitiesCollectionInfo:
|
|
97
285
|
"""
|
|
98
286
|
Creates Entities Collections.
|
|
@@ -103,6 +291,10 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
103
291
|
:type name: str
|
|
104
292
|
:param description: Entities Collection description.
|
|
105
293
|
:type description: str
|
|
294
|
+
:param type: Type of the collection. Defaults to "default".
|
|
295
|
+
:type type: str
|
|
296
|
+
:param ai_search_key: AI search key for the collection. Defaults to None.
|
|
297
|
+
:type ai_search_key: Optional[str]
|
|
106
298
|
:return: Information about new Entities Collection
|
|
107
299
|
:rtype: :class:`EntitiesCollectionInfo`
|
|
108
300
|
:Usage example:
|
|
@@ -115,28 +307,77 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
115
307
|
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
116
308
|
api = sly.Api.from_env()
|
|
117
309
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
310
|
+
project_id = 123
|
|
311
|
+
name = "Chihuahuas"
|
|
312
|
+
description = "Collection of Chihuahuas"
|
|
313
|
+
type = CollectionType.AI_SEARCH
|
|
314
|
+
ai_search_key = "0ed6a5256433bbe32822949d563d476a"
|
|
315
|
+
|
|
316
|
+
new_collection = api.entities_collection.create(project_id, name, description, type, ai_search_key)
|
|
121
317
|
print(new_collection)
|
|
122
318
|
"""
|
|
123
|
-
|
|
124
|
-
data = {
|
|
319
|
+
method = "entities-collections.add"
|
|
320
|
+
data = {
|
|
321
|
+
ApiField.PROJECT_ID: project_id,
|
|
322
|
+
ApiField.NAME: name,
|
|
323
|
+
ApiField.TYPE: type,
|
|
324
|
+
}
|
|
125
325
|
if description is not None:
|
|
126
326
|
data[ApiField.DESCRIPTION] = description
|
|
127
|
-
|
|
327
|
+
if ai_search_key is not None:
|
|
328
|
+
if type != CollectionType.AI_SEARCH:
|
|
329
|
+
raise ValueError(
|
|
330
|
+
"ai_search_key is only available for creation AI Search collection type."
|
|
331
|
+
)
|
|
332
|
+
if ApiField.META not in data:
|
|
333
|
+
data[ApiField.META] = {}
|
|
334
|
+
data[ApiField.META][ApiField.AI_SEARCH_KEY] = ai_search_key
|
|
335
|
+
elif type == CollectionType.AI_SEARCH:
|
|
336
|
+
raise ValueError("ai_search_key is required for AI Search collection type.")
|
|
337
|
+
response = self._api.post(method, data)
|
|
128
338
|
return self._convert_json_info(response.json())
|
|
129
339
|
|
|
340
|
+
def remove(self, id: int, force: bool = False):
|
|
341
|
+
"""
|
|
342
|
+
Remove Entites Collection with the specified ID from the Supervisely server.
|
|
343
|
+
|
|
344
|
+
If `force` is set to True, the collection will be removed permanently.
|
|
345
|
+
If `force` is set to False, the collection will be disabled instead of removed.
|
|
346
|
+
|
|
347
|
+
:param id: Entites Collection ID in Supervisely
|
|
348
|
+
:type id: int
|
|
349
|
+
:param force: If True, the collection will be removed permanently. Defaults to False.
|
|
350
|
+
:type force: bool
|
|
351
|
+
:return: None
|
|
352
|
+
"""
|
|
353
|
+
self._api.post(
|
|
354
|
+
self._remove_api_method_name(), {ApiField.ID: id, ApiField.HARD_DELETE: force}
|
|
355
|
+
)
|
|
356
|
+
|
|
130
357
|
def get_list(
|
|
131
358
|
self,
|
|
132
359
|
project_id: int,
|
|
133
360
|
filters: Optional[List[Dict[str, str]]] = None,
|
|
361
|
+
with_meta: bool = False,
|
|
362
|
+
collection_type: CollectionType = CollectionType.DEFAULT,
|
|
134
363
|
) -> List[EntitiesCollectionInfo]:
|
|
135
364
|
"""
|
|
136
365
|
Get list of information about Entities Collection for the given project.
|
|
137
366
|
|
|
138
367
|
:param project_id: Project ID in Supervisely.
|
|
139
368
|
:type project_id: int, optional
|
|
369
|
+
:param filters: List of filters to apply to the request. Optional, defaults to None.
|
|
370
|
+
:type filters: List[Dict[str, str]], optional
|
|
371
|
+
:param with_meta: If True, includes meta information in the response. Defaults to False.
|
|
372
|
+
:type with_meta: bool, optional
|
|
373
|
+
:param collection_type: Type of the collection.
|
|
374
|
+
Defaults to CollectionType.DEFAULT.
|
|
375
|
+
|
|
376
|
+
Available types are:
|
|
377
|
+
- CollectionType.DEFAULT
|
|
378
|
+
- CollectionType.AI_SEARCH
|
|
379
|
+
- CollectionType.ALL
|
|
380
|
+
:type collection_type: CollectionType
|
|
140
381
|
:return: List of information about Entities Collections.
|
|
141
382
|
:rtype: :class:`List[EntitiesCollectionInfo]`
|
|
142
383
|
:Usage example:
|
|
@@ -151,19 +392,25 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
151
392
|
|
|
152
393
|
collections = api.entities_collection.get_list(4)
|
|
153
394
|
"""
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
395
|
+
method = "entities-collections.list"
|
|
396
|
+
|
|
397
|
+
data = {
|
|
398
|
+
ApiField.PROJECT_ID: project_id,
|
|
399
|
+
ApiField.FILTER: filters or [],
|
|
400
|
+
ApiField.TYPE: collection_type,
|
|
401
|
+
}
|
|
402
|
+
if with_meta:
|
|
403
|
+
data.update({ApiField.EXTRA_FIELDS: [ApiField.META]})
|
|
404
|
+
return self.get_list_all_pages(method, data)
|
|
405
|
+
|
|
406
|
+
def get_info_by_id(self, id: int, with_meta: bool = False) -> EntitiesCollectionInfo:
|
|
162
407
|
"""
|
|
163
408
|
Get information about Entities Collection with given ID.
|
|
164
409
|
|
|
165
410
|
:param id: Entities Collection ID in Supervisely.
|
|
166
411
|
:type id: int
|
|
412
|
+
:param with_meta: If True, includes meta information in the response. Defaults to False.
|
|
413
|
+
:type with_meta: bool, optional
|
|
167
414
|
:return: Information about Entities Collection.
|
|
168
415
|
:rtype: :class:`EntitiesCollectionInfo`
|
|
169
416
|
:Usage example:
|
|
@@ -189,7 +436,66 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
189
436
|
# "updatedAt": "2018-08-21T14:25:56.140Z"
|
|
190
437
|
# }
|
|
191
438
|
"""
|
|
192
|
-
|
|
439
|
+
method = "entities-collections.info"
|
|
440
|
+
if with_meta:
|
|
441
|
+
extra_fields = [ApiField.META]
|
|
442
|
+
else:
|
|
443
|
+
extra_fields = None
|
|
444
|
+
return self._get_info_by_id(id, method, extra_fields)
|
|
445
|
+
|
|
446
|
+
def get_info_by_ai_search_key(
|
|
447
|
+
self, project_id: int, ai_search_key: str
|
|
448
|
+
) -> Optional[EntitiesCollectionInfo]:
|
|
449
|
+
"""
|
|
450
|
+
Get information about Entities Collection of type `CollectionType.AI_SEARCH` with given AI search key.
|
|
451
|
+
|
|
452
|
+
:param project_id: Project ID in Supervisely.
|
|
453
|
+
:type project_id: int
|
|
454
|
+
:param ai_search_key: AI search key for the collection.
|
|
455
|
+
:type ai_search_key: str
|
|
456
|
+
:return: Information about Entities Collection.
|
|
457
|
+
:rtype: :class:`EntitiesCollectionInfo`
|
|
458
|
+
:Usage example:
|
|
459
|
+
.. code-block:: python
|
|
460
|
+
import supervisely as sly
|
|
461
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
462
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
463
|
+
api = sly.Api.from_env()
|
|
464
|
+
project_id = 123
|
|
465
|
+
ai_search_key = "0ed6a5256433bbe32822949d563d476a"
|
|
466
|
+
|
|
467
|
+
# Get collection by AI search key
|
|
468
|
+
collection = api.entities_collection.get_info_by_ai_search_key(project_id, ai_search_key)
|
|
469
|
+
"""
|
|
470
|
+
collections = self.get_list(
|
|
471
|
+
project_id=project_id,
|
|
472
|
+
with_meta=True,
|
|
473
|
+
collection_type=CollectionType.AI_SEARCH,
|
|
474
|
+
)
|
|
475
|
+
return next(
|
|
476
|
+
(collection for collection in collections if collection.ai_search_key == ai_search_key),
|
|
477
|
+
None,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
def _get_response_by_id(self, id, method, id_field, extra_fields=None):
|
|
481
|
+
"""Differs from the original method by using extra_fields."""
|
|
482
|
+
try:
|
|
483
|
+
data = {id_field: id}
|
|
484
|
+
if extra_fields is not None:
|
|
485
|
+
data.update({ApiField.EXTRA_FIELDS: extra_fields})
|
|
486
|
+
return self._api.post(method, data)
|
|
487
|
+
except requests.exceptions.HTTPError as error:
|
|
488
|
+
if error.response.status_code == 404:
|
|
489
|
+
return None
|
|
490
|
+
else:
|
|
491
|
+
raise error
|
|
492
|
+
|
|
493
|
+
def _get_info_by_id(self, id, method, extra_fields=None):
|
|
494
|
+
"""_get_info_by_id"""
|
|
495
|
+
response = self._get_response_by_id(
|
|
496
|
+
id, method, id_field=ApiField.ID, extra_fields=extra_fields
|
|
497
|
+
)
|
|
498
|
+
return self._convert_json_info(response.json()) if (response is not None) else None
|
|
193
499
|
|
|
194
500
|
def _get_update_method(self):
|
|
195
501
|
""" """
|
|
@@ -199,23 +505,22 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
199
505
|
""" """
|
|
200
506
|
return "entities-collections.remove"
|
|
201
507
|
|
|
202
|
-
def
|
|
203
|
-
""" """
|
|
204
|
-
return "entities-collections.editInfo"
|
|
205
|
-
|
|
206
|
-
def add_items(self, id: int, items: List[int]) -> List[Dict[str, int]]:
|
|
508
|
+
def add_items(self, id: int, items: List[Union[int, CollectionItem]]) -> List[Dict[str, int]]:
|
|
207
509
|
"""
|
|
208
510
|
Add items to Entities Collection.
|
|
209
511
|
|
|
210
512
|
:param id: Entities Collection ID in Supervisely.
|
|
211
513
|
:type id: int
|
|
212
|
-
:param items: List of
|
|
213
|
-
:type items: List[int]
|
|
514
|
+
:param items: List of items to add to the collection. Could be a list of entity IDs (int) or CollectionItem objects.
|
|
515
|
+
:type items: List[Union[int, CollectionItem]]
|
|
516
|
+
:return: List of added items with their IDs and creation timestamps.
|
|
517
|
+
:rtype: List[Dict[str, int]]
|
|
214
518
|
:Usage example:
|
|
215
519
|
|
|
216
520
|
.. code-block:: python
|
|
217
521
|
|
|
218
522
|
import supervisely as sly
|
|
523
|
+
from supervisely.api.entities_collection_api import CollectionItem
|
|
219
524
|
|
|
220
525
|
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
221
526
|
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
@@ -223,14 +528,24 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
223
528
|
|
|
224
529
|
collection_id = 2
|
|
225
530
|
item_ids = [525, 526]
|
|
226
|
-
|
|
531
|
+
items = [CollectionItem(entity_id=item_id) for item_id in item_ids]
|
|
532
|
+
new_items = api.entities_collection.add_items(collection_id, items)
|
|
227
533
|
print(new_items)
|
|
228
534
|
# Output: [
|
|
229
535
|
# {"id": 1, "entityId": 525, 'createdAt': '2025-04-10T08:49:41.852Z'},
|
|
230
536
|
# {"id": 2, "entityId": 526, 'createdAt': '2025-04-10T08:49:41.852Z'}
|
|
231
537
|
]
|
|
232
538
|
"""
|
|
233
|
-
|
|
539
|
+
if all(isinstance(item, int) for item in items):
|
|
540
|
+
data = {ApiField.COLLECTION_ID: id, ApiField.ENTITY_IDS: items}
|
|
541
|
+
elif all(isinstance(item, CollectionItem) for item in items):
|
|
542
|
+
items = [item.to_json() for item in items]
|
|
543
|
+
data = {ApiField.COLLECTION_ID: id, ApiField.ENTITY_ITEMS: items}
|
|
544
|
+
else:
|
|
545
|
+
raise ValueError(
|
|
546
|
+
"Items list must contain only integers or only CollectionItem instances, not a mix."
|
|
547
|
+
)
|
|
548
|
+
|
|
234
549
|
response = self._api.post("entities-collections.items.bulk.add", data)
|
|
235
550
|
response = response.json()
|
|
236
551
|
if len(response["missing"]) > 0:
|
|
@@ -239,16 +554,29 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
239
554
|
)
|
|
240
555
|
return response["items"]
|
|
241
556
|
|
|
242
|
-
def get_items(
|
|
557
|
+
def get_items(
|
|
558
|
+
self,
|
|
559
|
+
collection_id: int,
|
|
560
|
+
collection_type: CollectionTypeFilter,
|
|
561
|
+
project_id: Optional[int] = None,
|
|
562
|
+
ai_search_threshold: Optional[float] = None,
|
|
563
|
+
ai_search_threshold_direction: AiSearchThresholdDirection = AiSearchThresholdDirection.ABOVE,
|
|
564
|
+
) -> List[ImageInfo]:
|
|
243
565
|
"""
|
|
244
566
|
Get items from Entities Collection.
|
|
245
567
|
|
|
246
|
-
:param
|
|
247
|
-
:type
|
|
568
|
+
:param collection_id: Entities Collection ID in Supervisely.
|
|
569
|
+
:type collection_id: int
|
|
570
|
+
:param collection_type: Type of the collection. Can be CollectionTypeFilter.AI_SEARCH or CollectionTypeFilter.DEFAULT.
|
|
571
|
+
:type collection_type: CollectionTypeFilter
|
|
248
572
|
:param project_id: Project ID in Supervisely.
|
|
249
573
|
:type project_id: int, optional
|
|
250
|
-
:
|
|
251
|
-
:
|
|
574
|
+
:param ai_search_threshold: AI search threshold for filtering items. Optional, defaults to None.
|
|
575
|
+
:type ai_search_threshold: float, optional
|
|
576
|
+
:param ai_search_threshold_direction: Direction for the AI search threshold. Optional, defaults to 'above'.
|
|
577
|
+
:type ai_search_threshold_direction: str
|
|
578
|
+
:return: List of ImageInfo objects.
|
|
579
|
+
:rtype: List[ImageInfo]
|
|
252
580
|
:raises RuntimeError: If Entities Collection with given ID not found.
|
|
253
581
|
:Usage example:
|
|
254
582
|
|
|
@@ -266,12 +594,24 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
266
594
|
print(item_ids)
|
|
267
595
|
"""
|
|
268
596
|
if project_id is None:
|
|
269
|
-
info = self.get_info_by_id(
|
|
597
|
+
info = self.get_info_by_id(collection_id)
|
|
270
598
|
if info is None:
|
|
271
|
-
raise RuntimeError(f"Entities Collection with id={
|
|
599
|
+
raise RuntimeError(f"Entities Collection with id={collection_id} not found.")
|
|
272
600
|
project_id = info.project_id
|
|
273
601
|
|
|
274
|
-
|
|
602
|
+
if collection_type == CollectionTypeFilter.AI_SEARCH:
|
|
603
|
+
return self._api.image.get_list(
|
|
604
|
+
project_id=project_id,
|
|
605
|
+
ai_search_collection_id=collection_id,
|
|
606
|
+
extra_fields=[ApiField.EMBEDDINGS_UPDATED_AT],
|
|
607
|
+
ai_search_threshold=ai_search_threshold,
|
|
608
|
+
ai_search_threshold_direction=ai_search_threshold_direction,
|
|
609
|
+
)
|
|
610
|
+
else:
|
|
611
|
+
return self._api.image.get_list(
|
|
612
|
+
project_id=project_id,
|
|
613
|
+
entities_collection_id=collection_id,
|
|
614
|
+
)
|
|
275
615
|
|
|
276
616
|
def remove_items(self, id: int, items: List[int]) -> List[Dict[str, int]]:
|
|
277
617
|
"""
|
|
@@ -279,7 +619,7 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
279
619
|
|
|
280
620
|
:param id: Entities Collection ID in Supervisely.
|
|
281
621
|
:type id: int
|
|
282
|
-
:param items: List of
|
|
622
|
+
:param items: List of entity IDs in Supervisely, e.g. image IDs etc.
|
|
283
623
|
:type items: List[int]
|
|
284
624
|
:Usage example:
|
|
285
625
|
|
|
@@ -294,8 +634,10 @@ class EntitiesCollectionApi(UpdateableModule, RemoveableModuleApi):
|
|
|
294
634
|
# print(removed_items)
|
|
295
635
|
# Output: [{"id": 1, "entityId": 525}, {"id": 2, "entityId": 526}]
|
|
296
636
|
"""
|
|
637
|
+
method = "entities-collections.items.bulk.remove"
|
|
638
|
+
|
|
297
639
|
data = {ApiField.COLLECTION_ID: id, ApiField.ENTITY_IDS: items}
|
|
298
|
-
response = self._api.post(
|
|
640
|
+
response = self._api.post(method, data)
|
|
299
641
|
response = response.json()
|
|
300
642
|
if len(response["missing"]) > 0:
|
|
301
643
|
logger.warning(
|