supervisely 6.73.452__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.
- supervisely/__init__.py +25 -1
- supervisely/annotation/annotation.py +8 -2
- supervisely/annotation/json_geometries_map.py +13 -12
- supervisely/api/annotation_api.py +6 -3
- supervisely/api/api.py +2 -0
- supervisely/api/app_api.py +10 -1
- supervisely/api/dataset_api.py +74 -12
- supervisely/api/entities_collection_api.py +10 -0
- supervisely/api/entity_annotation/figure_api.py +28 -0
- supervisely/api/entity_annotation/object_api.py +3 -3
- supervisely/api/entity_annotation/tag_api.py +63 -12
- supervisely/api/guides_api.py +210 -0
- supervisely/api/image_api.py +4 -0
- supervisely/api/labeling_job_api.py +83 -1
- supervisely/api/labeling_queue_api.py +33 -7
- supervisely/api/module_api.py +5 -0
- supervisely/api/project_api.py +71 -26
- supervisely/api/storage_api.py +3 -1
- supervisely/api/task_api.py +13 -2
- supervisely/api/team_api.py +4 -3
- supervisely/api/video/video_annotation_api.py +119 -3
- supervisely/api/video/video_api.py +65 -14
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +23 -7
- supervisely/app/development/development.py +18 -2
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +105 -0
- supervisely/app/fastapi/subapp.py +88 -42
- supervisely/app/fastapi/websocket.py +77 -9
- supervisely/app/singleton.py +21 -0
- supervisely/app/v1/app_service.py +18 -2
- supervisely/app/v1/constants.py +7 -1
- supervisely/app/widgets/__init__.py +6 -0
- supervisely/app/widgets/activity_feed/__init__.py +0 -0
- supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
- supervisely/app/widgets/activity_feed/style.css +78 -0
- supervisely/app/widgets/activity_feed/template.html +22 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
- supervisely/app/widgets/classes_list_selector/template.html +60 -93
- supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
- supervisely/app/widgets/classes_table/classes_table.py +1 -0
- supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
- supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
- supervisely/app/widgets/fast_table/fast_table.py +184 -60
- supervisely/app/widgets/fast_table/template.html +1 -1
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +564 -0
- supervisely/app/widgets/heatmap/script.js +533 -0
- supervisely/app/widgets/heatmap/style.css +233 -0
- supervisely/app/widgets/heatmap/template.html +21 -0
- supervisely/app/widgets/modal/__init__.py +0 -0
- supervisely/app/widgets/modal/modal.py +198 -0
- supervisely/app/widgets/modal/template.html +10 -0
- supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
- supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
- supervisely/app/widgets/radio_tabs/template.html +1 -0
- supervisely/app/widgets/select/select.py +6 -3
- supervisely/app/widgets/select_class/__init__.py +0 -0
- supervisely/app/widgets/select_class/select_class.py +363 -0
- supervisely/app/widgets/select_class/template.html +50 -0
- supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
- supervisely/app/widgets/select_tag/__init__.py +0 -0
- supervisely/app/widgets/select_tag/select_tag.py +352 -0
- supervisely/app/widgets/select_tag/template.html +64 -0
- supervisely/app/widgets/select_team/select_team.py +37 -4
- supervisely/app/widgets/select_team/template.html +4 -5
- supervisely/app/widgets/select_user/__init__.py +0 -0
- supervisely/app/widgets/select_user/select_user.py +270 -0
- supervisely/app/widgets/select_user/template.html +13 -0
- supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
- supervisely/app/widgets/select_workspace/template.html +9 -12
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/aug/aug.py +6 -2
- supervisely/convert/base_converter.py +1 -0
- supervisely/convert/converter.py +2 -2
- supervisely/convert/image/image_converter.py +3 -1
- supervisely/convert/image/image_helper.py +48 -4
- supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
- supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
- supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
- supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
- supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
- supervisely/convert/pointcloud/las/las_converter.py +13 -1
- supervisely/convert/pointcloud/las/las_helper.py +110 -11
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
- supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
- supervisely/convert/video/__init__.py +1 -0
- supervisely/convert/video/multi_view/__init__.py +0 -0
- supervisely/convert/video/multi_view/multi_view.py +543 -0
- supervisely/convert/video/sly/sly_video_converter.py +359 -3
- supervisely/convert/video/video_converter.py +22 -2
- supervisely/convert/volume/dicom/dicom_converter.py +13 -5
- supervisely/convert/volume/dicom/dicom_helper.py +30 -18
- supervisely/geometry/constants.py +1 -0
- supervisely/geometry/geometry.py +4 -0
- supervisely/geometry/helpers.py +5 -1
- supervisely/geometry/oriented_bbox.py +676 -0
- supervisely/geometry/rectangle.py +2 -1
- supervisely/io/env.py +76 -1
- supervisely/io/fs.py +21 -0
- supervisely/nn/benchmark/base_evaluator.py +104 -11
- supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
- supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
- supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
- supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
- supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
- supervisely/nn/inference/cache.py +43 -18
- supervisely/nn/inference/gui/serving_gui_template.py +5 -2
- supervisely/nn/inference/inference.py +795 -199
- supervisely/nn/inference/inference_request.py +42 -9
- supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
- supervisely/nn/inference/predict_app/gui/gui.py +676 -488
- supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
- supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
- supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
- supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
- supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
- supervisely/nn/inference/predict_app/gui/utils.py +236 -119
- supervisely/nn/inference/predict_app/predict_app.py +2 -2
- supervisely/nn/inference/session.py +43 -35
- supervisely/nn/inference/tracking/bbox_tracking.py +113 -34
- supervisely/nn/inference/tracking/tracker_interface.py +7 -2
- supervisely/nn/inference/uploader.py +139 -12
- supervisely/nn/live_training/__init__.py +7 -0
- supervisely/nn/live_training/api_server.py +111 -0
- supervisely/nn/live_training/artifacts_utils.py +243 -0
- supervisely/nn/live_training/checkpoint_utils.py +229 -0
- supervisely/nn/live_training/dynamic_sampler.py +44 -0
- supervisely/nn/live_training/helpers.py +14 -0
- supervisely/nn/live_training/incremental_dataset.py +146 -0
- supervisely/nn/live_training/live_training.py +497 -0
- supervisely/nn/live_training/loss_plateau_detector.py +111 -0
- supervisely/nn/live_training/request_queue.py +52 -0
- supervisely/nn/model/model_api.py +9 -0
- supervisely/nn/prediction_dto.py +12 -1
- supervisely/nn/tracker/base_tracker.py +11 -1
- supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
- supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
- supervisely/nn/tracker/botsort_tracker.py +94 -65
- supervisely/nn/tracker/visualize.py +87 -90
- supervisely/nn/training/gui/classes_selector.py +16 -1
- supervisely/nn/training/train_app.py +28 -29
- supervisely/project/data_version.py +115 -51
- supervisely/project/download.py +1 -1
- supervisely/project/pointcloud_episode_project.py +37 -8
- supervisely/project/pointcloud_project.py +30 -2
- supervisely/project/project.py +14 -2
- supervisely/project/project_meta.py +27 -1
- supervisely/project/project_settings.py +32 -18
- supervisely/project/versioning/__init__.py +1 -0
- supervisely/project/versioning/common.py +20 -0
- supervisely/project/versioning/schema_fields.py +35 -0
- supervisely/project/versioning/video_schema.py +221 -0
- supervisely/project/versioning/volume_schema.py +87 -0
- supervisely/project/video_project.py +717 -15
- supervisely/project/volume_project.py +623 -5
- supervisely/template/experiment/experiment.html.jinja +4 -4
- supervisely/template/experiment/experiment_generator.py +14 -21
- supervisely/template/live_training/__init__.py +0 -0
- supervisely/template/live_training/header.html.jinja +96 -0
- supervisely/template/live_training/live_training.html.jinja +51 -0
- supervisely/template/live_training/live_training_generator.py +464 -0
- supervisely/template/live_training/sly-style.css +402 -0
- supervisely/template/live_training/template.html.jinja +18 -0
- supervisely/versions.json +28 -26
- supervisely/video/sampling.py +39 -20
- supervisely/video/video.py +40 -11
- supervisely/video_annotation/video_object.py +29 -4
- supervisely/volume/stl_converter.py +2 -0
- supervisely/worker_api/agent_rpc.py +24 -1
- supervisely/worker_api/rpc_servicer.py +31 -7
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/METADATA +56 -39
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/RECORD +189 -142
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
- {supervisely-6.73.452.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")
|
supervisely/api/image_api.py
CHANGED
|
@@ -397,6 +397,9 @@ class ImageInfo(NamedTuple):
|
|
|
397
397
|
#: Format: "YYYY-MM-DDTHH:MM:SS.sssZ"
|
|
398
398
|
embeddings_updated_at: Optional[str] = None
|
|
399
399
|
|
|
400
|
+
#: :class:`int`: :class:`Dataset<supervisely.project.project.Project>` ID in Supervisely.
|
|
401
|
+
project_id: int = None
|
|
402
|
+
|
|
400
403
|
# DO NOT DELETE THIS COMMENT
|
|
401
404
|
#! New fields must be added with default values to keep backward compatibility.
|
|
402
405
|
|
|
@@ -476,6 +479,7 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
476
479
|
ApiField.OFFSET_END,
|
|
477
480
|
ApiField.AI_SEARCH_META,
|
|
478
481
|
ApiField.EMBEDDINGS_UPDATED_AT,
|
|
482
|
+
ApiField.PROJECT_ID,
|
|
479
483
|
]
|
|
480
484
|
|
|
481
485
|
@staticmethod
|
|
@@ -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
|
-
|
|
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:
|
supervisely/api/module_api.py
CHANGED
|
@@ -713,6 +713,11 @@ class ApiField:
|
|
|
713
713
|
""""""
|
|
714
714
|
NN_UPDATED = "nnUpdated"
|
|
715
715
|
""""""
|
|
716
|
+
M_GUIDE_ID = (["meta", "guide"], "guide_id")
|
|
717
|
+
""""""
|
|
718
|
+
GUIDE_ID = "guideId"
|
|
719
|
+
""""""
|
|
720
|
+
SINGLE_SESSION_MODE = "singleSessionMode"
|
|
716
721
|
|
|
717
722
|
|
|
718
723
|
def _get_single_item(items):
|
supervisely/api/project_api.py
CHANGED
|
@@ -52,6 +52,7 @@ from supervisely.io.json import dump_json_file, load_json_file
|
|
|
52
52
|
from supervisely.project.project_meta import ProjectMeta
|
|
53
53
|
from supervisely.project.project_meta import ProjectMetaJsonFields as MetaJsonF
|
|
54
54
|
from supervisely.project.project_settings import (
|
|
55
|
+
LabelingInterface,
|
|
55
56
|
ProjectSettings,
|
|
56
57
|
ProjectSettingsJsonFields,
|
|
57
58
|
)
|
|
@@ -691,6 +692,7 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
691
692
|
type: ProjectType = ProjectType.IMAGES,
|
|
692
693
|
description: Optional[str] = "",
|
|
693
694
|
change_name_if_conflict: Optional[bool] = False,
|
|
695
|
+
readme: Optional[str] = None,
|
|
694
696
|
) -> ProjectInfo:
|
|
695
697
|
"""
|
|
696
698
|
Create Project with given name in the given Workspace ID.
|
|
@@ -705,6 +707,8 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
705
707
|
:type description: str
|
|
706
708
|
:param change_name_if_conflict: Checks if given name already exists and adds suffix to the end of the name.
|
|
707
709
|
:type change_name_if_conflict: bool, optional
|
|
710
|
+
:param readme: Project readme.
|
|
711
|
+
:type readme: str, optional
|
|
708
712
|
:return: Information about Project. See :class:`info_sequence<info_sequence>`
|
|
709
713
|
:rtype: :class:`ProjectInfo`
|
|
710
714
|
:Usage example:
|
|
@@ -745,15 +749,15 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
745
749
|
name=name,
|
|
746
750
|
change_name_if_conflict=change_name_if_conflict,
|
|
747
751
|
)
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
)
|
|
752
|
+
payload = {
|
|
753
|
+
ApiField.NAME: effective_name,
|
|
754
|
+
ApiField.WORKSPACE_ID: workspace_id,
|
|
755
|
+
ApiField.DESCRIPTION: description,
|
|
756
|
+
ApiField.TYPE: str(type),
|
|
757
|
+
}
|
|
758
|
+
if readme is not None:
|
|
759
|
+
payload[ApiField.README] = readme
|
|
760
|
+
response = self._api.post("projects.add", payload)
|
|
757
761
|
return self._convert_json_info(response.json())
|
|
758
762
|
|
|
759
763
|
def _get_update_method(self):
|
|
@@ -1368,6 +1372,8 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
1368
1372
|
|
|
1369
1373
|
def get_settings(self, id: int) -> Dict[str, str]:
|
|
1370
1374
|
info = self._get_info_by_id(id, "projects.info")
|
|
1375
|
+
if info is None:
|
|
1376
|
+
raise ProjectNotFound(f"Project with id={id} not found")
|
|
1371
1377
|
return info.settings
|
|
1372
1378
|
|
|
1373
1379
|
def update_settings(self, id: int, settings: Dict[str, str]) -> None:
|
|
@@ -1993,8 +1999,11 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
1993
1999
|
)
|
|
1994
2000
|
|
|
1995
2001
|
def set_multiview_settings(self, project_id: int) -> None:
|
|
1996
|
-
"""Sets the project settings for multiview
|
|
1997
|
-
|
|
2002
|
+
"""Sets the project settings for multiview mode.
|
|
2003
|
+
Automatically detects project type and applies appropriate settings:
|
|
2004
|
+
|
|
2005
|
+
- For IMAGE projects: Images are grouped by tag with synchronized view and labeling.
|
|
2006
|
+
- For VIDEO projects: Videos are grouped by datasets (each dataset = one group).
|
|
1998
2007
|
|
|
1999
2008
|
:param project_id: Project ID to set multiview settings.
|
|
2000
2009
|
:type project_id: int
|
|
@@ -2015,17 +2024,51 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2015
2024
|
load_dotenv(os.path.expanduser("~/supervisely.env"))
|
|
2016
2025
|
api = sly.Api.from_env()
|
|
2017
2026
|
|
|
2018
|
-
|
|
2027
|
+
# For images project - will enable grouping by tags
|
|
2028
|
+
api.project.set_multiview_settings(image_project_id)
|
|
2029
|
+
|
|
2030
|
+
# For videos project - will enable grouping by datasets
|
|
2031
|
+
api.project.set_multiview_settings(video_project_id)
|
|
2019
2032
|
"""
|
|
2033
|
+
project_info = self.get_info_by_id(project_id)
|
|
2034
|
+
if project_info.type == ProjectType.IMAGES.value:
|
|
2035
|
+
self._set_custom_grouping_settings(
|
|
2036
|
+
id=project_id,
|
|
2037
|
+
group_images=True,
|
|
2038
|
+
tag_name=_MULTIVIEW_TAG_NAME,
|
|
2039
|
+
sync=False,
|
|
2040
|
+
label_group_tag_name=_LABEL_GROUP_TAG_NAME,
|
|
2041
|
+
)
|
|
2042
|
+
elif project_info.type == ProjectType.VIDEOS.value:
|
|
2043
|
+
self._set_custom_grouping_settings_video(project_id, sync=True)
|
|
2044
|
+
else:
|
|
2045
|
+
raise ValueError("Multiview settings can only be set for image or video projects")
|
|
2020
2046
|
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2047
|
+
def _set_custom_grouping_settings_video(self, project_id: int, sync: bool = True) -> None:
|
|
2048
|
+
"""Sets the project settings for multiview videos (private method).
|
|
2049
|
+
For video projects, videos are grouped by datasets (not by tags).
|
|
2050
|
+
Each dataset represents a group of videos that will be displayed together in multiview mode.
|
|
2051
|
+
|
|
2052
|
+
:param project_id: Project ID to set video multiview settings.
|
|
2053
|
+
:type project_id: int
|
|
2054
|
+
:param sync: If True, enables synchronized playback across video views.
|
|
2055
|
+
:type sync: bool
|
|
2056
|
+
:return: None
|
|
2057
|
+
:rtype: :class:`NoneType`
|
|
2058
|
+
"""
|
|
2059
|
+
meta = ProjectMeta.from_json(self.get_meta(project_id, with_settings=True))
|
|
2060
|
+
|
|
2061
|
+
new_settings = ProjectSettings(
|
|
2062
|
+
multiview_enabled=True,
|
|
2063
|
+
multiview_tag_name=None, # Not used for videos
|
|
2064
|
+
multiview_tag_id=None, # Not used for videos
|
|
2065
|
+
multiview_is_synced=sync,
|
|
2066
|
+
labeling_interface=LabelingInterface.MULTIVIEW,
|
|
2027
2067
|
)
|
|
2028
2068
|
|
|
2069
|
+
meta = meta.clone(project_settings=new_settings)
|
|
2070
|
+
self.update_meta(id=project_id, meta=meta)
|
|
2071
|
+
|
|
2029
2072
|
def remove_permanently(
|
|
2030
2073
|
self, ids: Union[int, List], batch_size: int = 50, progress_cb=None
|
|
2031
2074
|
) -> List[dict]:
|
|
@@ -2327,7 +2370,7 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2327
2370
|
"""
|
|
2328
2371
|
info = self.get_info_by_id(id, extra_fields=[ApiField.EMBEDDINGS_IN_PROGRESS])
|
|
2329
2372
|
if info is None:
|
|
2330
|
-
raise
|
|
2373
|
+
raise ProjectNotFound(f"Project with ID {id} not found.")
|
|
2331
2374
|
if not hasattr(info, "embeddings_in_progress"):
|
|
2332
2375
|
raise RuntimeError(
|
|
2333
2376
|
f"Project with ID {id} does not have 'embeddings_in_progress' field in its info."
|
|
@@ -2392,7 +2435,7 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2392
2435
|
"""
|
|
2393
2436
|
info = self.get_info_by_id(id, extra_fields=[ApiField.EMBEDDINGS_UPDATED_AT])
|
|
2394
2437
|
if info is None:
|
|
2395
|
-
raise
|
|
2438
|
+
raise ProjectNotFound(f"Project with ID {id} not found.")
|
|
2396
2439
|
if not hasattr(info, "embeddings_updated_at"):
|
|
2397
2440
|
raise RuntimeError(
|
|
2398
2441
|
f"Project with ID {id} does not have 'embeddings_updated_at' field in its info."
|
|
@@ -2606,7 +2649,9 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2606
2649
|
)
|
|
2607
2650
|
dst_project_id = dst_project_info.id
|
|
2608
2651
|
|
|
2609
|
-
datasets = self._api.dataset.get_list(
|
|
2652
|
+
datasets = self._api.dataset.get_list(
|
|
2653
|
+
src_project_id, recursive=True, include_custom_data=True
|
|
2654
|
+
)
|
|
2610
2655
|
src_to_dst_ids = {}
|
|
2611
2656
|
|
|
2612
2657
|
for src_dataset_info in datasets:
|
|
@@ -2626,7 +2671,7 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2626
2671
|
src_project_id: int,
|
|
2627
2672
|
dst_project_id: Optional[int] = None,
|
|
2628
2673
|
dst_project_name: Optional[str] = None,
|
|
2629
|
-
) -> Tuple[
|
|
2674
|
+
) -> List[Tuple[DatasetInfo, DatasetInfo]]:
|
|
2630
2675
|
"""This method can be used to recreate a project with hierarchial datasets (without the data itself).
|
|
2631
2676
|
|
|
2632
2677
|
:param src_project_id: Source project ID
|
|
@@ -2636,8 +2681,8 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2636
2681
|
:param dst_project_name: Name of the destination project. If `dst_project_id` is None, a new project will be created with this name. If `dst_project_id` is provided, this parameter will be ignored.
|
|
2637
2682
|
:type dst_project_name: str, optional
|
|
2638
2683
|
|
|
2639
|
-
:return:
|
|
2640
|
-
:rtype:
|
|
2684
|
+
:return: List of tuples of source and destination DatasetInfo objects
|
|
2685
|
+
:rtype: List[Tuple[DatasetInfo, DatasetInfo]]
|
|
2641
2686
|
|
|
2642
2687
|
:Usage example:
|
|
2643
2688
|
|
|
@@ -2650,8 +2695,8 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2650
2695
|
src_project_id = 123
|
|
2651
2696
|
dst_project_name = "New Project"
|
|
2652
2697
|
|
|
2653
|
-
|
|
2654
|
-
print(f"Recreated project {src_project_id}
|
|
2698
|
+
infos = api.project.recreate_structure(src_project_id, dst_project_name=dst_project_name)
|
|
2699
|
+
print(f"Recreated project {src_project_id}")
|
|
2655
2700
|
"""
|
|
2656
2701
|
infos = []
|
|
2657
2702
|
for src_info, dst_info in self.recreate_structure_generator(
|