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
@@ -1,26 +1,33 @@
1
+ import threading
1
2
  from typing import Any, Dict, List
2
3
 
4
+ from supervisely.api.api import Api
5
+ from supervisely.api.dataset_api import DatasetInfo
6
+ from supervisely.api.project_api import ProjectInfo
7
+ from supervisely.api.video.video_api import VideoInfo
3
8
  from supervisely.app.widgets import (
4
9
  Button,
5
10
  Card,
6
11
  Container,
12
+ FastTable,
7
13
  OneOf,
8
14
  RadioGroup,
9
- RadioTable,
10
15
  SelectDatasetTree,
11
16
  Text,
12
17
  )
18
+ from supervisely.app.widgets.widget import Widget
13
19
  from supervisely.project.project import ProjectType
14
20
 
15
21
 
16
22
  class InputSelector:
17
- title = "Select Input"
23
+ title = "Input data"
18
24
  description = "Select input data on which to run model for prediction"
19
25
  lock_message = None
20
26
 
21
- def __init__(self, workspace_id: int):
27
+ def __init__(self, workspace_id: int, api: Api):
22
28
  # Init Step
23
29
  self.workspace_id = workspace_id
30
+ self.api = api
24
31
  self.display_widgets: List[Any] = []
25
32
  # -------------------------------- #
26
33
 
@@ -48,7 +55,7 @@ class InputSelector:
48
55
  self.select_dataset_for_images = SelectDatasetTree(
49
56
  multiselect=True,
50
57
  flat=True,
51
- select_all_datasets=False,
58
+ select_all_datasets=True,
52
59
  allowed_project_types=[ProjectType.IMAGES],
53
60
  always_open=False,
54
61
  compact=False,
@@ -64,16 +71,31 @@ class InputSelector:
64
71
 
65
72
  # Videos
66
73
  self.select_dataset_for_video = SelectDatasetTree(
74
+ multiselect=True,
67
75
  flat=True,
68
- select_all_datasets=False,
76
+ select_all_datasets=True,
69
77
  allowed_project_types=[ProjectType.VIDEOS],
70
78
  always_open=False,
71
79
  compact=False,
72
80
  team_is_selectable=False,
73
81
  workspace_is_selectable=False,
74
- show_select_all_datasets_checkbox=False,
82
+ show_select_all_datasets_checkbox=True,
75
83
  )
76
- self.select_video = RadioTable(columns=["id", "name", "dataset"], rows=[])
84
+ self._video_table_columns = [
85
+ "Video id",
86
+ "Video name",
87
+ "Size",
88
+ "Duration",
89
+ "FPS",
90
+ "Frames count",
91
+ "Dataset name",
92
+ "Dataset id",
93
+ ]
94
+ self.select_video = FastTable(
95
+ columns=self._video_table_columns,
96
+ is_selectable=True,
97
+ )
98
+ self.select_video.hide()
77
99
  self.select_video_container = Container(
78
100
  widgets=[self.select_dataset_for_video, self.select_video]
79
101
  )
@@ -83,9 +105,9 @@ class InputSelector:
83
105
  # -------------------------------- #
84
106
 
85
107
  # Data type Radio Selector
86
- # self.radio = RadioGroup(items=[self._radio_item_images, self._radio_item_videos])
87
- self.radio = RadioGroup(items=[self._radio_item_images])
88
- self.radio.hide()
108
+ self.radio = RadioGroup(items=[self._radio_item_images, self._radio_item_videos])
109
+ # self.radio = RadioGroup(items=[self._radio_item_images])
110
+ # self.radio.hide()
89
111
  self.one_of = OneOf(conditional_widget=self.radio)
90
112
  # Add widgets to display ------------ #
91
113
  self.display_widgets.extend([self.radio, self.one_of])
@@ -109,12 +131,148 @@ class InputSelector:
109
131
  )
110
132
  # ----------------------------------- #
111
133
 
