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
@@ -1,20 +1,29 @@
1
1
  import os
2
- from typing import List
2
+ from typing import Dict, List, Optional
3
3
 
4
4
  import supervisely.convert.video.sly.sly_video_helper as sly_video_helper
5
- from supervisely import KeyIdMap, ProjectMeta, VideoAnnotation, logger
5
+ from supervisely import ProjectMeta, VideoAnnotation, logger
6
+ from supervisely.api.api import Api, ApiContext
6
7
  from supervisely.convert.base_converter import AvailableVideoConverters
7
8
  from supervisely.convert.video.video_converter import VideoConverter
8
- from supervisely.io.fs import JUNK_FILES, get_file_ext
9
+ from supervisely.io.fs import JUNK_FILES, file_exists, get_file_ext
9
10
  from supervisely.io.json import load_json_file
11
+ from supervisely.project.project import OpenMode, find_project_dirs
12
+ from supervisely.project.project_settings import LabelingInterface
13
+ from supervisely.project.video_project import VideoProject
10
14
  from supervisely.video.video import validate_ext as validate_video_ext
11
15
 
16
+ DATASET_ITEMS = "items"
17
+ NESTED_DATASETS = "datasets"
18
+
12
19
 
13
20
  class SLYVideoConverter(VideoConverter):
14
21
 
15
22
  def __init__(self, *args, **kwargs):
16
23
  super().__init__(*args, **kwargs)
17
24
  self._supports_links = True
25
+ self._project_structure = None
26
+ self._multi_view_setting_enabled = False
18
27
 
19
28
  def __str__(self) -> str:
20
29
  return AvailableVideoConverters.SLY
@@ -48,9 +57,34 @@ class SLYVideoConverter(VideoConverter):
48
57
  except Exception:
49
58
  return False
50
59
 
60
+ @staticmethod
61
+ def _create_project_node() -> Dict[str, dict]:
62
+ return {DATASET_ITEMS: [], NESTED_DATASETS: {}}
63
+
64
+ @classmethod
65
+ def _append_to_project_structure(
66
+ cls, project_structure: Dict[str, dict], dataset_name: str, items: List
67
+ ):
68
+ normalized_name = (dataset_name or "").replace("\\", "/").strip("/")
69
+ if not normalized_name:
70
+ normalized_name = dataset_name or "dataset"
71
+ parts = [part for part in normalized_name.split("/") if part]
72
+ if not parts:
73
+ parts = ["dataset"]
74
+
75
+ curr_ds = project_structure.setdefault(parts[0], cls._create_project_node())
76
+ for part in parts[1:]:
77
+ curr_ds = curr_ds[NESTED_DATASETS].setdefault(part, cls._create_project_node())
78
+ curr_ds[DATASET_ITEMS].extend(items)
79
+
51
80
  def validate_format(self) -> bool:
52
81
  if self.upload_as_links and self._supports_links:
53
82
  self._download_remote_ann_files()
83
+ if self.read_project_structure(self._input_data):
84
+ return True
85
+
86
+ if self.read_dataset_structure(self._input_data):
87
+ return True
54
88
  detected_ann_cnt = 0
55
89
  videos_list, ann_dict = [], {}
56
90
  for root, _, files in os.walk(self._input_data):
@@ -123,3 +157,325 @@ class SLYVideoConverter(VideoConverter):
123
157
  except Exception as e:
124
158
  logger.warning(f"Failed to convert annotation: {repr(e)}")
125
159
  return item.create_empty_annotation()
