supervisely 6.73.398__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 +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/convert/image/pascal_voc/pascal_voc_converter.py +46 -4
- supervisely/convert/image/pascal_voc/pascal_voc_helper.py +26 -11
- {supervisely-6.73.398.dist-info → supervisely-6.73.400.dist-info}/METADATA +1 -1
- {supervisely-6.73.398.dist-info → supervisely-6.73.400.dist-info}/RECORD +16 -16
- {supervisely-6.73.398.dist-info → supervisely-6.73.400.dist-info}/LICENSE +0 -0
- {supervisely-6.73.398.dist-info → supervisely-6.73.400.dist-info}/WHEEL +0 -0
- {supervisely-6.73.398.dist-info → supervisely-6.73.400.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.398.dist-info → supervisely-6.73.400.dist-info}/top_level.txt +0 -0
supervisely/api/image_api.py
CHANGED
|
@@ -17,7 +17,7 @@ from collections import defaultdict
|
|
|
17
17
|
from concurrent.futures import ThreadPoolExecutor
|
|
18
18
|
from contextlib import contextmanager
|
|
19
19
|
from dataclasses import dataclass
|
|
20
|
-
from datetime import datetime
|
|
20
|
+
from datetime import datetime, timezone
|
|
21
21
|
from functools import partial
|
|
22
22
|
from math import ceil
|
|
23
23
|
from pathlib import Path
|
|
@@ -57,6 +57,10 @@ from supervisely.annotation.tag import Tag
|
|
|
57
57
|
from supervisely.annotation.tag_meta import TagApplicableTo, TagMeta, TagValueType
|
|
58
58
|
from supervisely.api.constants import DOWNLOAD_BATCH_SIZE
|
|
59
59
|
from supervisely.api.dataset_api import DatasetInfo
|
|
60
|
+
from supervisely.api.entities_collection_api import (
|
|
61
|
+
AiSearchThresholdDirection,
|
|
62
|
+
CollectionTypeFilter,
|
|
63
|
+
)
|
|
60
64
|
from supervisely.api.entity_annotation.figure_api import FigureApi
|
|
61
65
|
from supervisely.api.entity_annotation.tag_api import TagApi
|
|
62
66
|
from supervisely.api.file_api import FileInfo
|
|
@@ -379,6 +383,15 @@ class ImageInfo(NamedTuple):
|
|
|
379
383
|
#: :class:`int`: Bytes offset of the blob file that points to the end of the image data.
|
|
380
384
|
offset_end: Optional[int] = None
|
|
381
385
|
|
|
386
|
+
#: :class:`dict`: Image meta that could have the confidence level of the image in Enntities Collection of type AI Search.
|
|
387
|
+
ai_search_meta: Optional[dict] = None
|
|
388
|
+
|
|
389
|
+
#: :class:`str`: Timestamp of the last update of the embeddings for the image.
|
|
390
|
+
#: This field is used to track when the embeddings were last updated.
|
|
391
|
+
#: It is set to None if the embeddings have never been computed for the image.
|
|
392
|
+
#: Format: "YYYY-MM-DDTHH:MM:SS.sssZ"
|
|
393
|
+
embeddings_updated_at: Optional[str] = None
|
|
394
|
+
|
|
382
395
|
# DO NOT DELETE THIS COMMENT
|
|
383
396
|
#! New fields must be added with default values to keep backward compatibility.
|
|
384
397
|
|
|
@@ -456,6 +469,8 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
456
469
|
ApiField.DOWNLOAD_ID,
|
|
457
470
|
ApiField.OFFSET_START,
|
|
458
471
|
ApiField.OFFSET_END,
|
|
472
|
+
ApiField.AI_SEARCH_META,
|
|
473
|
+
ApiField.EMBEDDINGS_UPDATED_AT,
|
|
459
474
|
]
|
|
460
475
|
|
|
461
476
|
@staticmethod
|
|
@@ -602,6 +617,10 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
602
617
|
fields: Optional[List[str]] = None,
|
|
603
618
|
recursive: Optional[bool] = False,
|
|
604
619
|
entities_collection_id: Optional[int] = None,
|
|
620
|
+
ai_search_collection_id: Optional[int] = None,
|
|
621
|
+
ai_search_threshold: Optional[float] = None,
|
|
622
|
+
ai_search_threshold_direction: AiSearchThresholdDirection = AiSearchThresholdDirection.ABOVE,
|
|
623
|
+
extra_fields: Optional[List[str]] = None,
|
|
605
624
|
) -> List[ImageInfo]:
|
|
606
625
|
"""
|
|
607
626
|
List of Images in the given :class:`Dataset<supervisely.project.project.Dataset>`.
|
|
@@ -628,8 +647,16 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
628
647
|
:type fields: List[str], optional
|
|
629
648
|
:param recursive: If True, returns all images from dataset recursively (including images in nested datasets).
|
|
630
649
|
:type recursive: bool, optional
|
|
631
|
-
:param entities_collection_id: :class:`EntitiesCollection
|
|
650
|
+
:param entities_collection_id: :class:`EntitiesCollection` ID of `Default` type to which the images belong.
|
|
632
651
|
:type entities_collection_id: int, optional
|
|
652
|
+
:param ai_search_collection_id: :class:`EntitiesCollection` ID of type `AI Search` to which the images belong.
|
|
653
|
+
:type ai_search_collection_id: int, optional
|
|
654
|
+
:param ai_search_threshold: Confidence level to filter images in AI Search collection.
|
|
655
|
+
:type ai_search_threshold: float, optional
|
|
656
|
+
:param ai_search_threshold_direction: Direction of the confidence level filter. One of {'above' (default), 'below'}.
|
|
657
|
+
:type ai_search_threshold_direction: str, optional
|
|
658
|
+
:param extra_fields: List of extra fields to return. If None, returns no extra fields.
|
|
659
|
+
:type extra_fields: List[str], optional
|
|
633
660
|
:return: Objects with image information from Supervisely.
|
|
634
661
|
:rtype: :class:`List[ImageInfo]<ImageInfo>`
|
|
635
662
|
:Usage example:
|
|
@@ -703,20 +730,44 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
703
730
|
},
|
|
704
731
|
}
|
|
705
732
|
]
|
|
706
|
-
|
|
733
|
+
# Handle collection filtering
|
|
734
|
+
collection_info = None
|
|
735
|
+
if entities_collection_id is not None and ai_search_collection_id is not None:
|
|
736
|
+
raise ValueError(
|
|
737
|
+
"You can use only one of entities_collection_id or ai_search_collection_id"
|
|
738
|
+
)
|
|
739
|
+
elif entities_collection_id is not None:
|
|
740
|
+
collection_info = (CollectionTypeFilter.DEFAULT, entities_collection_id)
|
|
741
|
+
elif ai_search_collection_id is not None:
|
|
742
|
+
collection_info = (CollectionTypeFilter.AI_SEARCH, ai_search_collection_id)
|
|
743
|
+
|
|
744
|
+
if collection_info is not None:
|
|
745
|
+
collection_type, collection_id = collection_info
|
|
707
746
|
if ApiField.FILTERS not in data:
|
|
708
747
|
data[ApiField.FILTERS] = []
|
|
748
|
+
|
|
749
|
+
collection_filter_data = {
|
|
750
|
+
ApiField.COLLECTION_ID: collection_id,
|
|
751
|
+
ApiField.INCLUDE: True,
|
|
752
|
+
}
|
|
753
|
+
if ai_search_threshold is not None:
|
|
754
|
+
if collection_type != CollectionTypeFilter.AI_SEARCH:
|
|
755
|
+
raise ValueError(
|
|
756
|
+
"ai_search_threshold is only available for AI Search collection"
|
|
757
|
+
)
|
|
758
|
+
collection_filter_data[ApiField.THRESHOLD] = ai_search_threshold
|
|
759
|
+
collection_filter_data[ApiField.THRESHOLD_DIRECTION] = ai_search_threshold_direction
|
|
709
760
|
data[ApiField.FILTERS].append(
|
|
710
761
|
{
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
ApiField.COLLECTION_ID: entities_collection_id,
|
|
714
|
-
ApiField.INCLUDE: True,
|
|
715
|
-
},
|
|
762
|
+
ApiField.TYPE: collection_type,
|
|
763
|
+
ApiField.DATA: collection_filter_data,
|
|
716
764
|
}
|
|
717
765
|
)
|
|
766
|
+
|
|
718
767
|
if fields is not None:
|
|
719
768
|
data[ApiField.FIELDS] = fields
|
|
769
|
+
if extra_fields is not None:
|
|
770
|
+
data[ApiField.EXTRA_FIELDS] = extra_fields
|
|
720
771
|
return self.get_list_all_pages(
|
|
721
772
|
"images.list",
|
|
722
773
|
data=data,
|
|
@@ -3679,9 +3730,7 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
3679
3730
|
|
|
3680
3731
|
return self.tag.add_to_entities_json(project_id, data, batch_size, log_progress)
|
|
3681
3732
|
|
|
3682
|
-
def update_tag_value(
|
|
3683
|
-
self, tag_id: int, value: Union[str, float]
|
|
3684
|
-
) -> Dict:
|
|
3733
|
+
def update_tag_value(self, tag_id: int, value: Union[str, float]) -> Dict:
|
|
3685
3734
|
"""
|
|
3686
3735
|
Update tag value with given ID.
|
|
3687
3736
|
|
|
@@ -4412,11 +4461,14 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
4412
4461
|
|
|
4413
4462
|
def set_remote(self, images: List[int], links: List[str]):
|
|
4414
4463
|
"""
|
|
4415
|
-
|
|
4464
|
+
Updates the source of existing images by setting new remote links.
|
|
4465
|
+
This method is used when an image was initially uploaded as a file or added via a link,
|
|
4466
|
+
but later it was decided to change its location (e.g., moved to another storage or re-uploaded elsewhere).
|
|
4467
|
+
By updating the link, the image source can be redirected to the new location.
|
|
4416
4468
|
|
|
4417
4469
|
:param images: List of image ids.
|
|
4418
4470
|
:type images: List[int]
|
|
4419
|
-
:param links: List of remote links.
|
|
4471
|
+
:param links: List of new remote links.
|
|
4420
4472
|
:type links: List[str]
|
|
4421
4473
|
:return: json-encoded content of a response.
|
|
4422
4474
|
|
|
@@ -4424,17 +4476,17 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
4424
4476
|
|
|
4425
4477
|
.. code-block:: python
|
|
4426
4478
|
|
|
4427
|
-
|
|
4479
|
+
import supervisely as sly
|
|
4428
4480
|
|
|
4429
|
-
|
|
4481
|
+
api = sly.Api.from_env()
|
|
4430
4482
|
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4483
|
+
images = [123, 124, 125]
|
|
4484
|
+
links = [
|
|
4485
|
+
"s3://bucket/lemons/ds1/img/IMG_444.jpeg",
|
|
4486
|
+
"s3://bucket/lemons/ds1/img/IMG_445.jpeg",
|
|
4487
|
+
"s3://bucket/lemons/ds1/img/IMG_446.jpeg",
|
|
4488
|
+
]
|
|
4489
|
+
result = api.image.set_remote(images, links)
|
|
4438
4490
|
"""
|
|
4439
4491
|
|
|
4440
4492
|
if len(images) == 0:
|
|
@@ -5412,3 +5464,47 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
5412
5464
|
)
|
|
5413
5465
|
tasks.append(task)
|
|
5414
5466
|
await asyncio.gather(*tasks)
|
|
5467
|
+
|
|
5468
|
+
def set_embeddings_updated_at(self, ids: List[int], timestamps: Optional[List[str]] = None):
|
|
5469
|
+
"""
|
|
5470
|
+
Updates the `updated_at` field of images with the timestamp of the embeddings were created.
|
|
5471
|
+
|
|
5472
|
+
:param ids: List of Image IDs in Supervisely.
|
|
5473
|
+
:type ids: List[int]
|
|
5474
|
+
:param timestamps: List of timestamps in ISO format. If None, uses current time.
|
|
5475
|
+
You could set timestamps to [None, ..., None] if you need to recreate embeddings for images.
|
|
5476
|
+
:type timestamps: List[str], optional
|
|
5477
|
+
:return: None
|
|
5478
|
+
:rtype: NoneType
|
|
5479
|
+
:Usage example:
|
|
5480
|
+
|
|
5481
|
+
.. code-block:: python
|
|
5482
|
+
|
|
5483
|
+
import supervisely as sly
|
|
5484
|
+
import datetime
|
|
5485
|
+
|
|
5486
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
5487
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
5488
|
+
api = sly.Api.from_env()
|
|
5489
|
+
|
|
5490
|
+
image_ids = [123, 456, 789]
|
|
5491
|
+
timestamps = [datetime.datetime.now().isoformat() for _ in image_ids]
|
|
5492
|
+
api.image.set_embeddings_updated_at(image_ids, timestamps)
|
|
5493
|
+
"""
|
|
5494
|
+
method = "images.embeddings-updated-at.update"
|
|
5495
|
+
|
|
5496
|
+
if timestamps is None:
|
|
5497
|
+
timestamps = [datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ") for _ in ids]
|
|
5498
|
+
|
|
5499
|
+
if len(ids) != len(timestamps):
|
|
5500
|
+
raise ValueError(
|
|
5501
|
+
f"Length of ids and timestamps should be equal. {len(ids)} != {len(timestamps)}"
|
|
5502
|
+
)
|
|
5503
|
+
images = [
|
|
5504
|
+
{ApiField.ID: image_id, ApiField.EMBEDDINGS_UPDATED_AT: timestamp}
|
|
5505
|
+
for image_id, timestamp in zip(ids, timestamps)
|
|
5506
|
+
]
|
|
5507
|
+
self._api.post(
|
|
5508
|
+
method,
|
|
5509
|
+
{ApiField.IMAGES: images},
|
|
5510
|
+
)
|
supervisely/api/module_api.py
CHANGED
|
@@ -663,6 +663,32 @@ class ApiField:
|
|
|
663
663
|
""""""
|
|
664
664
|
QUALITY_CHECK_USER_IDS = "qualityCheckUserIds"
|
|
665
665
|
""""""
|
|
666
|
+
EMBEDDINGS = "embeddings"
|
|
667
|
+
""""""
|
|
668
|
+
EMBEDDINGS_ENABLED = "embeddingsEnabled"
|
|
669
|
+
""""""
|
|
670
|
+
EMBEDDINGS_UPDATED_AT = "embeddingsUpdatedAt"
|
|
671
|
+
""""""
|
|
672
|
+
EMBEDDINGS_IN_PROGRESS = "embeddingsInProgress"
|
|
673
|
+
""""""
|
|
674
|
+
AI_SEARCH_KEY = "aiSearchKey"
|
|
675
|
+
""""""
|
|
676
|
+
AI_SEARCH_META = "aiSearchMeta"
|
|
677
|
+
""""""
|
|
678
|
+
ENTITY_ITEMS = "entityItems"
|
|
679
|
+
""""""
|
|
680
|
+
SCORE = "score"
|
|
681
|
+
""""""
|
|
682
|
+
HARD_DELETE = "hardDelete"
|
|
683
|
+
""""""
|
|
684
|
+
THRESHOLD = "threshold"
|
|
685
|
+
""""""
|
|
686
|
+
THRESHOLD_DIRECTION = "thresholdDirection"
|
|
687
|
+
""""""
|
|
688
|
+
METHOD = "method"
|
|
689
|
+
""""""
|
|
690
|
+
PROMPT = "prompt"
|
|
691
|
+
""""""
|
|
666
692
|
UPDATE_STRATEGY = "updateStrategy"
|
|
667
693
|
""""""
|
|
668
694
|
|
supervisely/api/project_api.py
CHANGED
|
@@ -24,7 +24,7 @@ from tqdm import tqdm
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
25
|
from pandas.core.frame import DataFrame
|
|
26
26
|
|
|
27
|
-
from datetime import datetime, timedelta
|
|
27
|
+
from datetime import datetime, timedelta, timezone
|
|
28
28
|
|
|
29
29
|
from supervisely import logger
|
|
30
30
|
from supervisely._utils import (
|
|
@@ -97,6 +97,9 @@ class ProjectInfo(NamedTuple):
|
|
|
97
97
|
import_settings: dict
|
|
98
98
|
version: dict
|
|
99
99
|
created_by_id: int
|
|
100
|
+
embeddings_enabled: Optional[bool] = None
|
|
101
|
+
embeddings_updated_at: Optional[str] = None
|
|
102
|
+
embeddings_in_progress: Optional[bool] = None
|
|
100
103
|
|
|
101
104
|
@property
|
|
102
105
|
def image_preview_url(self):
|
|
@@ -145,6 +148,8 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
145
148
|
project_info = api.project.get_info_by_id(project_id)
|
|
146
149
|
"""
|
|
147
150
|
|
|
151
|
+
debug_messages_sent = {"get_list_versions": False}
|
|
152
|
+
|
|
148
153
|
@staticmethod
|
|
149
154
|
def info_sequence():
|
|
150
155
|
"""
|
|
@@ -195,6 +200,9 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
195
200
|
ApiField.IMPORT_SETTINGS,
|
|
196
201
|
ApiField.VERSION,
|
|
197
202
|
ApiField.CREATED_BY_ID,
|
|
203
|
+
ApiField.EMBEDDINGS_ENABLED,
|
|
204
|
+
ApiField.EMBEDDINGS_UPDATED_AT,
|
|
205
|
+
ApiField.EMBEDDINGS_IN_PROGRESS,
|
|
198
206
|
]
|
|
199
207
|
|
|
200
208
|
@staticmethod
|
|
@@ -227,7 +235,7 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
227
235
|
:type workspace_id: int
|
|
228
236
|
:param filters: List of params to sort output Projects.
|
|
229
237
|
:type filters: List[dict], optional
|
|
230
|
-
:param fields: The list of api fields which will be returned with the response.
|
|
238
|
+
:param fields: The list of api fields which will be returned with the response. You must specify all fields you want to receive, not just additional ones.
|
|
231
239
|
:type fields: List[str]
|
|
232
240
|
|
|
233
241
|
:return: List of all projects with information for the given Workspace. See :class:`info_sequence<info_sequence>`
|
|
@@ -306,25 +314,29 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
306
314
|
# ]
|
|
307
315
|
|
|
308
316
|
"""
|
|
317
|
+
method = "projects.list"
|
|
318
|
+
|
|
319
|
+
debug_message = "While getting list of projects, the following fields are not available: "
|
|
320
|
+
|
|
309
321
|
if ApiField.VERSION in fields:
|
|
310
322
|
fields.remove(ApiField.VERSION)
|
|
311
|
-
|
|
312
|
-
"
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
)
|
|
323
|
+
if self.debug_messages_sent.get("get_list_versions", False) is False:
|
|
324
|
+
self.debug_messages_sent["get_list_versions"] = True
|
|
325
|
+
logger.debug(debug_message + "version. ")
|
|
326
|
+
|
|
327
|
+
data = {
|
|
328
|
+
ApiField.WORKSPACE_ID: workspace_id,
|
|
329
|
+
ApiField.FILTER: filters or [],
|
|
330
|
+
ApiField.FIELDS: fields,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return self.get_list_all_pages(method, data)
|
|
322
334
|
|
|
323
335
|
def get_info_by_id(
|
|
324
336
|
self,
|
|
325
337
|
id: int,
|
|
326
338
|
expected_type: Optional[str] = None,
|
|
327
|
-
raise_error:
|
|
339
|
+
raise_error: bool = False,
|
|
328
340
|
) -> ProjectInfo:
|
|
329
341
|
"""
|
|
330
342
|
Get Project information by ID.
|
|
@@ -892,8 +904,6 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
892
904
|
def _convert_json_info(self, info: dict, skip_missing=True) -> ProjectInfo:
|
|
893
905
|
""" """
|
|
894
906
|
res = super()._convert_json_info(info, skip_missing=skip_missing)
|
|
895
|
-
if res.reference_image_url is not None:
|
|
896
|
-
res = res._replace(reference_image_url=res.reference_image_url)
|
|
897
907
|
if res.items_count is None:
|
|
898
908
|
res = res._replace(items_count=res.images_count)
|
|
899
909
|
return ProjectInfo(**res._asdict())
|
|
@@ -1976,6 +1986,7 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
1976
1986
|
per_page: Optional[int] = None,
|
|
1977
1987
|
page: Union[int, Literal["all"]] = "all",
|
|
1978
1988
|
account_type: Optional[str] = None,
|
|
1989
|
+
extra_fields: Optional[List[str]] = None,
|
|
1979
1990
|
) -> dict:
|
|
1980
1991
|
"""
|
|
1981
1992
|
List all available projects from all available teams for the user that match the specified filtering criteria.
|
|
@@ -2009,6 +2020,9 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2009
2020
|
:param account_type: (Deprecated) Type of user account
|
|
2010
2021
|
:type account_type: str, optional
|
|
2011
2022
|
|
|
2023
|
+
:param extra_fields: List of additional fields to be included in the response.
|
|
2024
|
+
:type extra_fields: List[str], optional
|
|
2025
|
+
|
|
2012
2026
|
:return: Search response information and 'ProjectInfo' of all projects that are searched by a given criterion.
|
|
2013
2027
|
:rtype: dict
|
|
2014
2028
|
|
|
@@ -2101,6 +2115,8 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2101
2115
|
logger.warning(
|
|
2102
2116
|
"The 'account_type' parameter is deprecated. The result will not be filtered by account type. To filter received ProjectInfos, you could use the 'team_id' from the ProjectInfo object to get TeamInfo and check the account type."
|
|
2103
2117
|
)
|
|
2118
|
+
if extra_fields is not None:
|
|
2119
|
+
request_body[ApiField.EXTRA_FIELDS] = extra_fields
|
|
2104
2120
|
|
|
2105
2121
|
first_response = self._api.post(method, request_body).json()
|
|
2106
2122
|
|
|
@@ -2141,3 +2157,206 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2141
2157
|
)
|
|
2142
2158
|
_convert_entities(first_response)
|
|
2143
2159
|
return first_response
|
|
2160
|
+
|
|
2161
|
+
def enable_embeddings(self, id: int, silent: bool = True) -> None:
|
|
2162
|
+
"""
|
|
2163
|
+
Enable embeddings for the project.
|
|
2164
|
+
|
|
2165
|
+
:param id: Project ID
|
|
2166
|
+
:type id: int
|
|
2167
|
+
:param silent: Determines whether the `updatedAt` timestamp of the Project should be updated or not, if False - update `updatedAt`
|
|
2168
|
+
:type silent: bool
|
|
2169
|
+
:return: None
|
|
2170
|
+
:rtype: :class:`NoneType`
|
|
2171
|
+
"""
|
|
2172
|
+
self._api.post(
|
|
2173
|
+
"projects.editInfo",
|
|
2174
|
+
{ApiField.ID: id, ApiField.EMBEDDINGS_ENABLED: True, ApiField.SILENT: silent},
|
|
2175
|
+
)
|
|
2176
|
+
|
|
2177
|
+
def disable_embeddings(self, id: int, silent: bool = True) -> None:
|
|
2178
|
+
"""
|
|
2179
|
+
Disable embeddings for the project.
|
|
2180
|
+
|
|
2181
|
+
:param id: Project ID
|
|
2182
|
+
:type id: int
|
|
2183
|
+
:param silent: Determines whether the `updatedAt` timestamp of the Poject should be updated or not, if False - update `updatedAt`
|
|
2184
|
+
:type silent: bool
|
|
2185
|
+
:return: None
|
|
2186
|
+
:rtype: :class:`NoneType`
|
|
2187
|
+
"""
|
|
2188
|
+
self._api.post(
|
|
2189
|
+
"projects.editInfo",
|
|
2190
|
+
{ApiField.ID: id, ApiField.EMBEDDINGS_ENABLED: False, ApiField.SILENT: silent},
|
|
2191
|
+
)
|
|
2192
|
+
|
|
2193
|
+
def set_embeddings_in_progress(self, id: int, in_progress: bool) -> None:
|
|
2194
|
+
"""
|
|
2195
|
+
Set embeddings in progress status for the project.
|
|
2196
|
+
This method is used to indicate whether embeddings are currently being created for the project.
|
|
2197
|
+
|
|
2198
|
+
:param id: Project ID
|
|
2199
|
+
:type id: int
|
|
2200
|
+
:param in_progress: Status to set. If True, embeddings are in progress right now.
|
|
2201
|
+
:type in_progress: bool
|
|
2202
|
+
:return: None
|
|
2203
|
+
:rtype: :class:`NoneType`
|
|
2204
|
+
"""
|
|
2205
|
+
self._api.post(
|
|
2206
|
+
"projects.embeddings-in-progress.update",
|
|
2207
|
+
{ApiField.ID: id, ApiField.EMBEDDINGS_IN_PROGRESS: in_progress},
|
|
2208
|
+
)
|
|
2209
|
+
|
|
2210
|
+
def set_embeddings_updated_at(
|
|
2211
|
+
self, id: int, timestamp: Optional[str] = None, silent: bool = True
|
|
2212
|
+
) -> None:
|
|
2213
|
+
"""
|
|
2214
|
+
Set the timestamp when embeddings were last updated for the project.
|
|
2215
|
+
If no timestamp is provided, uses the current UTC time.
|
|
2216
|
+
|
|
2217
|
+
:param id: Project ID
|
|
2218
|
+
:type id: int
|
|
2219
|
+
:param timestamp: ISO format timestamp (YYYY-MM-DDTHH:MM:SS.fffffZ). If None, current UTC time is used.
|
|
2220
|
+
:type timestamp: Optional[str]
|
|
2221
|
+
:param silent: Determines whether the `updatedAt` timestamp of the Project should be updated or not, if False - update `updatedAt`
|
|
2222
|
+
:type silent: bool
|
|
2223
|
+
:return: None
|
|
2224
|
+
:rtype: :class:`NoneType`
|
|
2225
|
+
:Usage example:
|
|
2226
|
+
|
|
2227
|
+
.. code-block:: python
|
|
2228
|
+
|
|
2229
|
+
|
|
2230
|
+
api = sly.Api.from_env()
|
|
2231
|
+
project_id = 123
|
|
2232
|
+
|
|
2233
|
+
# Set current time as embeddings update timestamp
|
|
2234
|
+
api.project.set_embeddings_updated_at(project_id)
|
|
2235
|
+
|
|
2236
|
+
# Set specific timestamp
|
|
2237
|
+
api.project.set_embeddings_updated_at(project_id, "2025-06-01T10:30:45.123456Z")
|
|
2238
|
+
"""
|
|
2239
|
+
if timestamp is None:
|
|
2240
|
+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
2241
|
+
|
|
2242
|
+
self._api.post(
|
|
2243
|
+
"projects.editInfo",
|
|
2244
|
+
{ApiField.ID: id, ApiField.EMBEDDINGS_UPDATED_AT: timestamp, ApiField.SILENT: silent},
|
|
2245
|
+
)
|
|
2246
|
+
|
|
2247
|
+
def perform_ai_search(
|
|
2248
|
+
self,
|
|
2249
|
+
project_id: int,
|
|
2250
|
+
dataset_id: Optional[int] = None,
|
|
2251
|
+
image_id: Optional[int] = None,
|
|
2252
|
+
prompt: Optional[str] = None,
|
|
2253
|
+
method: Optional[Literal["centroids", "random"]] = None,
|
|
2254
|
+
limit: int = 100,
|
|
2255
|
+
) -> Optional[int]:
|
|
2256
|
+
"""
|
|
2257
|
+
Send AI search request to initiate search process.
|
|
2258
|
+
This method allows you to search for similar images in a project using either a text prompt, an image ID, or a method type.
|
|
2259
|
+
It is mutually exclusive, meaning you can only provide one of the parameters: `prompt`, `image_id`, or `method`.
|
|
2260
|
+
|
|
2261
|
+
:param project_id: ID of the Project
|
|
2262
|
+
:type project_id: int
|
|
2263
|
+
:param dataset_id: ID of the Dataset. If not None - search will be limited to this dataset.
|
|
2264
|
+
:type dataset_id: Optional[int]
|
|
2265
|
+
:param image_id: ID of the Image. Searches for images similar to the specified image.
|
|
2266
|
+
:type image_id: Optional[int]
|
|
2267
|
+
:param prompt: Text prompt for search request. Searches for similar images based on a text description.
|
|
2268
|
+
:type prompt: Optional[str]
|
|
2269
|
+
:param method: Activates diverse search using one of the following methods: "centroids", "random".
|
|
2270
|
+
:type method: Optional[Literal["centroids", "random"]]
|
|
2271
|
+
:param limit: Limit for search request
|
|
2272
|
+
:type limit: int
|
|
2273
|
+
:return: Entitites Collection ID of the search results, or None if no collection was created.
|
|
2274
|
+
:rtype: Optional[int]
|
|
2275
|
+
:raises ValueError: only one of `prompt`, `image_id` or `method`must be provided, and `method` must be one of the allowed values.
|
|
2276
|
+
:Usage example:
|
|
2277
|
+
|
|
2278
|
+
.. code-block:: python
|
|
2279
|
+
|
|
2280
|
+
import supervisely as sly
|
|
2281
|
+
|
|
2282
|
+
api = sly.Api.from_env()
|
|
2283
|
+
|
|
2284
|
+
project_id = 123
|
|
2285
|
+
image_id = 789
|
|
2286
|
+
prompt = "person with a dog"
|
|
2287
|
+
|
|
2288
|
+
# Search with text prompt
|
|
2289
|
+
collection_id = api.project.perform_ai_search(
|
|
2290
|
+
project_id=project_id,
|
|
2291
|
+
prompt=prompt,
|
|
2292
|
+
)
|
|
2293
|
+
|
|
2294
|
+
# Search with method
|
|
2295
|
+
collection_id = api.project.perform_ai_search(
|
|
2296
|
+
project_id=project_id,
|
|
2297
|
+
method="centroids",
|
|
2298
|
+
)
|
|
2299
|
+
|
|
2300
|
+
# Search with image ID
|
|
2301
|
+
collection_id = api.project.perform_ai_search(
|
|
2302
|
+
project_id=project_id,
|
|
2303
|
+
image_id=image_id,
|
|
2304
|
+
)
|
|
2305
|
+
"""
|
|
2306
|
+
|
|
2307
|
+
# Check that only one of prompt, method, or image_id is provided
|
|
2308
|
+
provided_params = sum([prompt is not None, method is not None, image_id is not None])
|
|
2309
|
+
if provided_params != 1:
|
|
2310
|
+
raise ValueError(
|
|
2311
|
+
"Must provide exactly one of 'prompt', 'method', or 'image_id' parameters. They are mutually exclusive."
|
|
2312
|
+
)
|
|
2313
|
+
|
|
2314
|
+
if prompt is None and method is None and image_id is None:
|
|
2315
|
+
raise ValueError("Must provide either 'prompt', 'method', or 'image_id' parameter.")
|
|
2316
|
+
|
|
2317
|
+
# Validate method values
|
|
2318
|
+
if method is not None and method not in ["centroids", "random"]:
|
|
2319
|
+
raise ValueError("Method must be either 'centroids' or 'random'.")
|
|
2320
|
+
|
|
2321
|
+
request_body = {
|
|
2322
|
+
ApiField.PROJECT_ID: project_id,
|
|
2323
|
+
ApiField.LIMIT: limit,
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
if dataset_id is not None:
|
|
2327
|
+
request_body[ApiField.DATASET_ID] = dataset_id
|
|
2328
|
+
|
|
2329
|
+
if image_id is not None:
|
|
2330
|
+
request_body[ApiField.IMAGE_ID] = image_id
|
|
2331
|
+
|
|
2332
|
+
if prompt is not None:
|
|
2333
|
+
request_body[ApiField.PROMPT] = prompt
|
|
2334
|
+
|
|
2335
|
+
if method is not None:
|
|
2336
|
+
request_body[ApiField.METHOD] = method
|
|
2337
|
+
|
|
2338
|
+
response = self._api.post("projects.send-ai-search", request_body)
|
|
2339
|
+
return response.json().get(ApiField.COLLECTION_ID, None)
|
|
2340
|
+
|
|
2341
|
+
def calculate_embeddings(self, id: int) -> None:
|
|
2342
|
+
"""
|
|
2343
|
+
Calculate embeddings for the project.
|
|
2344
|
+
This method is used to calculate embeddings for all images in the project.
|
|
2345
|
+
|
|
2346
|
+
:param id: Project ID
|
|
2347
|
+
:type id: int
|
|
2348
|
+
:return: None
|
|
2349
|
+
:rtype: :class:`NoneType`
|
|
2350
|
+
:Usage example:
|
|
2351
|
+
|
|
2352
|
+
.. code-block:: python
|
|
2353
|
+
|
|
2354
|
+
import supervisely as sly
|
|
2355
|
+
|
|
2356
|
+
api = sly.Api.from_env()
|
|
2357
|
+
project_id = 123
|
|
2358
|
+
|
|
2359
|
+
# Calculate embeddings for the project
|
|
2360
|
+
api.project.calculate_embeddings(project_id)
|
|
2361
|
+
"""
|
|
2362
|
+
self._api.post("projects.calculate-project-embeddings", {ApiField.PROJECT_ID: id})
|
|
@@ -2355,11 +2355,14 @@ class VideoApi(RemoveableBulkModuleApi):
|
|
|
2355
2355
|
|
|
2356
2356
|
def set_remote(self, videos: List[int], links: List[str]):
|
|
2357
2357
|
"""
|
|
2358
|
-
|
|
2358
|
+
Updates the source of existing videos by setting new remote links.
|
|
2359
|
+
This method is used when a video was initially uploaded as a file or added via a link,
|
|
2360
|
+
but later it was decided to change its location (e.g., moved to another storage or re-uploaded elsewhere).
|
|
2361
|
+
By updating the link, the video source can be redirected to the new location.
|
|
2359
2362
|
|
|
2360
2363
|
:param videos: List of video ids.
|
|
2361
2364
|
:type videos: List[int]
|
|
2362
|
-
:param links: List of remote links.
|
|
2365
|
+
:param links: List of new remote links.
|
|
2363
2366
|
:type links: List[str]
|
|
2364
2367
|
:return: json-encoded content of a response.
|
|
2365
2368
|
|
|
@@ -2367,17 +2370,17 @@ class VideoApi(RemoveableBulkModuleApi):
|
|
|
2367
2370
|
|
|
2368
2371
|
.. code-block:: python
|
|
2369
2372
|
|
|
2370
|
-
|
|
2373
|
+
import supervisely as sly
|
|
2371
2374
|
|
|
2372
|
-
|
|
2375
|
+
api = sly.Api.from_env()
|
|
2373
2376
|
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2377
|
+
videos = [123, 124, 125]
|
|
2378
|
+
links = [
|
|
2379
|
+
"s3://bucket/f1champ/ds1/lap_1.mp4",
|
|
2380
|
+
"s3://bucket/f1champ/ds1/lap_2.mp4",
|
|
2381
|
+
"s3://bucket/f1champ/ds1/lap_3.mp4",
|
|
2382
|
+
]
|
|
2383
|
+
result = api.video.set_remote(videos, links)
|
|
2381
2384
|
"""
|
|
2382
2385
|
|
|
2383
2386
|
if len(videos) == 0:
|