134
+ self._refresh_table_lock = threading.Lock()
135
+ self._refresh_table_thread: threading.Thread = None
136
+ self._refresh_called = False
137
+
138
+ @self.radio.value_changed
139
+ def input_selector_type_changed(value: str):
140
+ self.validator_text.hide()
141
+
142
+ @self.select_dataset_for_images.project_changed
143
+ def _images_project_changed(project_id):
144
+ self.validator_text.hide()
145
+
146
+ @self.select_dataset_for_images.value_changed
147
+ def _images_dataset_changed(dataset_ids):
148
+ self.validator_text.hide()
149
+
150
+ @self.select_dataset_for_video.project_changed
151
+ def _videos_project_changed(project_id: int):
152
+ self._refresh_video_table_called()
153
+
154
+ @self.select_dataset_for_video.value_changed
155
+ def _videos_dataset_changed(datasets_ids):
156
+ self._refresh_video_table_called()
157
+
158
+ def _refresh_video_table_called(self):
159
+ with self._refresh_table_lock:
160
+ self._refresh_called = True
161
+ if self._refresh_table_thread is None or not self._refresh_table_thread.is_alive():
162
+ self._refresh_table_thread = threading.Thread(target=self._refresh_video_table_loop)
163
+ if self._refresh_table_thread is not None and not self._refresh_table_thread.is_alive():
164
+ self._refresh_table_thread.start()
165
+
166
+ def _refresh_video_table_loop(self):
167
+ while self._refresh_called:
168
+ with self._refresh_table_lock:
169
+ self._refresh_called = False
170
+ self.select_video.loading = True
171
+ self._refresh_video_table()
172
+ if not self._refresh_called:
173
+ self.select_video.loading = False
174
+
175
+ def _refresh_video_table(self):
176
+ self.validator_text.hide()
177
+ self.select_video.clear()
178
+ selected_datasets = self.select_dataset_for_video.get_selected_ids()
179
+ if not selected_datasets:
180
+ self.select_video.hide()
181
+ else:
182
+ rows = []
183
+ self.select_video.show()
184
+ for dataset_id in selected_datasets:
185
+ dataset_info = self.api.dataset.get_info_by_id(dataset_id)
186
+ videos = self.api.video.get_list(dataset_id)
187
+ for video in videos:
188
+ size = f"{video.frame_height}x{video.frame_width}"
189
+ try:
190
+ frame_rate = int(video.frames_count / video.duration)
191
+ except:
192
+ frame_rate = "N/A"
193
+ rows.append(
194
+ [
195
+ video.id,
196
+ video.name,
197
+ size,
198
+ video.duration,
199
+ frame_rate,
200
+ video.frames_count,
201
+ dataset_info.name,
202
+ dataset_info.id,
203
+ ]
204
+ )
205
+
206
+ self.select_video.add_rows(rows)
207
+
208
+ def select_project(self, project_id: int, project_info: ProjectInfo = None):
209
+ if project_info is None:
210
+ project_info = self.api.project.get_info_by_id(project_id)
211
+ if project_info.type == ProjectType.IMAGES.value:
212
+ self.select_dataset_for_images.set_project_id(project_id)
213
+ self.select_dataset_for_images.select_all()
214
+ self.radio.set_value(ProjectType.IMAGES.value)
215
+ elif project_info.type == ProjectType.VIDEOS.value:
216
+ self.select_dataset_for_video.set_project_id(project_id)
217
+ self.select_dataset_for_video.select_all()
218
+ self._refresh_video_table()
219
+ self.select_video.select_rows(list(range(self.select_video._rows_total)))
220
+ self.radio.set_value(ProjectType.VIDEOS.value)
221
+ else:
222
+ raise ValueError(f"Project of type {project_info.type} is not supported.")
223
+
224
+ def select_datasets(self, dataset_ids: List[int], dataset_infos: List[DatasetInfo] = None):
225
+ if dataset_infos is None:
226
+ dataset_infos = [self.api.dataset.get_info_by_id(ds_id) for ds_id in dataset_ids]
227
+ project_ids = set(ds.project_id for ds in dataset_infos)
228
+ if len(project_ids) > 1:
229
+ raise ValueError("Cannot select datasets from different projects")
230
+ project_id = project_ids.pop()
231
+ project_info = self.api.project.get_info_by_id(project_id)
232
+ if project_info.type == ProjectType.IMAGES.value:
233
+ self.select_dataset_for_images.set_project_id(project_id)
234
+ self.select_dataset_for_images.set_dataset_ids(dataset_ids)
235
+ self.radio.set_value(ProjectType.IMAGES.value)
236
+ elif project_info.type == ProjectType.VIDEOS.value:
237
+ self.select_dataset_for_video.set_project_id(project_id)
238
+ self.select_dataset_for_video.set_dataset_ids(dataset_ids)
239
+ self._refresh_video_table()
240
+ self.select_video.select_rows(list(range(self.select_video._rows_total)))
241
+ self.radio.set_value(ProjectType.VIDEOS.value)
242
+ else:
243
+ raise ValueError(f"Project of type {project_info.type} is not supported.")
244
+
245
+ def select_videos(self, video_ids: List[int], video_infos: List[VideoInfo] = None):
246
+ if video_infos is None:
247
+ video_infos = self.api.video.get_info_by_id_batch(video_ids)
248
+ project_id = video_infos[0].project_id
249
+ self.select_dataset_for_video.set_project_id(project_id)
250
+ self.select_dataset_for_video.select_all()
251
+ self._refresh_video_table()
252
+ self.select_video.select_row_by_value("id", video_ids)
253
+ self.radio.set_value(ProjectType.VIDEOS.value)
254
+
255
+ def disable(self):
256
+ for widget in self.widgets_to_disable:
257
+ widget.disable()
258
+
259
+ def enable(self):
260
+ for widget in self.widgets_to_disable:
261
+ widget.enable()
262
+
112
263
  @property
