supervisely 6.73.438__py3-none-any.whl → 6.73.513__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.
Files changed (203) hide show
  1. supervisely/__init__.py +137 -1
  2. supervisely/_utils.py +81 -0
  3. supervisely/annotation/annotation.py +8 -2
  4. supervisely/annotation/json_geometries_map.py +14 -11
  5. supervisely/annotation/label.py +80 -3
  6. supervisely/api/annotation_api.py +14 -11
  7. supervisely/api/api.py +59 -38
  8. supervisely/api/app_api.py +11 -2
  9. supervisely/api/dataset_api.py +74 -12
  10. supervisely/api/entities_collection_api.py +10 -0
  11. supervisely/api/entity_annotation/figure_api.py +52 -4
  12. supervisely/api/entity_annotation/object_api.py +3 -3
  13. supervisely/api/entity_annotation/tag_api.py +63 -12
  14. supervisely/api/guides_api.py +210 -0
  15. supervisely/api/image_api.py +72 -1
  16. supervisely/api/labeling_job_api.py +83 -1
  17. supervisely/api/labeling_queue_api.py +33 -7
  18. supervisely/api/module_api.py +9 -0
  19. supervisely/api/project_api.py +71 -26
  20. supervisely/api/storage_api.py +3 -1
  21. supervisely/api/task_api.py +13 -2
  22. supervisely/api/team_api.py +4 -3
  23. supervisely/api/video/video_annotation_api.py +119 -3
  24. supervisely/api/video/video_api.py +65 -14
  25. supervisely/api/video/video_figure_api.py +24 -11
  26. supervisely/app/__init__.py +1 -1
  27. supervisely/app/content.py +23 -7
  28. supervisely/app/development/development.py +18 -2
  29. supervisely/app/fastapi/__init__.py +1 -0
  30. supervisely/app/fastapi/custom_static_files.py +1 -1
  31. supervisely/app/fastapi/multi_user.py +105 -0
  32. supervisely/app/fastapi/subapp.py +88 -42
  33. supervisely/app/fastapi/websocket.py +77 -9
  34. supervisely/app/singleton.py +21 -0
  35. supervisely/app/v1/app_service.py +18 -2
  36. supervisely/app/v1/constants.py +7 -1
  37. supervisely/app/widgets/__init__.py +6 -0
  38. supervisely/app/widgets/activity_feed/__init__.py +0 -0
  39. supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
  40. supervisely/app/widgets/activity_feed/style.css +78 -0
  41. supervisely/app/widgets/activity_feed/template.html +22 -0
  42. supervisely/app/widgets/card/card.py +20 -0
  43. supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
  44. supervisely/app/widgets/classes_list_selector/template.html +60 -93
  45. supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
  46. supervisely/app/widgets/classes_table/classes_table.py +1 -0
  47. supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
  48. supervisely/app/widgets/dialog/dialog.py +12 -0
  49. supervisely/app/widgets/dialog/template.html +2 -1
  50. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
  51. supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
  52. supervisely/app/widgets/fast_table/fast_table.py +184 -60
  53. supervisely/app/widgets/fast_table/template.html +1 -1
  54. supervisely/app/widgets/heatmap/__init__.py +0 -0
  55. supervisely/app/widgets/heatmap/heatmap.py +564 -0
  56. supervisely/app/widgets/heatmap/script.js +533 -0
  57. supervisely/app/widgets/heatmap/style.css +233 -0
  58. supervisely/app/widgets/heatmap/template.html +21 -0
  59. supervisely/app/widgets/modal/__init__.py +0 -0
  60. supervisely/app/widgets/modal/modal.py +198 -0
  61. supervisely/app/widgets/modal/template.html +10 -0
  62. supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
  63. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  64. supervisely/app/widgets/radio_tabs/template.html +1 -0
  65. supervisely/app/widgets/select/select.py +6 -3
  66. supervisely/app/widgets/select_class/__init__.py +0 -0
  67. supervisely/app/widgets/select_class/select_class.py +363 -0
  68. supervisely/app/widgets/select_class/template.html +50 -0
  69. supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
  70. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
  71. supervisely/app/widgets/select_tag/__init__.py +0 -0
  72. supervisely/app/widgets/select_tag/select_tag.py +352 -0
  73. supervisely/app/widgets/select_tag/template.html +64 -0
  74. supervisely/app/widgets/select_team/select_team.py +37 -4
  75. supervisely/app/widgets/select_team/template.html +4 -5
  76. supervisely/app/widgets/select_user/__init__.py +0 -0
  77. supervisely/app/widgets/select_user/select_user.py +270 -0
  78. supervisely/app/widgets/select_user/template.html +13 -0
  79. supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
  80. supervisely/app/widgets/select_workspace/template.html +9 -12
  81. supervisely/app/widgets/table/table.py +68 -13
  82. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  83. supervisely/aug/aug.py +6 -2
  84. supervisely/convert/base_converter.py +1 -0
  85. supervisely/convert/converter.py +2 -2
  86. supervisely/convert/image/csv/csv_converter.py +24 -15
  87. supervisely/convert/image/image_converter.py +3 -1
  88. supervisely/convert/image/image_helper.py +48 -4
  89. supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
  90. supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
  91. supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
  92. supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
  93. supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
  94. supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
  95. supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
  96. supervisely/convert/pointcloud/las/las_converter.py +13 -1
  97. supervisely/convert/pointcloud/las/las_helper.py +110 -11
  98. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
  99. supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
  100. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
  101. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
  102. supervisely/convert/video/__init__.py +1 -0
  103. supervisely/convert/video/multi_view/__init__.py +0 -0
  104. supervisely/convert/video/multi_view/multi_view.py +543 -0
  105. supervisely/convert/video/sly/sly_video_converter.py +359 -3
  106. supervisely/convert/video/video_converter.py +24 -4
  107. supervisely/convert/volume/dicom/dicom_converter.py +13 -5
  108. supervisely/convert/volume/dicom/dicom_helper.py +30 -18
  109. supervisely/geometry/constants.py +1 -0
  110. supervisely/geometry/geometry.py +4 -0
  111. supervisely/geometry/helpers.py +5 -1
  112. supervisely/geometry/oriented_bbox.py +676 -0
  113. supervisely/geometry/polyline_3d.py +110 -0
  114. supervisely/geometry/rectangle.py +2 -1
  115. supervisely/io/env.py +76 -1
  116. supervisely/io/fs.py +21 -0
  117. supervisely/nn/benchmark/base_evaluator.py +104 -11
  118. supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
  119. supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
  120. supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
  121. supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
  122. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
  123. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
  124. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
  125. supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
  126. supervisely/nn/inference/cache.py +43 -18
  127. supervisely/nn/inference/gui/serving_gui_template.py +5 -2
  128. supervisely/nn/inference/inference.py +916 -222
  129. supervisely/nn/inference/inference_request.py +55 -10
  130. supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
  131. supervisely/nn/inference/predict_app/gui/gui.py +676 -488
  132. supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
  133. supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
  134. supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
  135. supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
  136. supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
  137. supervisely/nn/inference/predict_app/gui/utils.py +236 -119
  138. supervisely/nn/inference/predict_app/predict_app.py +2 -2
  139. supervisely/nn/inference/session.py +43 -35
  140. supervisely/nn/inference/tracking/bbox_tracking.py +118 -35
  141. supervisely/nn/inference/tracking/point_tracking.py +5 -1
  142. supervisely/nn/inference/tracking/tracker_interface.py +10 -1
  143. supervisely/nn/inference/uploader.py +139 -12
  144. supervisely/nn/live_training/__init__.py +7 -0
  145. supervisely/nn/live_training/api_server.py +111 -0
  146. supervisely/nn/live_training/artifacts_utils.py +243 -0
  147. supervisely/nn/live_training/checkpoint_utils.py +229 -0
  148. supervisely/nn/live_training/dynamic_sampler.py +44 -0
  149. supervisely/nn/live_training/helpers.py +14 -0
  150. supervisely/nn/live_training/incremental_dataset.py +146 -0
  151. supervisely/nn/live_training/live_training.py +497 -0
  152. supervisely/nn/live_training/loss_plateau_detector.py +111 -0
  153. supervisely/nn/live_training/request_queue.py +52 -0
  154. supervisely/nn/model/model_api.py +9 -0
  155. supervisely/nn/model/prediction.py +2 -1
  156. supervisely/nn/model/prediction_session.py +26 -14
  157. supervisely/nn/prediction_dto.py +19 -1
  158. supervisely/nn/tracker/base_tracker.py +11 -1
  159. supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
  160. supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
  161. supervisely/nn/tracker/botsort_tracker.py +94 -65
  162. supervisely/nn/tracker/utils.py +4 -5
  163. supervisely/nn/tracker/visualize.py +93 -93
  164. supervisely/nn/training/gui/classes_selector.py +16 -1
  165. supervisely/nn/training/gui/train_val_splits_selector.py +52 -31
  166. supervisely/nn/training/train_app.py +46 -31
  167. supervisely/project/data_version.py +115 -51
  168. supervisely/project/download.py +1 -1
  169. supervisely/project/pointcloud_episode_project.py +37 -8
  170. supervisely/project/pointcloud_project.py +30 -2
  171. supervisely/project/project.py +14 -2
  172. supervisely/project/project_meta.py +27 -1
  173. supervisely/project/project_settings.py +32 -18
  174. supervisely/project/versioning/__init__.py +1 -0
  175. supervisely/project/versioning/common.py +20 -0
  176. supervisely/project/versioning/schema_fields.py +35 -0
  177. supervisely/project/versioning/video_schema.py +221 -0
  178. supervisely/project/versioning/volume_schema.py +87 -0
  179. supervisely/project/video_project.py +717 -15
  180. supervisely/project/volume_project.py +623 -5
  181. supervisely/template/experiment/experiment.html.jinja +4 -4
  182. supervisely/template/experiment/experiment_generator.py +14 -21
  183. supervisely/template/live_training/__init__.py +0 -0
  184. supervisely/template/live_training/header.html.jinja +96 -0
  185. supervisely/template/live_training/live_training.html.jinja +51 -0
  186. supervisely/template/live_training/live_training_generator.py +464 -0
  187. supervisely/template/live_training/sly-style.css +402 -0
  188. supervisely/template/live_training/template.html.jinja +18 -0
  189. supervisely/versions.json +28 -26
  190. supervisely/video/sampling.py +39 -20
  191. supervisely/video/video.py +41 -12
  192. supervisely/video_annotation/video_figure.py +38 -4
  193. supervisely/video_annotation/video_object.py +29 -4
  194. supervisely/volume/stl_converter.py +2 -0
  195. supervisely/worker_api/agent_rpc.py +24 -1
  196. supervisely/worker_api/rpc_servicer.py +31 -7
  197. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/METADATA +58 -40
  198. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/RECORD +203 -155
  199. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
  200. supervisely_lib/__init__.py +6 -1
  201. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
  202. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
  203. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,210 @@