160
+
161
+ def read_project_structure(self, input_data: str) -> bool:
162
+ """Read video project with multiple datasets."""
163
+ try:
164
+ self._items = []
165
+ project = {}
166
+ ds_cnt = 0
167
+ self._meta = None
168
+
169
+ logger.debug("Trying to find Supervisely video project format in the input data")
170
+ project_dirs = [d for d in find_project_dirs(input_data, project_class=VideoProject)]
171
+ if len(project_dirs) > 1:
172
+ logger.info("Found multiple possible Supervisely video projects in the input data")
173
+ elif len(project_dirs) == 1:
174
+ logger.info("Possible Supervisely video project found in the input data")
175
+ else:
176
+ return False
177
+
178
+ meta = None
179
+ for project_dir in project_dirs:
180
+ project_fs = VideoProject(project_dir, mode=OpenMode.READ)
181
+ if meta is None:
182
+ meta = project_fs.meta
183
+ else:
184
+ meta = meta.merge(project_fs.meta)
185
+
186
+ for dataset in project_fs.datasets:
187
+ ds_items = []
188
+ for name in dataset.get_items_names():
189
+ video_path, ann_path = dataset.get_item_paths(name)
190
+ item = self.Item(video_path)
191
+ if file_exists(ann_path):
192
+ if self.validate_ann_file(ann_path, meta):
193
+ item.ann_data = ann_path
194
+ ds_items.append(item)
195
+
196
+ if len(ds_items) > 0:
197
+ self._append_to_project_structure(project, dataset.name, ds_items)
198
+ ds_cnt += 1
199
+ self._items.extend(ds_items)
200
+
201
+ if self.items_count > 0:
202
+ self._meta = meta
203
+ meta: ProjectMeta
204
+ if meta.labeling_interface == LabelingInterface.MULTIVIEW:
205
+ self._multi_view_setting_enabled = True
206
+ if ds_cnt > 1:
207
+ self._project_structure = project
208
+ return True
209
+ else:
210
+ return False
211
+ except Exception as e:
212
+ logger.debug(f"Not a video project: {repr(e)}")
213
+ return False
214
+
215
+ def read_dataset_structure(self, input_data: str) -> bool:
216
+ """Read video datasets without project meta.json."""
217
+ try:
218
+ from supervisely import VideoDataset
219
+ from supervisely.io.fs import dirs_filter
220
+
221
+ self._items = []
222
+ project = {}
223
+ ds_cnt = 0
224
+ self._meta = None
225
+ logger.debug("Trying to read Supervisely video datasets")
226
+
227
+ def _check_function(path):
228
+ try:
229
+ dataset_ds = VideoDataset(path, OpenMode.READ)
230
+ return len(dataset_ds.get_items_names()) > 0
231
+ except Exception:
232
+ return False
233
+
234
+ meta = ProjectMeta()
235
+ dataset_dirs = [d for d in dirs_filter(input_data, _check_function)]
236
+ for dataset_dir in dataset_dirs:
237
+ dataset_fs = VideoDataset(dataset_dir, OpenMode.READ)
238
+ ds_items = []
239
+ for name in dataset_fs.get_items_names():
240
+ video_path, ann_path = dataset_fs.get_item_paths(name)
241
+ item = self.Item(video_path)
242
+ if file_exists(ann_path):
243
+ meta = self.generate_meta_from_annotation(ann_path, meta)
244
+ if self.validate_ann_file(ann_path, meta):
245
+ item.ann_data = ann_path
246
+ ds_items.append(item)
247
+
248
+ if len(ds_items) > 0:
249
+ self._append_to_project_structure(project, dataset_fs.name, ds_items)
250
+ ds_cnt += 1
251
+ self._items.extend(ds_items)
252
+
253
+ if self.items_count > 0:
254
+ self._meta = meta
255
+ if ds_cnt > 1: # multiple datasets
256
+ self._project_structure = project
257
+ return True
258
+ else:
259
+ return False
260
+ except Exception as e:
261
+ logger.debug(f"Failed to read Supervisely video datasets: {repr(e)}")
262
+ return False
263
+
264
+ def upload_dataset(
265
+ self, api: Api, dataset_id: int, batch_size: int = 10, log_progress=True
266
+ ) -> Optional[int]:
267
+ """Upload converted data to Supervisely."""
268
+ if self._project_structure:
269
+ return self._upload_project(api, dataset_id, batch_size, log_progress)
270
+ else:
271
+ self._upload_single_dataset(api, dataset_id, self._items, batch_size, log_progress)
272
+
273
+ def _upload_project(
274
+ self, api: Api, dataset_id: int, batch_size: int = 10, log_progress=True
275
+ ) -> Optional[int]:
276
+ """Upload video project with multiple datasets."""
277
+ from supervisely import generate_free_name, is_development
278
+
279
+ dataset_info = api.dataset.get_info_by_id(dataset_id, raise_error=True)
280
+ project_id = dataset_info.project_id
281
+ new_project_created = False
282
+
283
+ if self._multi_view_setting_enabled:
284
+ src_meta_json = api.project.get_meta(project_id, with_settings=True)
285
+ src_meta = ProjectMeta.from_json(src_meta_json)
286
+
287
+ if src_meta.labeling_interface == LabelingInterface.DEFAULT:
288
+ project_id, dataset_id = self._handle_multi_view_labeling_interface(
289
+ api, project_id, dataset_info
290
+ )
291
+ new_project_created = True
292
+
293
+ existing_datasets = api.dataset.get_list(project_id, recursive=True)
294
+ existing_datasets = {ds.name for ds in existing_datasets}
295
+
296
+ if log_progress:
297
+ progress, progress_cb = self.get_progress(self.items_count, "Uploading project")
298
+ else:
299
+ progress, progress_cb = None, None
300
+
301
+ logger.info("Uploading video project structure")
302
+
303
+ def _upload_datasets_recursive(
304
+ project_structure: dict,
305
+ project_id: int,
306
+ dataset_id: int,
307
+ parent_id=None,
308
+ first_dataset=False,
309
+ ):
310
+ for ds_name, value in project_structure.items():
311
+ ds_name = generate_free_name(existing_datasets, ds_name, extend_used_names=True)
312
+ if first_dataset:
313
+ first_dataset = False
314
+ api.dataset.update(dataset_id, ds_name) # rename first dataset
315
+ else:
316
+ dataset_id = api.dataset.create(project_id, ds_name, parent_id=parent_id).id
317
+
318
+ items = value.get(DATASET_ITEMS, [])
319
+ nested_datasets = value.get(NESTED_DATASETS, {})
320
+ logger.info(
321
+ f"Dataset: {ds_name}, items: {len(items)}, nested datasets: {len(nested_datasets)}"
322
+ )
323
+ if items:
324
+ self._upload_single_dataset(
325
+ api,
326
+ dataset_id,
327
+ items,
328
+ batch_size,
329
+ log_progress=False,
330
+ progress_cb=progress_cb,
331
+ )
332
+
333
+ if nested_datasets:
334
+ _upload_datasets_recursive(nested_datasets, project_id, dataset_id, dataset_id)
335
+
336
+ _upload_datasets_recursive(
337
+ self._project_structure, project_id, dataset_id, first_dataset=True
338
+ )
339
+
340
+ if is_development() and progress is not None:
341
+ progress.close()
342
+
343
+ if new_project_created:
344
+ logger.info(
345
+ "Data was uploaded to a new project with 'Multi-View' labeling interface setting."
346
+ )
347
+ return dataset_id
348
+
349
+ def _upload_single_dataset(
350
+ self,
351
+ api: Api,
352
+ dataset_id: int,
353
+ items: List,
354
+ batch_size: int = 10,
355
+ log_progress=True,
356
+ progress_cb=None,
357
+ ):
358
+ """Upload videos from a single dataset."""
359
+ from supervisely import batched, generate_free_name, is_development
360
+ from supervisely.io.fs import get_file_size
361
+
362
+ meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
363
+ videos_in_dataset = api.video.get_list(dataset_id, force_metadata_for_links=False)
364
+ existing_names = {video_info.name for video_info in videos_in_dataset}
365
+ items_count = len(items)
366
+ convert_progress, convert_progress_cb = self.get_progress(
367
+ items_count, "Preparing videos..."
368
+ )
369
+ for item in items:
370
+ item_name, item_path = self.convert_to_mp4_if_needed(item.path)
371
+ item.name = item_name
372
+ item.path = item_path
373
+ convert_progress_cb(1)
374
+ if is_development():
375
+ convert_progress.close()
376
+
377
+ has_large_files = False
378
+ size_progress_cb = None
379
+ _progress_cb, progress, ann_progress, ann_progress_cb = None, None, None, None
380
+ if log_progress:
381
+ if progress_cb is None:
382
+ progress, _progress_cb = self.get_progress(items_count, "Uploading videos...")
383
+ else:
384
+ _progress_cb = progress_cb
385
+ if not self.upload_as_links:
386
+ file_sizes = [get_file_size(item.path) for item in items]
387
+ has_large_files = any(
388
+ [self._check_video_file_size(file_size) for file_size in file_sizes]
389
+ )
390
+ if has_large_files:
391
+ upload_progress = []
392
+ size_progress_cb = self._get_video_upload_progress(upload_progress)
393
+
394
+ with ApiContext(api=api, project_meta=meta):
395
+ batch_size = 1 if has_large_files and not self.upload_as_links else batch_size
396
+ for batch in batched(items, batch_size=batch_size):
397
+ item_names = []
398
+ item_paths = []
399
+ anns = []
400
+ figures_cnt = 0
401
+ for item in batch:
402
+ item.name = generate_free_name(
403
+ existing_names, item.name, with_ext=True, extend_used_names=True
404
+ )
405
+ item_paths.append(item.path)
406
+ item_names.append(item.name)
407
+
408
+ ann = None
409
+ if not self.upload_as_links or self.supports_links:
410
+ ann = self.to_supervisely(item, meta, renamed_classes, renamed_tags)
411
+ if ann is not None:
412
+ figures_cnt += len(ann.figures)
413
+ anns.append(ann)
414
+
415
+ if self.upload_as_links:
416
+ vid_infos = api.video.upload_links(
417
+ dataset_id,
418
+ item_paths,
419
+ item_names,
420
+ skip_download=True,
421
+ progress_cb=_progress_cb if log_progress else None,
422
+ force_metadata_for_links=False,
423
+ )
424
+ else:
425
+ vid_infos = api.video.upload_paths(
426
+ dataset_id,
427
+ item_names,
428
+ item_paths,
429
+ progress_cb=_progress_cb if log_progress else None,
430
+ item_progress=(
431
+ size_progress_cb if log_progress and has_large_files else None
432
+ ),
433
+ )
434
+
435
+ vid_ids = [vid_info.id for vid_info in vid_infos]
436
+ if log_progress and has_large_files and figures_cnt > 0:
437
+ ann_progress, ann_progress_cb = self.get_progress(
438
+ figures_cnt, "Uploading annotations..."
439
+ )
440
+
441
+ if meta.labeling_interface == LabelingInterface.MULTIVIEW:
442
+ for idx, (ann, info) in enumerate(zip(anns, vid_infos)):
443
+ if ann is None:
444
+ anns[idx] = VideoAnnotation(
445
+ (info.frame_height, info.frame_width), info.frames_count
446
+ )
447
+ api.video.annotation.upload_anns_multiview(vid_ids, anns, ann_progress_cb)
448
+ else:
449
+ for vid, ann, info in zip(vid_ids, anns, vid_infos):
450
+ if ann is None:
451
+ ann = VideoAnnotation(
452
+ (info.frame_height, info.frame_width), info.frames_count
453
+ )
454
+ api.video.annotation.append(vid, ann, progress_cb=ann_progress_cb)
455
+
456
+ if log_progress and is_development():
457
+ if progress is not None:
458
+ progress.close()
459
+ if ann_progress is not None:
460
+ ann_progress.close()
461
+ logger.info(f"Dataset ID:{dataset_id} has been successfully uploaded.")
462
+
463
+ def _handle_multi_view_labeling_interface(self, api: Api, project_id: int, dataset_info):
464
+ project_info = api.project.get_info_by_id(project_id)
465
+ if project_info.items_count == 0:
466
+ return project_id, dataset_info.id
467
+ logger.warning(
468
+ "The uploaded project has 'Multi-View' labeling interface setting enabled, "
469
+ "but the target project has 'Default' labeling interface. "
470
+ )
471
+ logger.warning("New project with 'Multi-View' labeling interface will be created.")
472
+ new_project = api.project.create(
473
+ workspace_id=project_info.workspace_id,
474
+ name=f"{project_info.name}_multi_view",
475
+ type=project_info.type,
476
+ change_name_if_conflict=True,
477
+ )
478
+ new_dataset = api.dataset.create(
479
+ new_project.id, dataset_info.name, change_name_if_conflict=True
480
+ )
481
+ return new_project.id, new_dataset.id
@@ -40,10 +40,12 @@ class VideoConverter(BaseConverter):
40
40
  shape=None,