113
- def widgets_to_disable(self) -> list:
264
+ def widgets_to_disable(self) -> List[Widget]:
114
265
  return [
266
+ # Images Selector
115
267
  self.select_dataset_for_images,
268
+ self.select_dataset_for_images._select_project,
269
+ self.select_dataset_for_images._select_dataset,
270
+ # Videos Selector
116
271
  self.select_dataset_for_video,
272
+ self.select_dataset_for_video._select_project,
273
+ self.select_dataset_for_video._select_dataset,
117
274
  self.select_video,
275
+ # Controls
118
276
  self.radio,
119
277
  self.one_of,
120
278
  ]
@@ -126,39 +284,60 @@ class InputSelector:
126
284
  "dataset_ids": self.select_dataset_for_images.get_selected_ids(),
127
285
  }
128
286
  if self.radio.get_value() == ProjectType.VIDEOS.value:
129
- return {"video_id": self.select_video.get_selected_row()}
287
+ rows = self.select_video.get_selected_rows()
288
+ if rows:
289
+ video_ids = [row.row[0] for row in rows]
290
+ else:
291
+ video_ids = None
292
+ return {"video_ids": video_ids}
130
293
 
131
294
  def load_from_json(self, data):
132
- if "project_id" in data:
133
- self.select_dataset_for_images.set_project_id(data["project_id"])
134
- self.select_dataset_for_images.select_all()
135
- self.radio.set_value(ProjectType.IMAGES.value)
136
- if "dataset_ids" in data:
137
- self.select_dataset_for_images.set_dataset_ids(data["dataset_ids"])
138
- self.radio.set_value(ProjectType.IMAGES.value)
139
- if "video_id" in data:
140
- self.select_video.select_row_by_value("id", data["video_id"])
141
- self.radio.set_value(ProjectType.VIDEOS.value)
295
+ if "video_ids" in data:
296
+ video_ids = data["video_ids"]
297
+ if not video_ids:
298
+ raise ValueError("Video ids cannot be empty")
299
+ video_infos = self.api.video.get_info_by_id_batch(video_ids)
300
+ if not video_infos:
301
+ raise ValueError(f"Videos with video ids {video_ids} are not found")
302
+ self.select_videos(video_ids, video_infos)
303
+ elif "dataset_ids" in data:
304
+ dataset_ids = data["dataset_ids"]
305
+ self.select_datasets(dataset_ids)
306
+ elif "project_id" in data:
307
+ project_id = data["project_id"]
308
+ self.select_project(project_id)
309
+
310
+ def get_project_id(self) -> int:
311
+ if self.radio.get_value() == ProjectType.IMAGES.value:
312
+ return self.select_dataset_for_images.project_id
313
+ if self.radio.get_value() == ProjectType.VIDEOS.value:
314
+ return self.select_dataset_for_video.project_id
315
+ return None
142
316
 