1
+ # coding: utf-8
2
+ """create or manipulate guides that can be assigned to labeling jobs and labeling queues"""
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 ApiField, ModuleApiBase
10
+
11
+
12
+ class GuideInfo(NamedTuple):
13
+ """
14
+ Information about a Guide.
15
+
16
+ :param id: Guide ID in Supervisely.
17
+ :type id: int
18
+ :param name: Guide name.
19
+ :type name: str
20
+ :param description: Guide description.
21
+ :type description: str
22
+ :param file_path: Path to the guide file (PDF or other).
23
+ :type file_path: str
24
+ :param created_at: Guide creation date.
25
+ :type created_at: str
26
+ :param updated_at: Guide last update date.
27
+ :type updated_at: str
28
+ :param created_by_id: ID of the User who created the Guide.
29
+ :type created_by_id: int
30
+ :param team_id: Team ID where the Guide is located.
31
+ :type team_id: int
32
+ :param video_id: ID of the video associated with the guide (if any).
33
+ :type video_id: Optional[int]
34
+ :param disabled_by: ID of the User who disabled the Guide (if disabled).
35
+ :type disabled_by: Optional[int]
36
+ :param disabled_at: Date when the Guide was disabled (if disabled).
37
+ :type disabled_at: Optional[str]
38
+ """
39
+
40
+ id: int
41
+ name: str
42
+ description: str
43
+ file_path: str
44
+ created_at: str
45
+ updated_at: str
46
+ created_by_id: int
47
+ team_id: int
48
+ video_id: Optional[int] = None
49
+ disabled_by: Optional[int] = None
50
+ disabled_at: Optional[str] = None
51
+
52
+
53
+ class GuidesApi(ModuleApiBase):
54
+ """
55
+ API for working with Guides. :class:`GuidesApi<GuidesApi>` object is immutable.
56
+
57
+ :param api: API connection to the server.
58
+ :type api: Api
59
+ :Usage example:
60
+
61
+ .. code-block:: python
62
+
63
+ import os
64
+ from dotenv import load_dotenv
65
+
66
+ import supervisely as sly
67
+
68
+ # Load secrets and create API object from .env file (recommended)
69
+ # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
70
+
71
+ api = sly.Api.from_env()
72
+
73
+ # Get list of guides in team
74
+ guides = api.guides.get_list(team_id=123)
75
+ """
76
+
77
+ @staticmethod
78
+ def info_sequence():
79
+ """
80
+ NamedTuple GuideInfo information about Guide.
81
+
82
+ :Example:
83
+
84
+ .. code-block:: python
85
+
86
+ GuideInfo(
87
+ id=1,
88
+ name='How to label objects',
89
+ description='Comprehensive guide on object labeling',
90
+ file_path='/path/to/guide.pdf',
91
+ created_at='2023-01-01T00:00:00.000Z',
92
+ updated_at='2025-11-17T18:21:10.217Z',
93
+ created_by_id=1,
94
+ team_id=1,
95
+ video_id=None,
96
+ disabled_by=None,
97
+ disabled_at=None
98
+ )
99
+ """
100
+ return [
101
+ ApiField.ID,
102
+ ApiField.NAME,
103
+ ApiField.DESCRIPTION,
104
+ ApiField.FILE_PATH,
105
+ ApiField.CREATED_AT,
106
+ ApiField.UPDATED_AT,
107
+ ApiField.CREATED_BY_ID,
108
+ ApiField.TEAM_ID,
109
+ ApiField.VIDEO_ID,
110
+ ApiField.DISABLED_BY,
111
+ ApiField.DISABLED_AT,
112
+ ]
113
+
114
+ @staticmethod
115
+ def info_tuple_name():
116
+ """
117
+ NamedTuple name - **GuideInfo**.
118
+ """
119
+ return "GuideInfo"
120
+
121
+ def get_list(
122
+ self, team_id: int, filters: Optional[List[Dict[str, str]]] = None
123
+ ) -> List[GuideInfo]:
124
+ """
125
+ Get list of Guides in the given Team.
126
+
127
+ :param team_id: Team ID in Supervisely.
128
+ :type team_id: int
129
+ :param filters: List of parameters to filter Guides.
130
+ :type filters: List[Dict[str, str]], optional
131
+ :return: List of information about Guides.
132
+ :rtype: :class:`List[GuideInfo]`
133
+ :Usage example:
134
+
135
+ .. code-block:: python
136
+
137
+ import os
138
+ from dotenv import load_dotenv
139
+
140
+ import supervisely as sly
141
+
142
+ # Load secrets and create API object from .env file (recommended)
143
+ # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
144
+
145
+ api = sly.Api.from_env()
146
+
147
+ team_id = 123
148
+ guides = api.guides.get_list(team_id)
149
+ print(guides)
150
+ # Output: [
151
+ # GuideInfo(
152
+ # id=1,
153
+ # name='How to label objects',
154
+ # description='Comprehensive guide on object labeling',
155
+ # file_path='/path/to/guide.pdf',
156
+ # created_at='2023-01-01T00:00:00.000Z',
157
+ # updated_at='2025-11-17T18:21:10.217Z',
158
+ # created_by_id=1,
159
+ # team_id=1,
160
+ # video_id=None,
161
+ # disabled_by=None,
162
+ # disabled_at=None
163
+ # )
164
+ # ]
165
+ """
166
+ return self.get_list_all_pages(
167
+ "guides.list",
168
+ {ApiField.TEAM_ID: team_id, ApiField.FILTER: filters or []},
169
+ )
170
+
171
+ def get_info_by_id(self, id: int) -> GuideInfo:
172
+ """
173
+ Get Guide information by ID.
174
+
175
+ :param id: Guide ID in Supervisely.
176
+ :type id: int
177
+ :return: Information about Guide.
178
+ :rtype: :class:`GuideInfo`
179
+ :Usage example:
180
+
181
+ .. code-block:: python
182
+
183
+ import os
184
+ from dotenv import load_dotenv
185
+
186
+ import supervisely as sly
187
+
188
+ # Load secrets and create API object from .env file (recommended)
189
+ # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
190
+
191
+ api = sly.Api.from_env()
192
+
193
+ guide_id = 1
194
+ guide_info = api.guides.get_info_by_id(guide_id)
195
+ print(guide_info)
196
+ # Output: GuideInfo(
197
+ # id=1,
198
+ # name='How to label objects',
199
+ # description='Comprehensive guide on object labeling',
200
+ # file_path='/path/to/guide.pdf',
201
+ # created_at='2023-01-01T00:00:00.000Z',
202
+ # updated_at='2025-11-17T18:21:10.217Z',
203
+ # created_by_id=1,
204
+ # team_id=1,
205
+ # video_id=None,
206
+ # disabled_by=None,
207
+ # disabled_at=None
208
+ # )
209
+ """
210
+ return self._get_info_by_id(id, "guides.info")
@@ -70,7 +70,11 @@ from supervisely.api.module_api import (
70
70
  _get_single_item,
71
71
  )