41
41
  custom_data=None,
42
42
  frame_count=None,
43
+ metadata=None,
43
44
  ):
44
45
  self._path = item_path
45
46
  self._name: str = None
46
47
  self._ann_data = ann_data
48
+ self._metadata = metadata
47
49
  self._type = "video"
48
50
  if shape is None:
49
51
  vcap = cv2.VideoCapture(item_path)
@@ -83,6 +85,14 @@ class VideoConverter(BaseConverter):
83
85
  def name(self, name: str):
84
86
  self._name = name
85
87
 
88
+ @property
89
+ def metadata(self) -> Optional[str]:
90
+ return self._metadata
91
+
92
+ @metadata.setter
93
+ def metadata(self, metadata: Optional[str]):
94
+ self._metadata = metadata
95
+
86
96
  def create_empty_annotation(self) -> VideoAnnotation:
87
97
  return VideoAnnotation(self._shape, self._frame_count)
88
98
 
@@ -120,9 +130,7 @@ class VideoConverter(BaseConverter):
120
130
  log_progress=True,
121
131
  ):
122
132
  """Upload converted data to Supervisely"""
123
-
124
133
  meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
125
-
126
134
  videos_in_dataset = api.video.get_list(dataset_id, force_metadata_for_links=False)
127
135
  existing_names = {video_info.name for video_info in videos_in_dataset}
