supervisely 6.73.399__py3-none-any.whl → 6.73.400__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 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.entities_collection_api as entities_collection_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: Optional[bool] = False,
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 and token 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:
@@ -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 typing import Dict, List, NamedTuple, Optional
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
- description: str
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
- NamedTuple name - **EntitiesCollectionInfo**.
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, project_id: int, name: str, description: Optional[str] = None
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
- user_name = 'Collection #1'
119
- project_id = 602
120
- new_collection = api.entities_collection.create(name, project_id)
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 = {ApiField.NAME: name, ApiField.PROJECT_ID: project_id}
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
- response = self._api.post("entities-collections.add", data)
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
- 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:
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
- return self._get_info_by_id(id, "entities-collections.info")
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 _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]]:
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 item IDs in Supervisely.
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
- new_items = api.entities_collection.add_items(collection_id, item_ids)
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
- data = {ApiField.COLLECTION_ID: id, ApiField.ENTITY_IDS: items}
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(self, id: int, project_id: Optional[int] = None) -> List[int]:
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 id: Entities Collection ID in Supervisely.
247
- :type id: int
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
- :return: List of item IDs in Supervisely.
251
- :rtype: List[int]
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(id)
597
+ info = self.get_info_by_id(collection_id)
270
598
  if info is None:
271
- raise RuntimeError(f"Entities Collection with id={id} not found.")
599
+ raise RuntimeError(f"Entities Collection with id={collection_id} not found.")
272
600
  project_id = info.project_id
273
601
 
274
- return self._api.image.get_list(project_id=project_id, entities_collection_id=id)
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 item IDs in Supervisely.
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("entities-collections.items.bulk.remove", data)
640
+ response = self._api.post(method, data)
299
641
  response = response.json()
300
642
  if len(response["missing"]) > 0:
301
643
  logger.warning(