72
72
  from supervisely.imaging import image as sly_image
73
- from supervisely.io.env import app_categories, increment_upload_count, add_uploaded_ids_to_env
73
+ from supervisely.io.env import (
74
+ add_uploaded_ids_to_env,
75
+ app_categories,
76
+ increment_upload_count,
77
+ )
74
78
  from supervisely.io.fs import (
75
79
  OFFSETS_PKL_BATCH_SIZE,
76
80
  OFFSETS_PKL_SUFFIX,
@@ -393,6 +397,9 @@ class ImageInfo(NamedTuple):
393
397
  #: Format: "YYYY-MM-DDTHH:MM:SS.sssZ"
394
398
  embeddings_updated_at: Optional[str] = None
395
399
 
400
+ #: :class:`int`: :class:`Dataset<supervisely.project.project.Project>` ID in Supervisely.
401
+ project_id: int = None
402
+
396
403
  # DO NOT DELETE THIS COMMENT
397
404
  #! New fields must be added with default values to keep backward compatibility.
398
405
 
@@ -472,6 +479,7 @@ class ImageApi(RemoveableBulkModuleApi):
472
479
  ApiField.OFFSET_END,
473
480
  ApiField.AI_SEARCH_META,
474
481
  ApiField.EMBEDDINGS_UPDATED_AT,
482
+ ApiField.PROJECT_ID,
475
483
  ]