128
136
 
@@ -156,6 +164,7 @@ class VideoConverter(BaseConverter):
156
164
  for batch in batched(self._items, batch_size=batch_size):
157
165
  item_names = []
158
166
  item_paths = []
167
+ item_metas = []
159
168
  anns = []
160
169
  figures_cnt = 0
161
170
  for item in batch:
@@ -165,6 +174,15 @@ class VideoConverter(BaseConverter):
165
174
  item_paths.append(item.path)
166
175
  item_names.append(item.name)
167
176
 
177
+ if isinstance(item.metadata, str): # path to file
178
+ from supervisely.io.json import load_json_file
179
+
180
+ item_metas.append(load_json_file(item.metadata))
181
+ elif isinstance(item.metadata, dict):
182
+ item_metas.append(item.metadata)
183
+ else:
184
+ item_metas.append({})
185
+
168
186
  ann = None
169
187
  if not self.upload_as_links or self.supports_links:
170
188
  ann = self.to_supervisely(item, meta, renamed_classes, renamed_tags)
@@ -177,6 +195,7 @@ class VideoConverter(BaseConverter):
177
195
  dataset_id,
178
196
  item_paths,
179
197
  item_names,
198
+ metas=item_metas,
180
199
  skip_download=True,
181
200
  progress_cb=progress_cb if log_progress else None,