143
317
  def validate_step(self) -> bool:
144
318
  self.validator_text.hide()
145
319
  if self.radio.get_value() == ProjectType.IMAGES.value:
146
- if len(self.select_dataset_for_images.get_selected_ids()) == 0:
320
+ selected_ids = self.select_dataset_for_images.get_selected_ids()
321
+ if selected_ids is None:
322
+ self.validator_text.set(text="Select a project", status="error")
323
+ self.validator_text.show()
324
+ return False
325
+ if len(selected_ids) == 0:
147
326
  self.validator_text.set(text="Select at least one dataset", status="error")
148
327
  self.validator_text.show()
149
328
  return False
150
329
  if self.radio.get_value() == ProjectType.VIDEOS.value:
151
- if self.select_dataset_for_video.get_selected_id() is None:
330
+ if not self.select_dataset_for_video.get_selected_ids():
152
331
  self.validator_text.set(text="Select a dataset", status="error")
153
332
  self.validator_text.show()
154
333
  return False
155
- if len(self.select_video.rows) == 0:
334
+ if self.select_video._rows_total == 0:
156
335
  self.validator_text.set(
157
336
  text="No videos found in the selected dataset", status="error"
158
337
  )
159
338
  self.validator_text.show()
160
339
  return False
161
- if self.select_video.get_selected_row() == []:
340
+ if self.select_video.get_selected_rows() == []:
162
341
  self.validator_text.set(text="Select a video", status="error")
163
342
  self.validator_text.show()
164
343
  return False
@@ -5,7 +5,7 @@ from supervisely.app.widgets import Button, Card, Container, DeployModel, Text
5
5
 
6
6
 
7
7
  class ModelSelector:
8
- title = "Select Model"
8
+ title = "Model"
9
9
  description = "Connect to deployed model or deploy new model"
10
10
  lock_message = "Select previous step to unlock"
11
11
 
@@ -36,9 +36,8 @@ class ModelSelector:
36
36
  # Base Widgets
37
37
  self.validator_text = Text("")
38
38
  self.validator_text.hide()
39
- self.button = Button("Select")
40
39
  # Add widgets to display ------------ #
41
- self.display_widgets.extend([self.validator_text, self.button])
40
+ self.display_widgets.extend([self.validator_text])
42
41
  # ----------------------------------- #
43
42
 
44
43
  # Card Layout
@@ -49,7 +48,6 @@ class ModelSelector:
49
48
  content=self.container,
50
49
  lock_message=self.lock_message,
51
50
  )
52
- self.card.lock()
53
51
  # ----------------------------------- #
54
52
 
55
53
  @property
@@ -9,14 +9,17 @@ from supervisely.app.widgets import (
9
9
  Container,
10
10
  Field,
11
11
  Input,
12
+ OneOf,
12
13
  Progress,
13
14
  ProjectThumbnail,
15
+ RadioGroup,
14
16
  Text,
15
17
  )
18
+ from supervisely.project.project_meta import ProjectType
16
19
 
17
20
 
18
21
  class OutputSelector:
19
- title = "Select Output"
22
+ title = "Result"
20
23
  description = "Select the output mode"
21
24
  lock_message = "Select previous step to unlock"
22
25
 
