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.
Files changed (189) hide show
  1. supervisely/__init__.py +25 -1
  2. supervisely/annotation/annotation.py +8 -2
  3. supervisely/annotation/json_geometries_map.py +13 -12
  4. supervisely/api/annotation_api.py +6 -3
  5. supervisely/api/api.py +2 -0
  6. supervisely/api/app_api.py +10 -1
  7. supervisely/api/dataset_api.py +74 -12
  8. supervisely/api/entities_collection_api.py +10 -0
  9. supervisely/api/entity_annotation/figure_api.py +28 -0
  10. supervisely/api/entity_annotation/object_api.py +3 -3
  11. supervisely/api/entity_annotation/tag_api.py +63 -12
  12. supervisely/api/guides_api.py +210 -0
  13. supervisely/api/image_api.py +4 -0
  14. supervisely/api/labeling_job_api.py +83 -1
  15. supervisely/api/labeling_queue_api.py +33 -7
  16. supervisely/api/module_api.py +5 -0
  17. supervisely/api/project_api.py +71 -26
  18. supervisely/api/storage_api.py +3 -1
  19. supervisely/api/task_api.py +13 -2
  20. supervisely/api/team_api.py +4 -3
  21. supervisely/api/video/video_annotation_api.py +119 -3
  22. supervisely/api/video/video_api.py +65 -14
  23. supervisely/app/__init__.py +1 -1
  24. supervisely/app/content.py +23 -7
  25. supervisely/app/development/development.py +18 -2
  26. supervisely/app/fastapi/__init__.py +1 -0
  27. supervisely/app/fastapi/custom_static_files.py +1 -1
  28. supervisely/app/fastapi/multi_user.py +105 -0
  29. supervisely/app/fastapi/subapp.py +88 -42
  30. supervisely/app/fastapi/websocket.py +77 -9
  31. supervisely/app/singleton.py +21 -0
  32. supervisely/app/v1/app_service.py +18 -2
  33. supervisely/app/v1/constants.py +7 -1
  34. supervisely/app/widgets/__init__.py +6 -0
  35. supervisely/app/widgets/activity_feed/__init__.py +0 -0
  36. supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
  37. supervisely/app/widgets/activity_feed/style.css +78 -0
  38. supervisely/app/widgets/activity_feed/template.html +22 -0
  39. supervisely/app/widgets/card/card.py +20 -0
  40. supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
  41. supervisely/app/widgets/classes_list_selector/template.html +60 -93
  42. supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
  43. supervisely/app/widgets/classes_table/classes_table.py +1 -0
  44. supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
  45. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
  46. supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
  47. supervisely/app/widgets/fast_table/fast_table.py +184 -60
  48. supervisely/app/widgets/fast_table/template.html +1 -1
  49. supervisely/app/widgets/heatmap/__init__.py +0 -0
  50. supervisely/app/widgets/heatmap/heatmap.py +564 -0
  51. supervisely/app/widgets/heatmap/script.js +533 -0
  52. supervisely/app/widgets/heatmap/style.css +233 -0
  53. supervisely/app/widgets/heatmap/template.html +21 -0
  54. supervisely/app/widgets/modal/__init__.py +0 -0
  55. supervisely/app/widgets/modal/modal.py +198 -0
  56. supervisely/app/widgets/modal/template.html +10 -0
  57. supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
  58. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  59. supervisely/app/widgets/radio_tabs/template.html +1 -0
  60. supervisely/app/widgets/select/select.py +6 -3
  61. supervisely/app/widgets/select_class/__init__.py +0 -0
  62. supervisely/app/widgets/select_class/select_class.py +363 -0
  63. supervisely/app/widgets/select_class/template.html +50 -0
  64. supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
  65. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
  66. supervisely/app/widgets/select_tag/__init__.py +0 -0
  67. supervisely/app/widgets/select_tag/select_tag.py +352 -0
  68. supervisely/app/widgets/select_tag/template.html +64 -0
  69. supervisely/app/widgets/select_team/select_team.py +37 -4
  70. supervisely/app/widgets/select_team/template.html +4 -5
  71. supervisely/app/widgets/select_user/__init__.py +0 -0
  72. supervisely/app/widgets/select_user/select_user.py +270 -0
  73. supervisely/app/widgets/select_user/template.html +13 -0
  74. supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
  75. supervisely/app/widgets/select_workspace/template.html +9 -12
  76. supervisely/app/widgets/table/table.py +68 -13
  77. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  78. supervisely/aug/aug.py +6 -2
  79. supervisely/convert/base_converter.py +1 -0
  80. supervisely/convert/converter.py +2 -2
  81. supervisely/convert/image/image_converter.py +3 -1
  82. supervisely/convert/image/image_helper.py +48 -4
  83. supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
  84. supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
  85. supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
  86. supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
  87. supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
  88. supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
  89. supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
  90. supervisely/convert/pointcloud/las/las_converter.py +13 -1
  91. supervisely/convert/pointcloud/las/las_helper.py +110 -11
  92. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
  93. supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
  94. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
  95. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
  96. supervisely/convert/video/__init__.py +1 -0
  97. supervisely/convert/video/multi_view/__init__.py +0 -0
  98. supervisely/convert/video/multi_view/multi_view.py +543 -0
  99. supervisely/convert/video/sly/sly_video_converter.py +359 -3
  100. supervisely/convert/video/video_converter.py +22 -2
  101. supervisely/convert/volume/dicom/dicom_converter.py +13 -5
  102. supervisely/convert/volume/dicom/dicom_helper.py +30 -18
  103. supervisely/geometry/constants.py +1 -0
  104. supervisely/geometry/geometry.py +4 -0
  105. supervisely/geometry/helpers.py +5 -1
  106. supervisely/geometry/oriented_bbox.py +676 -0
  107. supervisely/geometry/rectangle.py +2 -1
  108. supervisely/io/env.py +76 -1
  109. supervisely/io/fs.py +21 -0
  110. supervisely/nn/benchmark/base_evaluator.py +104 -11
  111. supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
  112. supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
  113. supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
  114. supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
  115. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
  116. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
  117. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
  118. supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
  119. supervisely/nn/inference/cache.py +43 -18
  120. supervisely/nn/inference/gui/serving_gui_template.py +5 -2
  121. supervisely/nn/inference/inference.py +795 -199
  122. supervisely/nn/inference/inference_request.py +42 -9
  123. supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
  124. supervisely/nn/inference/predict_app/gui/gui.py +676 -488
  125. supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
  126. supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
  127. supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
  128. supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
  129. supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
  130. supervisely/nn/inference/predict_app/gui/utils.py +236 -119
  131. supervisely/nn/inference/predict_app/predict_app.py +2 -2
  132. supervisely/nn/inference/session.py +43 -35
  133. supervisely/nn/inference/tracking/bbox_tracking.py +113 -34
  134. supervisely/nn/inference/tracking/tracker_interface.py +7 -2
  135. supervisely/nn/inference/uploader.py +139 -12
  136. supervisely/nn/live_training/__init__.py +7 -0
  137. supervisely/nn/live_training/api_server.py +111 -0
  138. supervisely/nn/live_training/artifacts_utils.py +243 -0
  139. supervisely/nn/live_training/checkpoint_utils.py +229 -0
  140. supervisely/nn/live_training/dynamic_sampler.py +44 -0
  141. supervisely/nn/live_training/helpers.py +14 -0
  142. supervisely/nn/live_training/incremental_dataset.py +146 -0
  143. supervisely/nn/live_training/live_training.py +497 -0
  144. supervisely/nn/live_training/loss_plateau_detector.py +111 -0
  145. supervisely/nn/live_training/request_queue.py +52 -0
  146. supervisely/nn/model/model_api.py +9 -0
  147. supervisely/nn/prediction_dto.py +12 -1
  148. supervisely/nn/tracker/base_tracker.py +11 -1
  149. supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
  150. supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
  151. supervisely/nn/tracker/botsort_tracker.py +94 -65
  152. supervisely/nn/tracker/visualize.py +87 -90
  153. supervisely/nn/training/gui/classes_selector.py +16 -1
  154. supervisely/nn/training/train_app.py +28 -29
  155. supervisely/project/data_version.py +115 -51
  156. supervisely/project/download.py +1 -1
  157. supervisely/project/pointcloud_episode_project.py +37 -8
  158. supervisely/project/pointcloud_project.py +30 -2
  159. supervisely/project/project.py +14 -2
  160. supervisely/project/project_meta.py +27 -1
  161. supervisely/project/project_settings.py +32 -18
  162. supervisely/project/versioning/__init__.py +1 -0
  163. supervisely/project/versioning/common.py +20 -0
  164. supervisely/project/versioning/schema_fields.py +35 -0
  165. supervisely/project/versioning/video_schema.py +221 -0
  166. supervisely/project/versioning/volume_schema.py +87 -0
  167. supervisely/project/video_project.py +717 -15
  168. supervisely/project/volume_project.py +623 -5
  169. supervisely/template/experiment/experiment.html.jinja +4 -4
  170. supervisely/template/experiment/experiment_generator.py +14 -21
  171. supervisely/template/live_training/__init__.py +0 -0
  172. supervisely/template/live_training/header.html.jinja +96 -0
  173. supervisely/template/live_training/live_training.html.jinja +51 -0
  174. supervisely/template/live_training/live_training_generator.py +464 -0
  175. supervisely/template/live_training/sly-style.css +402 -0
  176. supervisely/template/live_training/template.html.jinja +18 -0
  177. supervisely/versions.json +28 -26
  178. supervisely/video/sampling.py +39 -20
  179. supervisely/video/video.py +40 -11
  180. supervisely/video_annotation/video_object.py +29 -4
  181. supervisely/volume/stl_converter.py +2 -0
  182. supervisely/worker_api/agent_rpc.py +24 -1
  183. supervisely/worker_api/rpc_servicer.py +31 -7
  184. {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/METADATA +56 -39
  185. {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/RECORD +189 -142
  186. {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
  187. {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
  188. {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
  189. {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,543 @@
1
+ import os
2
+ from collections import defaultdict
3
+ from pathlib import Path
4
+ from typing import Dict, List, Union
5
+
6
+ from supervisely.api.api import Api
7
+ import supervisely.convert.video.sly.sly_video_helper as sly_video_helper
8
+ from supervisely import OpenMode, ProjectMeta, VideoAnnotation, VideoProject, logger
9
+ from supervisely.convert.base_converter import AvailableVideoConverters
10
+ from supervisely.convert.video.video_converter import VideoConverter
11
+ from supervisely.io.fs import JUNK_FILES, file_exists, get_file_ext
12
+ from supervisely.io.json import load_json_file
13
+ from supervisely.project.project import find_project_dirs
14
+ from supervisely.project.project_settings import LabelingInterface
15
+ from supervisely.video.video import has_valid_ext, validate_ext
16
+
17
+ DATASET_ITEMS = "items"
18
+ NESTED_DATASETS = "datasets"
19
+
20
+
21
+ class MultiViewVideoConverter(VideoConverter):
22
+ def __init__(self, *args, **kwargs):
23
+ super().__init__(*args, **kwargs)
24
+ self._supports_links = True
25
+ self._project_structure = None
26
+
27
+ def __str__(self) -> str:
28
+ return AvailableVideoConverters.MULTI_VIEW
29
+
30
+ @property
31
+ def ann_ext(self) -> str:
32
+ return ".json"
33
+
34
+ @property
35
+ def key_file_ext(self) -> str:
36
+ return ".json"
37
+
38
+ @staticmethod
39
+ def _create_project_node() -> Dict[str, dict]:
40
+ return {DATASET_ITEMS: [], NESTED_DATASETS: {}}
41
+
42
+ @classmethod
43
+ def _append_to_project_structure(
44
+ cls, project_structure: Dict[str, dict], dataset_name: str, items: list
45
+ ):
46
+ normalized_name = (dataset_name or "").replace("\\", "/").strip("/")
47
+ if not normalized_name:
48
+ normalized_name = dataset_name or "dataset"
49
+ parts = [part for part in normalized_name.split("/") if part]
50
+ if not parts:
51
+ parts = ["dataset"]
52
+
53
+ curr_ds = project_structure.setdefault(parts[0], cls._create_project_node())
54
+ for part in parts[1:]:
55
+ curr_ds = curr_ds[NESTED_DATASETS].setdefault(part, cls._create_project_node())
56
+ curr_ds[DATASET_ITEMS].extend(items)
57
+
58
+ def validate_labeling_interface(self) -> bool:
59
+ return self._labeling_interface == LabelingInterface.MULTIVIEW
60
+
61
+ def generate_meta_from_annotation(self, ann_path: str, meta: ProjectMeta) -> ProjectMeta:
62
+ meta = sly_video_helper.get_meta_from_annotation(ann_path, meta)
63
+ return meta
64
+
65
+ def validate_ann_file(self, ann_path: str, meta: ProjectMeta) -> bool:
66
+ try:
67
+ ann_json = load_json_file(ann_path)
68
+ if "annotation" in ann_json:
69
+ ann_json = ann_json["annotation"]
70
+ VideoAnnotation.from_json(ann_json, meta)
71
+ return True
72
+ except Exception:
73
+ return False
74
+
75
+ def validate_key_file(self, key_file_path: str) -> bool:
76
+ try:
77
+ self._meta = ProjectMeta.from_json(load_json_file(key_file_path))
78
+ return True
79
+ except Exception:
80
+ return False
81
+
82
+ def read_multiview_project(self, input_data: str) -> bool:
83
+ """Read multi-view video project with multiple datasets."""
84
+ try:
85
+ self._items = []
86
+ project = {}
87
+ ds_cnt = 0
88
+ self._meta = None
89
+
90
+ logger.debug("Trying to find Supervisely video project format in the input data")
91
+ project_dirs = [d for d in find_project_dirs(input_data, project_class=VideoProject)]
92
+ if len(project_dirs) > 1:
93
+ logger.info("Found multiple possible Supervisely video projects in the input data")
94
+ elif len(project_dirs) == 1:
95
+ logger.info("Possible Supervisely video project found in the input data")
96
+ else:
97
+ return False
98
+
99
+ meta = None
100
+ for project_dir in project_dirs:
101
+ project_fs = VideoProject(project_dir, mode=OpenMode.READ)
102
+ if meta is None:
103
+ meta = project_fs.meta
104
+ else:
105
+ meta = meta.merge(project_fs.meta)
106
+
107
+ for dataset in project_fs.datasets:
108
+ ds_items = []
109
+ for name in dataset.get_items_names():
110
+ video_path, ann_path = dataset.get_item_paths(name)
111
+ metadata_path = os.path.join(
112
+ dataset.directory, "metadata", f"{name}.meta.json"
113
+ )
114
+ item = self.Item(video_path)
115
+ if file_exists(ann_path):
116
+ if self.validate_ann_file(ann_path, meta):
117
+ item.ann_data = ann_path
118
+ if file_exists(metadata_path):
119
+ item.metadata = metadata_path
120
+ ds_items.append(item)
121
+
122
+ if len(ds_items) > 0:
123
+ self._append_to_project_structure(project, dataset.name, ds_items)
124
+ ds_cnt += 1
125
+ self._items.extend(ds_items)
126
+
127
+ if self.items_count > 0:
128
+ self._meta = meta
129
+ if ds_cnt > 1:
130
+ self._project_structure = project
131
+ return True
132
+ else:
133
+ return False
134
+ except Exception as e:
135
+ logger.debug(f"Not a multi-view video project: {repr(e)}")
136
+ return False
137
+
138
+ def read_multiview_dataset(self, input_data: str) -> bool:
139
+ """Read multi-view video datasets without project meta.json."""
140
+ try:
141
+ from supervisely import VideoDataset
142
+ from supervisely.io.fs import dirs_filter
143
+
144
+ self._items = []
145
+ project = {}
146
+ ds_cnt = 0
147
+ self._meta = None
148
+ logger.debug("Trying to read Supervisely video datasets")
149
+
150
+ def _check_function(path):
151
+ try:
152
+ dataset_ds = VideoDataset(path, OpenMode.READ)
153
+ return len(dataset_ds.get_items_names()) > 0
154
+ except:
155
+ return False
156
+
157
+ meta = ProjectMeta()
158
+ dataset_dirs = [d for d in dirs_filter(input_data, _check_function)]
159
+ for dataset_dir in dataset_dirs:
160
+ dataset_fs = VideoDataset(dataset_dir, OpenMode.READ)
161
+ ds_items = []
162
+ for name in dataset_fs.get_items_names():
163
+ video_path, ann_path = dataset_fs.get_item_paths(name)
164
+ metadata_path = os.path.join(
165
+ dataset_fs.directory, "metadata", f"{name}.meta.json"
166
+ )
167
+
168
+ item = self.Item(video_path)
169
+ if file_exists(ann_path):
170
+ meta = self.generate_meta_from_annotation(ann_path, meta)
171
+ if self.validate_ann_file(ann_path, meta):
172
+ item.ann_data = ann_path
173
+ if file_exists(metadata_path):
174
+ item.metadata = metadata_path
175
+ ds_items.append(item)
176
+
177
+ if len(ds_items) > 0:
178
+ self._append_to_project_structure(project, dataset_fs.name, ds_items)
179
+ ds_cnt += 1
180
+ self._items.extend(ds_items)
181
+
182
+ if self.items_count > 0:
183
+ self._meta = meta
184
+ if ds_cnt > 1: # multiple datasets
185
+ self._project_structure = project
186
+ return True
187
+ else:
188
+ return False
189
+ except Exception as e:
190
+ logger.debug(f"Failed to read Supervisely video datasets: {repr(e)}")
191
+ return False
192
+
193
+ def read_multiview_folder_structure(self, input_data: str) -> bool:
194
+ """Read multi-view folder layout: <dataset>/video (+optional ann, metadata)."""
195
+ try:
196
+ logger.debug("Trying to read folder-based multi-view structure")
197
+ self._items = []
198
+ project = {}
199
+ ds_cnt = 0
200
+ self._meta = None
201
+ self._project_structure = None
202
+
203
+ has_meta_file = False
204
+ for file in Path(input_data).rglob("meta.json"):
205
+ if file.is_file() and self.validate_key_file(str(file)):
206
+ has_meta_file = True
207
+ break
208
+ meta = self._meta if has_meta_file else ProjectMeta()
209
+
210
+ video_groups = self._find_video_groups(input_data)
211
+
212
+ for dataset_name, video_paths in video_groups.items():
213
+ ds_items = []
214
+ for path in video_paths:
215
+ item = self.Item(path)
216
+
217
+ # check both levels
218
+ possible_ann_dirs = [Path(path).parent.parent, Path(path).parent]
219
+
220
+ for possible_dir in possible_ann_dirs:
221
+ ann_path = possible_dir / "ann" / f"{item.name}.json"
222
+ if not ann_path.exists():
223
+ ann_path = possible_dir / f"{item.name}.json"
224
+ if ann_path.exists():
225
+ if not has_meta_file:
226
+ meta = self.generate_meta_from_annotation(str(ann_path), meta)
227
+ if self.validate_ann_file(str(ann_path), meta):
228
+ item.ann_data = str(ann_path)
229
+
230
+ item_meta = possible_dir / "metadata" / f"{item.name}.meta.json"
231
+ if not item_meta.exists():
232
+ item_meta = possible_dir / f"{item.name}.meta.json"
233
+ if item_meta.exists():
234
+ item.metadata = str(item_meta)
235
+ ds_items.append(item)
236
+
237
+ if len(ds_items) == 0:
238
+ continue
239
+
240
+ self._items.extend(ds_items)
241
+ ds_cnt += 1
242
+
243
+ self._append_to_project_structure(project, dataset_name, ds_items)
244
+
245
+ if self.items_count > 0:
246
+ self._meta = meta
247
+ if ds_cnt > 1:
248
+ self._project_structure = project
249
+ return True
250
+ else:
251
+ return False
252
+ except Exception as e:
253
+ logger.debug(f"Failed to read folder-based multi-view structure: {repr(e)}")
254
+ return False
255
+
256
+ def _find_video_groups(self, path) -> Dict[str, List[str]]:
257
+ video_groups = defaultdict(list)
258
+ for file_path in Path(path).rglob("*"):
259
+ if file_path.is_file() and has_valid_ext(str(file_path)):
260
+ video_groups[file_path.parent].append(str(file_path))
261
+
262
+ sanitized = self._sanitize_dataset_names(video_groups)
263
+ return {sanitized[parent]: files for parent, files in video_groups.items()}
264
+
265
+ def _sanitize_dataset_names(self, video_groups: Dict[Path, list]) -> Dict[Path, str]:
266
+ name_counts = defaultdict(int)
267
+ sanitized = {}
268
+ for parent in video_groups:
269
+ base_name = parent.name or "root"
270
+ name_counts[base_name] += 1
271
+ count = name_counts[base_name]
272
+ sanitized[parent] = base_name if count == 1 else f"{base_name}_{count}"
273
+ return sanitized
274
+
275
+ def validate_format(self) -> bool:
276
+ if self.upload_as_links and self._supports_links:
277
+ self._download_remote_ann_files()
278
+
279
+ if self.read_multiview_project(self._input_data):
280
+ return True
281
+
282
+ if self.read_multiview_dataset(self._input_data):
283
+ return True
284
+
285
+ if self.read_multiview_folder_structure(self._input_data):
286
+ return True
287
+
288
+ detected_ann_cnt = 0
289
+ videos_list, ann_dict, meta_dict = [], {}, {}
290
+ for root, _, files in os.walk(self._input_data):
291
+ for file in files:
292
+ full_path = os.path.join(root, file)
293
+ if file == "key_id_map.json":
294
+ continue
295
+ if file == "meta.json":
296
+ is_valid = self.validate_key_file(full_path)
297
+ if is_valid:
298
+ continue
299
+
300
+ ext = get_file_ext(full_path)
301
+ if file in JUNK_FILES:
302
+ continue
303
+
304
+ elif file.endswith(".meta.json"):
305
+ meta_dict[file] = full_path
306
+ elif ext in self.ann_ext:
307
+ ann_dict[file] = full_path
308
+ else:
309
+ try:
310
+ validate_ext(ext) # validate video extension
311
+ videos_list.append(full_path)
312
+ except Exception:
313
+ continue
314
+
315
+ if self._meta is not None:
316
+ meta = self._meta
317
+ else:
318
+ meta = ProjectMeta()
319
+
320
+ self._items = []
321
+ for video_path in videos_list:
322
+ item = self.Item(video_path)
323
+ ann_name = f"{item.name}.json"
324
+ if ann_name in ann_dict:
325
+ ann_path = ann_dict[ann_name]
326
+ if self._meta is None:
327
+ meta = self.generate_meta_from_annotation(ann_path, meta)
328
+ is_valid = self.validate_ann_file(ann_path, meta)
329
+ if is_valid:
330
+ item.ann_data = ann_path
331
+ detected_ann_cnt += 1
332
+
333
+ meta_name = f"{item.name}.meta.json"
334
+ if meta_name in meta_dict:
335
+ meta_path = meta_dict[meta_name]
336
+ item.metadata = meta_path
337
+
338
+ self._items.append(item)
339
+ self._meta = meta
340
+ return len(self._items) > 0
341
+
342
+ def upload_dataset(self, api, dataset_id: int, batch_size: int = 10, log_progress=True):
343
+ """Upload converted data to Supervisely."""
344
+ if self._project_structure:
345
+ self._upload_project(api, dataset_id, batch_size, log_progress)
346
+ else:
347
+ self._upload_single_dataset(api, dataset_id, self._items, batch_size, log_progress)
348
+
349
+ def _upload_project(self, api, dataset_id: int, batch_size: int = 10, log_progress=True):
350
+ """Upload multi-view video project with multiple datasets."""
351
+ from supervisely import generate_free_name, is_development
352
+
353
+ dataset_info = api.dataset.get_info_by_id(dataset_id, raise_error=True)
354
+ project_id = dataset_info.project_id
355
+ existing_datasets = api.dataset.get_list(project_id, recursive=True)
356
+ existing_datasets = {ds.name for ds in existing_datasets}
357
+
358
+ if log_progress:
359
+ progress, progress_cb = self.get_progress(self.items_count, "Uploading project")
360
+ else:
361
+ progress, progress_cb = None, None
362
+
363
+ logger.info("Uploading multi-view video project structure")
364
+
365
+ def _upload_datasets_recursive(
366
+ project_structure: dict,
367
+ project_id: int,
368
+ dataset_id: int,
369
+ parent_id=None,
370
+ first_dataset=False,
371
+ ):
372
+ for ds_name, value in project_structure.items():
373
+ ds_name = generate_free_name(existing_datasets, ds_name, extend_used_names=True)
374
+ if first_dataset:
375
+ first_dataset = False
376
+ api.dataset.update(dataset_id, ds_name) # rename first dataset
377
+ else:
378
+ dataset_id = api.dataset.create(project_id, ds_name, parent_id=parent_id).id
379
+
380
+ items = value.get(DATASET_ITEMS, [])
381
+ nested_datasets = value.get(NESTED_DATASETS, {})
382
+ logger.info(
383
+ f"Dataset: {ds_name}, items: {len(items)}, nested datasets: {len(nested_datasets)}"
384
+ )
385
+ if items:
386
+ self._upload_single_dataset(
387
+ api,
388
+ dataset_id,
389
+ items,
390
+ batch_size,
391
+ log_progress=False,
392
+ progress_cb=progress_cb,
393
+ )
394
+
395
+ if nested_datasets:
396
+ _upload_datasets_recursive(nested_datasets, project_id, dataset_id, dataset_id)
397
+
398
+ _upload_datasets_recursive(
399
+ self._project_structure, project_id, dataset_id, first_dataset=True
400
+ )
401
+
402
+ if is_development() and progress is not None:
403
+ progress.close()
404
+
405
+ def _upload_single_dataset(
406
+ self,
407
+ api: Api,
408
+ dataset_id: int,
409
+ items: list,
410
+ batch_size: int = 10,
411
+ log_progress=True,
412
+ progress_cb=None,
413
+ ):
414
+ """Upload videos from a single dataset."""
415
+ from supervisely import batched, generate_free_name, is_development
416
+ from supervisely.io.fs import get_file_size
417
+ from supervisely.io.json import load_json_file
418
+
419
+ meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
420
+ videos_in_dataset = api.video.get_list(dataset_id, force_metadata_for_links=False)
421
+ existing_names = {video_info.name for video_info in videos_in_dataset}
422
+ items_count = len(items)
423
+ convert_progress, convert_progress_cb = self.get_progress(
424
+ items_count, "Preparing videos..."
425
+ )
426
+ for item in items:
427
+ item_name, item_path = self.convert_to_mp4_if_needed(item.path)
428
+ item.name = item_name
429
+ item.path = item_path
430
+ convert_progress_cb(1)
431
+ if is_development():
432
+ convert_progress.close()
433
+
434
+ has_large_files = False
435
+ size_progress_cb = None
436
+ _progress_cb, progress, ann_progress, ann_progress_cb = None, None, None, None
437
+ if log_progress:
438
+ if progress_cb is None:
439
+ progress, _progress_cb = self.get_progress(items_count, "Uploading videos...")
440
+ else:
441
+ _progress_cb = progress_cb
442
+ if not self.upload_as_links:
443
+ file_sizes = [get_file_size(item.path) for item in items]
444
+ has_large_files = any(
445
+ [self._check_video_file_size(file_size) for file_size in file_sizes]
446
+ )
447
+ if has_large_files:
448
+ upload_progress = []
449
+ size_progress_cb = self._get_video_upload_progress(upload_progress)
450
+
451
+ batch_size = 1 if has_large_files and not self.upload_as_links else batch_size
452
+ for batch in batched(items, batch_size=batch_size):
453
+ item_names = []
454
+ item_paths = []
455
+ item_metas = []
456
+ anns = []
457
+ figures_cnt = 0
458
+ for item in batch:
459
+ item.name = generate_free_name(
460
+ existing_names, item.name, with_ext=True, extend_used_names=True
461
+ )
462
+ item_paths.append(item.path)
463
+ item_names.append(item.name)
464
+
465
+ if isinstance(item.metadata, str): # path to file
466
+ item_metas.append(load_json_file(item.metadata))
467
+ elif isinstance(item.metadata, dict):
468
+ item_metas.append(item.metadata)
469
+ else:
470
+ item_metas.append({})
471
+
472
+ ann = None
473
+ if not self.upload_as_links or self.supports_links:
474
+ ann = self.to_supervisely(item, meta, renamed_classes, renamed_tags)
475
+ if ann is not None:
476
+ figures_cnt += len(ann.figures)
477
+ anns.append(ann)
478
+
479
+ if self.upload_as_links:
480
+ vid_infos = api.video.upload_links(
481
+ dataset_id,
482
+ item_paths,
483
+ item_names,
484
+ metas=item_metas,
485
+ skip_download=True,
486
+ progress_cb=_progress_cb if log_progress else None,
487
+ force_metadata_for_links=False,
488
+ )
489
+ else:
490
+ vid_infos = api.video.upload_paths(
491
+ dataset_id,
492
+ item_names,
493
+ item_paths,
494
+ metas=item_metas,
495
+ progress_cb=_progress_cb if log_progress else None,
496
+ item_progress=(size_progress_cb if log_progress and has_large_files else None),
497
+ )
498
+
499
+ vid_ids = [vid_info.id for vid_info in vid_infos]
500
+ if log_progress and has_large_files and figures_cnt > 0:
501
+ ann_progress, ann_progress_cb = self.get_progress(
502
+ figures_cnt, "Uploading annotations..."
503
+ )
504
+
505
+ for idx, (ann, info) in enumerate(zip(anns, vid_infos)):
506
+ if ann is None:
507
+ anns[idx] = VideoAnnotation(
508
+ (info.frame_height, info.frame_width), info.frames_count
509
+ )
510
+ api.video.annotation.upload_anns_multiview(vid_ids, anns, ann_progress_cb)
511
+
512
+ if log_progress and is_development():
513
+ if progress is not None:
514
+ progress.close()
515
+ if ann_progress is not None:
516
+ ann_progress.close()
517
+ logger.info(f"Dataset ID:{dataset_id} has been successfully uploaded.")
518
+
519
+ def to_supervisely(
520
+ self,
521
+ item: VideoConverter.Item,
522
+ meta: ProjectMeta = None,
523
+ renamed_classes: dict = None,
524
+ renamed_tags: dict = None,
525
+ ) -> VideoAnnotation:
526
+ if meta is None:
527
+ meta = self._meta
528
+
529
+ if item.ann_data is None:
530
+ if self._upload_as_links:
531
+ return None
532
+ return item.create_empty_annotation()
533
+
534
+ try:
535
+ ann_json = load_json_file(item.ann_data)
536
+ if "annotation" in ann_json:
537
+ ann_json = ann_json["annotation"]
538
+ if renamed_classes or renamed_tags:
539
+ ann_json = sly_video_helper.rename_in_json(ann_json, renamed_classes, renamed_tags)
540
+ return VideoAnnotation.from_json(ann_json, meta)
541
+ except Exception as e:
542
+ logger.warning(f"Failed to convert annotation: {repr(e)}")
543
+ return item.create_empty_annotation()