182
201
  force_metadata_for_links=False,
@@ -188,6 +207,7 @@ class VideoConverter(BaseConverter):
188
207
  item_paths,
189
208
  progress_cb=progress_cb if log_progress else None,
190
209
  item_progress=(size_progress_cb if log_progress and has_large_files else None),
210
+ metas=item_metas,
191
211
  )
192
212
  vid_ids = [vid_info.id for vid_info in vid_infos]
193
213
 
@@ -1,15 +1,21 @@
1
1
  from typing import List
2
2
 
3
+ from supervisely import ProjectMeta, generate_free_name, logger
3
4
  from supervisely.api.api import Api
4
- from supervisely import generate_free_name, logger, ProjectMeta
5
5
  from supervisely.convert.base_converter import AvailableVolumeConverters
6
- from supervisely.convert.volume.volume_converter import VolumeConverter
7
6
  from supervisely.convert.volume.dicom import dicom_helper as h
8
- from supervisely.volume.volume import inspect_dicom_series, get_extension, read_dicom_serie_volume
7
+ from supervisely.convert.volume.volume_converter import VolumeConverter
8
+ from supervisely.volume.volume import (
9
+ get_extension,
10
+ inspect_dicom_series,
11
+ read_dicom_serie_volume,
12
+ )
13
+
9
14
 