476
484
 
477
485
  @staticmethod
@@ -5519,3 +5527,66 @@ class ImageApi(RemoveableBulkModuleApi):
5519
5527
  method,
5520
5528
  {ApiField.IMAGES: images},
5521
5529
  )
5530
+
5531
+ def get_subsequent_image_ids(
5532
+ self,
5533
+ image_id: int,
5534
+ images_count: Optional[int] = None,
5535
+ job_id: Optional[int] = None,
5536
+ params: Optional[dict] = None,
5537
+ dataset_id: Optional[int] = None,
5538
+ project_id: Optional[int] = None,
5539
+ ) -> List[int]:
5540
+ """
5541
+ Get list of subsequent image IDs after the specified image ID.
5542
+
5543
+ :param image_id: Image ID in Supervisely.
5544
+ :type image_id: int
5545
+ :param images_count: Number of subsequent images to retrieve. If None, retrieves all subsequent images.
5546
+ :type images_count: int, optional
5547
+ :param job_id: Job ID to filter images. If None, does not filter by job ID.
5548
+ :type job_id: int, optional
5549
+ :param params: Additional parameters for filtering and sorting images.
5550
+ :type params: dict, optional
5551
+ :param dataset_id: Dataset ID to filter images.
5552
+ :type dataset_id: int, optional
5553
+ :param project_id: Project ID to filter images. If None, makes a request to retrieve it from the specified image.
5554
+ :type project_id: int, optional
5555
+ """
5556
+ data = {
5557
+ "recursive": True,
5558
+ "projectId": project_id,
5559
+ "filters": [],
5560
+ "sort": "name",
5561
+ "sort_order": "asc",
5562
+ }
5563
+
5564
+ if params is not None:
5565
+ data.update(params)
5566
+
5567
+ if data["projectId"] is None:
5568
+ image_info = self.get_info_by_id(image_id)
5569
+ if image_info is None:
5570
+ raise ValueError(f"Image with ID {image_id} not found.")
5571
+ project_id = self._api.dataset.get_info_by_id(image_info.dataset_id).project_id
5572
+ if job_id is not None:
5573
+ self._api.add_header("x-job-id", str(job_id))
5574
+ if dataset_id is not None:
5575
+ data["datasetId"] = dataset_id
5576
+
5577
+ image_infos = self.get_list_all_pages(
5578
+ "images.list",
5579
+ data,
5580
+ limit=None,
5581
+ return_first_response=False,
5582
+ )
5583
+ self._api.headers.pop("x-job-id", None)
5584
+ image_ids = [img_info.id for img_info in image_infos]
5585
+ if len(image_ids) == 0:
5586
+ raise ValueError("No images found with the specified criteria.")
5587
+ elif image_id not in image_ids:
5588
+ raise ValueError(f"Image with ID {image_id} not found in the specified entity.")
5589
+
5590
+ target_idx = image_ids.index(image_id) + 1
5591
+ to_idx = target_idx + images_count if images_count is not None else len(image_ids)
5592
+ return image_ids[target_idx:to_idx]
@@ -89,6 +89,7 @@ class LabelingJobInfo(NamedTuple):
89
89
  exclude_images_with_tags: list