@@ -58,8 +61,18 @@ class OutputSelector:
58
61
  title="New Project Name",
59
62
  description="Name of the new project to create for the results. The created project will have the same dataset structure as the input project.",
60
63
  )
64
+ self.skip_annotated_checkbox = Checkbox("Skip annotated items", False)
65
+ self._tab_names = ["Create New Project", "Update source project"]
66
+ self._tab_contents = [self.project_name_field, self.skip_annotated_checkbox]
67
+ self.tabs = RadioGroup(
68
+ items=[
69
+ RadioGroup.Item(tab_name, content=tab_content)
70
+ for tab_name, tab_content in zip(self._tab_names, self._tab_contents)
71
+ ],
72
+ )
73
+ self.oneof = OneOf(self.tabs)
61
74
  # Add widgets to display ------------ #
62
- self.display_widgets.extend([self.project_name_field])
75
+ self.display_widgets.extend([self.tabs, self.oneof])
63
76
  # ----------------------------------- #
64
77
 
65
78
  # Base Widgets
@@ -74,8 +87,10 @@ class OutputSelector:
74
87
  # Progress
75
88
  self.progress = Progress(hide_on_finish=False)
76
89
  self.progress.hide()
90
+ self.secondary_progress = Progress(hide_on_finish=False)
91
+ self.secondary_progress.hide()
77
92
  # Add widgets to display ------------ #
78
- self.display_widgets.extend([self.progress])
93
+ self.display_widgets.extend([self.progress, self.secondary_progress])
79
94
  # ----------------------------------- #
80
95
 
81
96
  # Result
@@ -93,9 +108,14 @@ class OutputSelector:
93
108
  content=self.container,
94
109
  lock_message=self.lock_message,
95
110
  )
96
- self.card.lock()
97
111
  # ----------------------------------- #
98
112
 
113
+ def lock(self):
114
+ self.card.lock(self.lock_message)
115
+
116
+ def unlock(self):
117
+ self.card.unlock()
118
+
99
119
  @property
100
120
  def widgets_to_disable(self) -> list:
101
121
  return [self.project_name_input]
@@ -111,7 +131,11 @@ class OutputSelector:
111
131
 
112
132
  def get_settings(self) -> Dict[str, Any]:
113
133
  settings = {}
114
- settings["project_name"] = self.project_name_input.get_value()
134
+ if self.tabs.get_value() == self._tab_names[1]:
135
+ settings["upload_to_source_project"] = True
136
+ else:
137
+ settings["project_name"] = self.project_name_input.get_value()
138
+ settings["skip_annotated"] = self.skip_annotated_checkbox.is_checked()
115
139
  return settings
116
140
 
117
141
  def should_stop_serving_on_finish(self) -> bool:
@@ -128,12 +152,28 @@ class OutputSelector:
128
152
  project_name = data.get("project_name", None)
129
153
  if project_name:
130
154
  self.project_name_input.set_value(project_name)
155
+ upload_to_source_project = data.get("upload_to_source_project", False)
156
+ if upload_to_source_project:
157
+ self.tabs.set_value(self._tab_names[1])
158
+ else:
159
+ self.tabs.set_value(self._tab_names[0])
131
160
 
132
161
  def validate_step(self) -> bool:
133
162
  self.validator_text.hide()
134
- if self.project_name_input.get_value() == "":
163
+ if (
164
+ self.tabs.get_value() == self._tab_names[0]
165
+ and self.project_name_input.get_value() == ""
166
+ ):
135
167
  self.validator_text.set(text="Project name is required", status="error")
136
168
  self.validator_text.show()
137
169
  return False
138
170
 
139
171
  return True
172
+
173
+ def update_item_type(self, item_type: str):
174
+ if item_type == ProjectType.IMAGES.value:
175
+ self.skip_annotated_checkbox.show()
176
+ elif item_type == ProjectType.VIDEOS.value:
177
+ self.skip_annotated_checkbox.hide()
178
+ else:
179
+ raise ValueError(f"Unsupported item type: {item_type}")