10
15
  class DICOMConverter(VolumeConverter):
11
16
  class Item(VolumeConverter.Item):
12
17
  """Item class for DICOM series."""
18
+
13
19
  def __init__(self, serie_id: str, item_paths: List[str], volume_meta: dict):
14
20
  item_path = item_paths[0] if len(item_paths) > 0 else None
15
21
  super().__init__(item_path, volume_meta=volume_meta)
@@ -62,9 +68,11 @@ class DICOMConverter(VolumeConverter):
62
68
  continue
63
69
 
64
70
  for dicom_path in dicom_paths:
65
- h.convert_to_monochrome2(dicom_path)
71
+ h.read_and_convert_to_monochrome2(dicom_path)
66
72
  _, meta = read_dicom_serie_volume(dicom_paths, anonymize=True)
67
- item = self.Item(serie_id=dicom_id, item_paths=dicom_paths, volume_meta=meta)
73
+ item = self.Item(
74
+ serie_id=dicom_id, item_paths=dicom_paths, volume_meta=meta
75
+ )
68
76
  self._items.append(item)
69
77
  self._meta = ProjectMeta()
70
78
 
@@ -3,7 +3,7 @@ from typing import List
3
3
 
4
4
  import nrrd
5
5
  import numpy as np
6
-
6
+ from pydicom import FileDataset
7
7
  from supervisely import logger
8
8
  from supervisely.io.fs import file_exists
9
9
  from supervisely.volume.volume import read_dicom_serie_volume_np
@@ -38,31 +38,43 @@ def dcm_to_nrrd(id: str, paths: List[str]) -> str:
38
38
 
39
39
  return nrrd_path, volume_meta
40
40
 
41
- def convert_to_monochrome2(dcm_path: str):
42
- import pydicom
43
41
 
44
- is_modified = False
42
+ def read_and_convert_to_monochrome2(dcm_path: str):
43
+ import pydicom
45
44
 
46
45
  try:
47
46
  dcm = pydicom.dcmread(dcm_path)
48
47
  except Exception as e:
49
- logger.warn("Failed to read DICOM file: " + str(e))
48
+ logger.warning("Failed to read DICOM file: " + str(e))
50
49
  return
51
50
 
52
51
  try:
53
52
  if dcm.file_meta.TransferSyntaxUID.is_compressed:
54
53
  dcm.decompress()
55
- is_modified = True
56
54
  except Exception as e:
57
- logger.warn("Failed to decompress DICOM file: " + str(e))
55
+ logger.warning("Failed to decompress DICOM file: " + str(e))
58
56
  return
59
57
 
58
+ convert_to_monochrome2(dcm_path, dcm)
59
+
60
+
61
+ def convert_to_monochrome2(dcm_path: str, dcm: FileDataset) -> FileDataset:
60
62
  if getattr(dcm, "PhotometricInterpretation", None) == "YBR_FULL_422":
61
63
  # * Convert dicom to monochrome
62
- if len(dcm.pixel_array.shape) == 4 and dcm.pixel_array.shape[-1] == 3:
63
- monochrome = dcm.pixel_array[..., 0].astype(np.uint8)
64
+ monochrome = None
65
+ pixel_array = dcm.pixel_array
66
+
67
+ if len(pixel_array.shape) == 4 and pixel_array.shape[-1] == 3:
68
+ monochrome = pixel_array[..., 0].astype(np.uint8)
69
+ elif len(pixel_array.shape) == 3 and pixel_array.shape[-1] == 3:
70
+ monochrome = pixel_array[..., 0].astype(np.uint8)
64
71
  else:
65
- logger.warn("Unexpected shape for YBR_FULL_422 data: " + str(dcm.pixel_array.shape))
72
+ logger.warning(
73
+ "Unexpected shape for YBR_FULL_422 data: " + str(pixel_array.shape)
74
+ )
75
+ return dcm
76
+
77
+ logger.debug("Monochrome shape: " + str(monochrome.shape))
66
78
 
67
79
  try:
68
80
  dcm.SamplesPerPixel = 1