90
90
  entities: list
91
91
  priority: int
92
+ guide_id: Optional[int] = None
92
93
 
93
94
 
94
95
  class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
@@ -223,6 +224,7 @@ class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
223
224
  ApiField.EXCLUDE_IMAGES_WITH_TAGS,
224
225
  ApiField.ENTITIES,
225
226
  ApiField.PRIORITY,
227
+ ApiField.M_GUIDE_ID,
226
228
  ]
227
229
 
228
230
  @staticmethod
@@ -261,7 +263,10 @@ class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
261
263
  else:
262
264
  value = info[sub_name]
263
265
  else:
264
- value = value[sub_name]
266
+ if skip_missing is True:
267
+ value = value.get(sub_name, None)
268
+ else:
269
+ value = value[sub_name]
265
270
  else:
266
271
  raise RuntimeError("Can not parse field {!r}".format(field_name))
267
272
 
@@ -341,6 +346,8 @@ class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
341
346
  disable_submit: Optional[bool] = None,
342
347
  toolbox_settings: Optional[Dict] = None,
343
348
  enable_quality_check: Optional[bool] = None,
349
+ guide_id: Optional[int] = None,
350
+ allow_restore: bool = False,
344
351
  ) -> List[LabelingJobInfo]:
345
352
  """
346
353
  Creates Labeling Job and assigns given Users to it.
@@ -385,6 +392,10 @@ class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
385
392
  :type toolbox_settings: Dict, optional
386
393
  :param enable_quality_check: If True, adds an intermediate step between "review" and completing the Labeling Job.
387
394
  :type enable_quality_check: bool, optional
395
+ :param guide_id: Guide ID in Supervisely to assign a guide to the Labeling Job.
396
+ :type guide_id: int, optional
397
+ :param allow_restore: If True, allows restoring a previously deleted labeling job with the same name in the same dataset.
398
+ :type allow_restore: bool
388
399
  :return: List of information about new Labeling Job. See :class:`info_sequence<info_sequence>`
389
400
  :rtype: :class:`List[LabelingJobInfo]`
390
401
  :Usage example:
@@ -463,8 +474,18 @@ class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
463
474
  "entityIds": images_ids,
464
475
  "dynamicClasses": dynamic_classes,
465
476
  "dynamicTags": dynamic_tags,
477
+ "allowRestore": allow_restore,
466
478
  }
467
479
 
480
+ if guide_id is not None:
481
+ try:
482
+ guide_id = int(guide_id)
483
+ except Exception as e:
484
+ raise ValueError(
485
+ f"guide_id must be an integer, got {type(guide_id)} with value '{guide_id}'"
486
+ ) from None
487
+ meta["guide"] = guide_id
488
+
468
489
  if toolbox_settings is not None:
469
490
  dataset_info = self._api.dataset.get_info_by_id(dataset_id)
470
491
  project_id = dataset_info.project_id
@@ -1460,3 +1481,64 @@ class LabelingJobApi(RemoveableBulkModuleApi, ModuleWithStatus):
1460
1481
 
1461
1482
  response = self._api.post("jobs.restart", data).json()
1462
1483
  return response
1484
+
1485
+ def get_custom_data(self, id: int) -> dict:
1486
+ """
1487
+ Get custom data of Labeling Job with given ID.
1488
+
1489
+ :param id: Labeling Job ID in Supervisely.
1490
+ :type id: int
1491
+ :return: Custom data of the job
1492
+ :rtype: :class:`dict`
1493
+ :Usage example:
1494
+
1495
+ .. code-block:: python
1496
+
1497
+ import supervisely as sly
1498
+
1499
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
1500
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
1501
+ api = sly.Api.from_env()
1502
+
1503
+ custom_data = api.labeling_job.get_custom_data(9)
1504
+ print(custom_data)
1505
+ """
1506
+ method = "jobs.info"
1507
+ response = self._get_response_by_id(id, method, id_field=ApiField.ID)
1508
+ json_response = response.json() if response is not None else None
1509
+ if json_response is not None:
1510
+ return json_response.get(ApiField.CUSTOM_DATA, {})
1511
+ return {}
1512
+
1513
+ def set_custom_data(self, id: int, custom_data: dict, update: bool = True) -> None:
1514
+ """
1515
+ Update or replace custom data of Labeling Job with given ID.
1516
+ By default, updates existing custom data. To replace it entirely, set `update` to False.
1517
+
1518
+ :param id: Labeling Job ID in Supervisely.
1519
+ :type id: int
1520
+ :param custom_data: Custom data to set
1521
+ :type custom_data: dict
1522
+ :param update: Whether to update existing custom data or replace it entirely.
1523
+ :type update: bool
1524
+ :return: None
1525
+ :rtype: :class:`NoneType`
1526
+ :Usage example:
1527
+
1528
+ .. code-block:: python
1529
+
1530
+ import supervisely as sly
1531
+
1532
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
1533
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
1534
+ api = sly.Api.from_env()
1535
+
1536
+ api.labeling_job.set_custom_data(9, {"key": "value"})
1537
+ """
1538
+ method = "jobs.editInfo"
1539
+
1540
+ if update is True:
1541
+ existing_custom_data = self.get_custom_data(id)
1542
+ existing_custom_data.update(custom_data)
1543
+ custom_data = existing_custom_data
1544
+ self._api.post(method, {ApiField.ID: id, ApiField.CUSTOM_DATA: custom_data})
@@ -37,6 +37,7 @@ class LabelingQueueInfo(NamedTuple):
37
37
  in_progress_count: int
38
38
  pending_count: int
39
39
  meta: dict
40
+ collection_id: Optional[int] = None
40
41
 
41
42
 
42
43
  class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
@@ -93,7 +94,8 @@ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
93
94
  annotated_count=3,
94
95
  in_progress_count=2,
95
96
  pending_count=1,
96
- meta={}
97
+ meta={},
98
+ collection_id=None,
97
99
  )
98
100
  """
99
101
  return [
@@ -115,6 +117,7 @@ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
115
117
  ApiField.IN_PROGRESS_COUNT,
116
118
  ApiField.PENDING_COUNT,
117
119
  ApiField.META,
120
+ ApiField.COLLECTION_ID,
118
121
  ]
119
122
 
120
123
  @staticmethod
@@ -200,24 +203,24 @@ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
200
203
  skip_complete_job_on_empty: Optional[bool] = False,
201
204
  enable_quality_check: Optional[bool] = None,
202
205
  quality_check_user_ids: Optional[List[int]] = None,
206
+ guide_id: Optional[int] = None,
207
+ description: Optional[str] = None,
203
208
  ) -> int:
204
209
  """
205
210
  Creates Labeling Queue and assigns given Users to it.
206
211
 
207
212
  :param name: Labeling Queue name in Supervisely.
208
213
  :type name: str
209
- :param dataset_id: Dataset ID in Supervisely.
210
- :type dataset_id: int
211
- :param collection_id: Entities Collection ID in Supervisely.
212
- :type collection_id: int, optional
213
214
  :param user_ids: User IDs in Supervisely to assign Users as labelers to Labeling Queue.
214
215
  :type user_ids: List[int]
215
216
  :param reviewer_ids: User IDs in Supervisely to assign Users as reviewers to Labeling Queue.
216
217
  :type reviewer_ids: List[int]
218
+ :param dataset_id: Dataset ID in Supervisely.
219
+ :type dataset_id: int
220
+ :param collection_id: Entities Collection ID in Supervisely.
221
+ :type collection_id: int, optional
217
222
  :param readme: Additional information about Labeling Queue.
218
223
  :type readme: str, optional
219
- :param description: Description of Labeling Queue.
220
- :type description: str, optional
221
224
  :param classes_to_label: List of classes to label in Dataset.
222
225
  :type classes_to_label: List[str], optional
223
226
  :param objects_limit_per_image: Limit the number of objects that the labeler can create on each image.
@@ -256,6 +259,10 @@ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
256
259
  :type enable_quality_check: bool, optional
257
260
  :param quality_check_user_ids: List of User IDs in Supervisely to assign Users as Quality Checkers to Labeling Queue.
258
261
  :type quality_check_user_ids: List[int], optional
262
+ :param guide_id: Guide ID in Supervisely to assign a guide to the Labeling Queue.
263
+ :type guide_id: int, optional
264
+ :param description: Description of Labeling Queue.
265
+ :type description: str, optional
259
266
  :return: Labeling Queue ID in Supervisely.
260
267
  :rtype: int
261
268
  :Usage example:
@@ -340,6 +347,15 @@ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
340
347
  if images_ids is not None:
341
348
  meta["entityIds"] = images_ids
342
349
 
350
+ if guide_id is not None:
351
+ try:
352
+ guide_id = int(guide_id)
353
+ except Exception as e:
354
+ raise ValueError(
355
+ f"guide_id must be an integer, got {type(guide_id)} with value '{guide_id}'"
356
+ ) from None
357
+ meta["guide"] = guide_id
358
+
343
359
  if toolbox_settings is not None:
344
360
  if dataset_id is not None:
345
361
  dataset_info = self._api.dataset.get_info_by_id(dataset_id)
@@ -399,6 +415,9 @@ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
399
415
  if readme is not None:
400
416
  data[ApiField.README] = str(readme)
401
417
 
418
+ if description is not None:
419
+ data[ApiField.DESCRIPTION] = str(description)
420
+
402
421
  if images_range is not None and images_range != (None, None):
403
422
  if len(images_range) != 2:
404
423
  raise RuntimeError("images_range has to contain 2 elements (start, end)")
@@ -419,6 +438,7 @@ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
419
438
  ids: Optional[List[int]] = None,
420
439
  names: Optional[List[str]] = None,
421
440
  show_disabled: Optional[bool] = False,
441
+ collection_id: Optional[int] = None,
422
442
  ) -> List[LabelingQueueInfo]:
423
443
  """