@@ -71,15 +83,15 @@ def convert_to_monochrome2(dcm_path: str):
71
83
  if len(monochrome.shape) == 3:
72
84
  dcm.NumberOfFrames = str(monochrome.shape[0])
73
85
  dcm.Rows, dcm.Columns = monochrome.shape[1:3]
86
+ elif len(monochrome.shape) == 2:
87
+ dcm.Rows, dcm.Columns = monochrome.shape[0:2]
88
+ if hasattr(dcm, "NumberOfFrames"):
89
+ delattr(dcm, "NumberOfFrames")
74
90
  dcm.PixelData = monochrome.tobytes()
75
91
  except AttributeError as ae:
76
92
  logger.error(f"Error occurred while converting dicom to monochrome: {ae}")
93
+ return dcm
77
94
 
78
- logger.info("Rewriting DICOM file with MONOCHROME2 photometric interpretation.")
79
- is_modified = True
80
-
81
- try:
82
- if is_modified:
83
- dcm.save_as(dcm_path)
84
- except Exception as e:
85
- logger.warn("Failed to save DICOM file: " + str(e))
95
+ logger.info("Rewriting DICOM file with monochrome2 format")
96
+ dcm.save_as(dcm_path)
97
+ return dcm
@@ -15,6 +15,7 @@ SPACE_ORIGIN = "space_origin"
15
15
  SPACE = "space"
16
16
  SPACE_DIRECTIONS = "space_directions"
17
17
  POINTS = "points"
18
+ ANGLE = "angle"
18
19
  ROWS = "rows"
19
20
  TYPE = "type"
20
21
  GEOMETRY_SHAPE = "shape"
@@ -272,6 +272,7 @@ class Geometry(JsonSerializable):
272
272
  )
273
273
  from supervisely.geometry.polygon import Polygon
274
274
  from supervisely.geometry.rectangle import Rectangle
275
+ from supervisely.geometry.oriented_bbox import OrientedBBox
275
276
 
276
277
  res = []
277
278
  if new_geometry == Bitmap:
@@ -282,6 +283,9 @@ class Geometry(JsonSerializable):
282
283
  res = [self.to_bbox()]
283
284
  elif new_geometry == Polygon:
284
285
  res = geometry_to_polygon(self, approx_epsilon=approx_epsilon)
286
+ elif new_geometry == OrientedBBox:
287
+ bbox = self.to_bbox()
288
+ res = [OrientedBBox.from_bbox(bbox)]
285
289
 
286
290
  if len(res) == 0:
287
291
  logger.warn(
@@ -13,6 +13,7 @@ from supervisely.geometry.point_location import PointLocation
13
13
  from supervisely.geometry.polygon import Polygon
14
14
  from supervisely.geometry.polyline import Polyline
15
15
  from supervisely.geometry.rectangle import Rectangle
16
+ from supervisely.geometry.oriented_bbox import OrientedBBox
16
17
 
17
18
 
18
19
  def _geometry_to_mask_base(
@@ -122,7 +123,7 @@ def deserialize_geometry(geometry_type_str: str, geometry_json: Dict) -> Geometr
122
123
 
123
124
 
124
125
  def geometry_to_polygon(geometry: Geometry, approx_epsilon: Optional[int] = None) -> List[Geometry]:
125
- if type(geometry) not in (Rectangle, Polyline, Polygon, Bitmap, AlphaMask):
126
+ if type(geometry) not in (Rectangle, Polyline, Polygon, Bitmap, AlphaMask, OrientedBBox):
126
127
  raise KeyError(
127
128
  "Can not convert {} to {}".format(geometry.geometry_name(), Polygon.__name__)
128
129
  )
@@ -135,6 +136,9 @@ def geometry_to_polygon(geometry: Geometry, approx_epsilon: Optional[int] = None
135
136
 
136
137
  if type(geometry) == Polygon:
137
138
  return [geometry]
139
+
140
+ if type(geometry) == OrientedBBox:
141
+ return [Polygon(geometry.calculate_rotated_corners(), [])]
138
142
 
139
143
  if type(geometry) in [AlphaMask, Bitmap]:
140
144
  new_geometries = geometry.to_contours()