424
444
  Get list of information about Labeling Queues in the given Team.
@@ -435,6 +455,8 @@ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
435
455
  :type names: List[str], optional
436
456
  :param show_disabled: Show disabled Labeling Queues.
437
457
  :type show_disabled: bool, optional
458
+ :param collection_id: Entities Collection ID in Supervisely.
459
+ :type collection_id: int, optional
438
460
  :return: List of information about Labeling Queues. See :class:`info_sequence<info_sequence>`
439
461
  :rtype: :class:`List[LabelingQueueInfo]`
440
462
  :Usage example:
@@ -455,6 +477,10 @@ class LabelingQueueApi(RemoveableBulkModuleApi, ModuleWithStatus):
455
477
  filters.append({"field": ApiField.PROJECT_ID, "operator": "=", "value": project_id})
456
478
  if dataset_id is not None:
457
479
  filters.append({"field": ApiField.DATASET_ID, "operator": "=", "value": dataset_id})
480
+ if collection_id is not None:
481
+ filters.append(
482
+ {"field": ApiField.COLLECTION_ID, "operator": "=", "value": collection_id}
483
+ )
458
484
  if names is not None:
459
485
  filters.append({"field": ApiField.NAME, "operator": "in", "value": names})
460
486
  if ids is not None:
@@ -709,6 +709,15 @@ class ApiField:
709
709
  """"""
710
710
  UNIQUE_ITEMS = "uniqueItems"
711
711
  """"""
712
+ NN_CREATED = "nnCreated"
713
+ """"""
714
+ NN_UPDATED = "nnUpdated"
715
+ """"""
716
+ M_GUIDE_ID = (["meta", "guide"], "guide_id")
717
+ """"""
718
+ GUIDE_ID = "guideId"
719
+ """"""
720
+ SINGLE_SESSION_MODE = "singleSessionMode"
712
721
 
713
722
 
714
723
  def _get_single_item(items):