supervisely 6.73.410__py3-none-any.whl → 6.73.470__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.

Potentially problematic release.


This version of supervisely might be problematic. Click here for more details.

Files changed (190) hide show
  1. supervisely/__init__.py +136 -1
  2. supervisely/_utils.py +81 -0
  3. supervisely/annotation/json_geometries_map.py +2 -0
  4. supervisely/annotation/label.py +80 -3
  5. supervisely/api/annotation_api.py +9 -9
  6. supervisely/api/api.py +67 -43
  7. supervisely/api/app_api.py +72 -5
  8. supervisely/api/dataset_api.py +108 -33
  9. supervisely/api/entity_annotation/figure_api.py +113 -49
  10. supervisely/api/image_api.py +82 -0
  11. supervisely/api/module_api.py +10 -0
  12. supervisely/api/nn/deploy_api.py +15 -9
  13. supervisely/api/nn/ecosystem_models_api.py +201 -0
  14. supervisely/api/nn/neural_network_api.py +12 -3
  15. supervisely/api/pointcloud/pointcloud_api.py +38 -0
  16. supervisely/api/pointcloud/pointcloud_episode_annotation_api.py +3 -0
  17. supervisely/api/project_api.py +213 -6
  18. supervisely/api/task_api.py +11 -1
  19. supervisely/api/video/video_annotation_api.py +4 -2
  20. supervisely/api/video/video_api.py +79 -1
  21. supervisely/api/video/video_figure_api.py +24 -11
  22. supervisely/api/volume/volume_api.py +38 -0
  23. supervisely/app/__init__.py +1 -1
  24. supervisely/app/content.py +14 -6
  25. supervisely/app/fastapi/__init__.py +1 -0
  26. supervisely/app/fastapi/custom_static_files.py +1 -1
  27. supervisely/app/fastapi/multi_user.py +88 -0
  28. supervisely/app/fastapi/subapp.py +175 -42
  29. supervisely/app/fastapi/templating.py +1 -1
  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 +11 -1
  35. supervisely/app/widgets/agent_selector/template.html +1 -0
  36. supervisely/app/widgets/card/card.py +20 -0
  37. supervisely/app/widgets/dataset_thumbnail/dataset_thumbnail.py +11 -2
  38. supervisely/app/widgets/dataset_thumbnail/template.html +3 -1
  39. supervisely/app/widgets/deploy_model/deploy_model.py +750 -0
  40. supervisely/app/widgets/dialog/dialog.py +12 -0
  41. supervisely/app/widgets/dialog/template.html +2 -1
  42. supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
  43. supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
  44. supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
  45. supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
  46. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +195 -0
  47. supervisely/app/widgets/experiment_selector/experiment_selector.py +454 -263
  48. supervisely/app/widgets/fast_table/fast_table.py +713 -126
  49. supervisely/app/widgets/fast_table/script.js +492 -95
  50. supervisely/app/widgets/fast_table/style.css +54 -0
  51. supervisely/app/widgets/fast_table/template.html +45 -5
  52. supervisely/app/widgets/heatmap/__init__.py +0 -0
  53. supervisely/app/widgets/heatmap/heatmap.py +523 -0
  54. supervisely/app/widgets/heatmap/script.js +378 -0
  55. supervisely/app/widgets/heatmap/style.css +227 -0
  56. supervisely/app/widgets/heatmap/template.html +21 -0
  57. supervisely/app/widgets/input_tag/input_tag.py +102 -15
  58. supervisely/app/widgets/input_tag_list/__init__.py +0 -0
  59. supervisely/app/widgets/input_tag_list/input_tag_list.py +274 -0
  60. supervisely/app/widgets/input_tag_list/template.html +70 -0
  61. supervisely/app/widgets/radio_table/radio_table.py +10 -2
  62. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  63. supervisely/app/widgets/radio_tabs/template.html +1 -0
  64. supervisely/app/widgets/select/select.py +6 -4
  65. supervisely/app/widgets/select_dataset/select_dataset.py +6 -0
  66. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +83 -7
  67. supervisely/app/widgets/table/table.py +68 -13
  68. supervisely/app/widgets/tabs/tabs.py +22 -6
  69. supervisely/app/widgets/tabs/template.html +5 -1
  70. supervisely/app/widgets/transfer/style.css +3 -0
  71. supervisely/app/widgets/transfer/template.html +3 -1
  72. supervisely/app/widgets/transfer/transfer.py +48 -45
  73. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  74. supervisely/convert/image/csv/csv_converter.py +24 -15
  75. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +43 -41
  76. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +75 -51
  77. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +137 -124
  78. supervisely/convert/video/video_converter.py +2 -2
  79. supervisely/geometry/polyline_3d.py +110 -0
  80. supervisely/io/env.py +161 -1
  81. supervisely/nn/artifacts/__init__.py +1 -1
  82. supervisely/nn/artifacts/artifacts.py +10 -2
  83. supervisely/nn/artifacts/detectron2.py +1 -0
  84. supervisely/nn/artifacts/hrda.py +1 -0
  85. supervisely/nn/artifacts/mmclassification.py +20 -0
  86. supervisely/nn/artifacts/mmdetection.py +5 -3
  87. supervisely/nn/artifacts/mmsegmentation.py +1 -0
  88. supervisely/nn/artifacts/ritm.py +1 -0
  89. supervisely/nn/artifacts/rtdetr.py +1 -0
  90. supervisely/nn/artifacts/unet.py +1 -0
  91. supervisely/nn/artifacts/utils.py +3 -0
  92. supervisely/nn/artifacts/yolov5.py +2 -0
  93. supervisely/nn/artifacts/yolov8.py +1 -0
  94. supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
  95. supervisely/nn/experiments.py +9 -0
  96. supervisely/nn/inference/cache.py +37 -17
  97. supervisely/nn/inference/gui/serving_gui_template.py +39 -13
  98. supervisely/nn/inference/inference.py +953 -211
  99. supervisely/nn/inference/inference_request.py +15 -8
  100. supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
  101. supervisely/nn/inference/object_detection/object_detection.py +1 -0
  102. supervisely/nn/inference/predict_app/__init__.py +0 -0
  103. supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
  104. supervisely/nn/inference/predict_app/gui/classes_selector.py +160 -0
  105. supervisely/nn/inference/predict_app/gui/gui.py +915 -0
  106. supervisely/nn/inference/predict_app/gui/input_selector.py +344 -0
  107. supervisely/nn/inference/predict_app/gui/model_selector.py +77 -0
  108. supervisely/nn/inference/predict_app/gui/output_selector.py +179 -0
  109. supervisely/nn/inference/predict_app/gui/preview.py +93 -0
  110. supervisely/nn/inference/predict_app/gui/settings_selector.py +881 -0
  111. supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
  112. supervisely/nn/inference/predict_app/gui/utils.py +399 -0
  113. supervisely/nn/inference/predict_app/predict_app.py +176 -0
  114. supervisely/nn/inference/session.py +47 -39
  115. supervisely/nn/inference/tracking/bbox_tracking.py +5 -1
  116. supervisely/nn/inference/tracking/point_tracking.py +5 -1
  117. supervisely/nn/inference/tracking/tracker_interface.py +4 -0
  118. supervisely/nn/inference/uploader.py +9 -5
  119. supervisely/nn/model/model_api.py +44 -22
  120. supervisely/nn/model/prediction.py +15 -1
  121. supervisely/nn/model/prediction_session.py +70 -14
  122. supervisely/nn/prediction_dto.py +7 -0
  123. supervisely/nn/tracker/__init__.py +6 -8
  124. supervisely/nn/tracker/base_tracker.py +54 -0
  125. supervisely/nn/tracker/botsort/__init__.py +1 -0
  126. supervisely/nn/tracker/botsort/botsort_config.yaml +30 -0
  127. supervisely/nn/tracker/botsort/osnet_reid/__init__.py +0 -0
  128. supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
  129. supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
  130. supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
  131. supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
  132. supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
  133. supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
  134. supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
  135. supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
  136. supervisely/nn/tracker/botsort_tracker.py +273 -0
  137. supervisely/nn/tracker/calculate_metrics.py +264 -0
  138. supervisely/nn/tracker/utils.py +273 -0
  139. supervisely/nn/tracker/visualize.py +520 -0
  140. supervisely/nn/training/gui/gui.py +152 -49
  141. supervisely/nn/training/gui/hyperparameters_selector.py +1 -1
  142. supervisely/nn/training/gui/model_selector.py +8 -6
  143. supervisely/nn/training/gui/train_val_splits_selector.py +144 -71
  144. supervisely/nn/training/gui/training_artifacts.py +3 -1
  145. supervisely/nn/training/train_app.py +225 -46
  146. supervisely/project/pointcloud_episode_project.py +12 -8
  147. supervisely/project/pointcloud_project.py +12 -8
  148. supervisely/project/project.py +221 -75
  149. supervisely/template/experiment/experiment.html.jinja +105 -55
  150. supervisely/template/experiment/experiment_generator.py +258 -112
  151. supervisely/template/experiment/header.html.jinja +31 -13
  152. supervisely/template/experiment/sly-style.css +7 -2
  153. supervisely/versions.json +3 -1
  154. supervisely/video/sampling.py +42 -20
  155. supervisely/video/video.py +41 -12
  156. supervisely/video_annotation/video_figure.py +38 -4
  157. supervisely/volume/stl_converter.py +2 -0
  158. supervisely/worker_api/agent_rpc.py +24 -1
  159. supervisely/worker_api/rpc_servicer.py +31 -7
  160. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/METADATA +22 -14
  161. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/RECORD +167 -148
  162. supervisely_lib/__init__.py +6 -1
  163. supervisely/app/widgets/experiment_selector/style.css +0 -27
  164. supervisely/app/widgets/experiment_selector/template.html +0 -61
  165. supervisely/nn/tracker/bot_sort/__init__.py +0 -21
  166. supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
  167. supervisely/nn/tracker/bot_sort/matching.py +0 -127
  168. supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
  169. supervisely/nn/tracker/deep_sort/__init__.py +0 -6
  170. supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
  171. supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
  172. supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
  173. supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
  174. supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
  175. supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
  176. supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
  177. supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
  178. supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
  179. supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
  180. supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
  181. supervisely/nn/tracker/tracker.py +0 -285
  182. supervisely/nn/tracker/utils/kalman_filter.py +0 -492
  183. supervisely/nn/tracking/__init__.py +0 -1
  184. supervisely/nn/tracking/boxmot.py +0 -114
  185. supervisely/nn/tracking/tracking.py +0 -24
  186. /supervisely/{nn/tracker/utils → app/widgets/deploy_model}/__init__.py +0 -0
  187. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/LICENSE +0 -0
  188. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/WHEEL +0 -0
  189. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/entry_points.txt +0 -0
  190. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
4
+ import uuid
2
5
  from os import path as osp
3
6
  from pathlib import Path
4
- from typing import Dict, Optional
7
+ from typing import Dict, List, Optional
5
8
 
6
9
  import supervisely.convert.pointcloud_episodes.nuscenes_conv.nuscenes_helper as helpers
7
10
  import supervisely.io.fs as fs
@@ -9,6 +12,7 @@ from supervisely._utils import is_development
9
12
  from supervisely.annotation.obj_class import ObjClass
10
13
  from supervisely.annotation.tag_meta import TagMeta, TagValueType
11
14
  from supervisely.api.api import Api, ApiField
15
+ from supervisely.api.dataset_api import DatasetInfo
12
16
  from supervisely.convert.base_converter import AvailablePointcloudConverters
13
17
  from supervisely.convert.pointcloud_episodes.pointcloud_episodes_converter import (
14
18
  PointcloudEpisodeConverter,
@@ -36,20 +40,14 @@ from supervisely.pointcloud_annotation.pointcloud_figure import PointcloudFigure
36
40
  from supervisely.project.project_meta import ProjectMeta
37
41
  from supervisely.sly_logger import logger
38
42
  from supervisely.tiny_timer import TinyTimer
43
+ from supervisely.video_annotation.key_id_map import KeyIdMap
39
44
 
40
45
 
41
46
  class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
42
47
  """Converter for NuScenes pointcloud episodes format."""
43
48
 
44
- def __init__(
45
- self,
46
- input_data: str,
47
- labeling_interface: str,
48
- upload_as_links: bool,
49
- remote_files_map: Optional[Dict[str, str]] = None,
50
- ):
51
- super().__init__(input_data, labeling_interface, upload_as_links, remote_files_map)
52
- self._nuscenes = None
49
+ _nuscenes: "NuScenes" = None # type: ignore
50
+ _custom_data: Dict = {}
53
51
 
54
52
  def __str__(self) -> str:
55
53
  return AvailablePointcloudConverters.NUSCENES
@@ -61,8 +59,10 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
61
59
  logger.warning("Please, run 'pip install nuscenes-devkit' to import NuScenes data.")
62
60
  return False
63
61
 
64
- def _contains_tables(p):
65
- return all(fs.file_exists(Path(p) / f"{name}.json") for name in helpers.TABLE_NAMES)
62
+ table_json_filenames = [f"{name}.json" for name in helpers.TABLE_NAMES]
63
+
64
+ def _contains_tables(dir_path: str) -> bool:
65
+ return all(fs.file_exists(osp.join(dir_path, table)) for table in table_json_filenames)
66
66
 
67
67
  def _filter_fn(path):
68
68
  has_tables = False
@@ -83,18 +83,19 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
83
83
  version = osp.basename(ann_dir)
84
84
  try:
85
85
  t = TinyTimer()
86
- nuscenes = NuScenes(version=version, dataroot=input_path, verbose=False)
87
- self._nuscenes: NuScenes = nuscenes
86
+ self._nuscenes: NuScenes = NuScenes(version=version, dataroot=input_path, verbose=False)
88
87
  logger.debug(f"NuScenes initialization took {t.get_sec():.3f} sec")
89
88
  except Exception as e:
90
89
  logger.debug(f"Failed to initialize NuScenes: {e}")
91
90
  return False
92
91
 
92
+ self._custom_data["nuscenes_version"] = version
93
+ self._custom_data["dataroot"] = input_path
93
94
  return True
94
95
 
95
96
  def to_supervisely(
96
97
  self,
97
- scene_samples,
98
+ scene_samples: Dict[str, helpers.Sample],
98
99
  meta: ProjectMeta,
99
100
  renamed_classes: dict = {},
100
101
  renamed_tags: dict = {},
@@ -102,9 +103,13 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
102
103
  token_to_obj = {}
103
104
  frames = []
104
105
  tags = []
105
- for sample_i, sample in enumerate(scene_samples):
106
+ frame_idx_to_scene_sample_token = {}
107
+ if "frame_token_map" not in self._custom_data:
108
+ self._custom_data["frame_token_map"] = {}
109
+ for sample_i, (token, sample) in enumerate(scene_samples.items()):
106
110
  figures = []
107
111
  for obj in sample.anns:
112
+ ann_token = uuid.UUID(obj.token)
108
113
  instance_token = obj.instance_token
109
114
  class_name = obj.category
110
115
  parent_obj_token = obj.parent_token
@@ -114,7 +119,9 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
114
119
  obj_class_name = renamed_classes.get(class_name, class_name)
115
120
  obj_class = meta.get_obj_class(obj_class_name)
116
121
  obj_tags = None # ! TODO: fix tags
117
- pcd_ep_obj = PointcloudEpisodeObject(obj_class, obj_tags)
122
+ pcd_ep_obj = PointcloudEpisodeObject(
123
+ obj_class, obj_tags, uuid.UUID(instance_token)
124
+ )
118
125
  # * Assign the object to the starting token
119
126
  token_to_obj[instance_token] = pcd_ep_obj
120
127
  parent_object = pcd_ep_obj
@@ -123,42 +130,45 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
123
130
  token_to_obj[instance_token] = token_to_obj[parent_obj_token]
124
131
  parent_object = token_to_obj[parent_obj_token]
125
132
  geom = obj.to_supervisely()
126
- pcd_figure = PointcloudFigure(parent_object, geom, sample_i)
133
+ pcd_figure = PointcloudFigure(parent_object, geom, sample_i, ann_token)
127
134
  figures.append(pcd_figure)
135
+ frame_idx_to_scene_sample_token[sample_i] = token
128
136
  frame = PointcloudEpisodeFrame(sample_i, figures)
129
137
  frames.append(frame)
130
138
  tag_collection = PointcloudEpisodeTagCollection(tags) if len(tags) > 0 else None
139
+ self._custom_data["frame_token_map"][self._current_ds_id] = frame_idx_to_scene_sample_token
140
+ key_uuid = uuid.UUID(token)
131
141
  return PointcloudEpisodeAnnotation(
132
142
  len(frames),
133
143
  PointcloudEpisodeObjectCollection(list(set(token_to_obj.values()))),
134
144
  PointcloudEpisodeFrameCollection(frames),
135
145
  tag_collection,
146
+ key=key_uuid,
136
147
  )
137
148
 
138
149
  def upload_dataset(self, api: Api, dataset_id: int, batch_size: int = 1, log_progress=True):
139
- nuscenes = self._nuscenes
150
+ from nuscenes import NuScenes # pylint: disable=import-error
151
+
152
+ nuscenes: NuScenes = self._nuscenes
153
+ key_id_map = KeyIdMap()
140
154
 
141
155
  tag_metas = [TagMeta(attr["name"], TagValueType.NONE) for attr in nuscenes.attribute]
142
- obj_classes = []
156
+ obj_classes = {}
143
157
  for category in nuscenes.category:
144
158
  color = nuscenes.colormap[category["name"]]
145
- description = category["description"]
146
- if len(description) > 255:
147
- # * Trim description to fit into 255 characters limit
148
- sentences = description.split(".")
149
- trimmed_description = ""
150
- for sentence in sentences:
151
- if len(trimmed_description) + len(sentence) + 1 > 255:
152
- break
153
- trimmed_description += sentence + "."
154
- description = trimmed_description.strip()
155
- obj_classes.append(ObjClass(category["name"], Cuboid3d, color, description=description))
156
-
157
- self._meta = ProjectMeta(obj_classes, tag_metas)
159
+ description = helpers.trim_description(category.get("description", ""))
160
+ token = category["token"]
161
+ obj_classes[token] = ObjClass(
162
+ category["name"], Cuboid3d, color, description=description
163
+ )
164
+
165
+ self._custom_data["classes_token_map"] = {k: v.name for k, v in obj_classes.items()}
166
+
167
+ self._meta = ProjectMeta(list(obj_classes.values()), tag_metas)
158
168
  meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
159
169
 
160
170
  dataset_info = api.dataset.get_info_by_id(dataset_id)
161
- scene_name_to_dataset = {}
171
+ scene_name_to_dataset: Dict[str, DatasetInfo] = {}
162
172
 
163
173
  scene_names = [scene["name"] for scene in nuscenes.scene]
164
174
  scene_cnt = len(scene_names)
@@ -188,12 +198,13 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
188
198
 
189
199
  for scene in nuscenes.scene:
190
200
  current_dataset_id = scene_name_to_dataset[scene["name"]].id
201
+ self._current_ds_id = current_dataset_id
191
202
 
192
203
  log = nuscenes.get("log", scene["log_token"])
193
204
  sample_token = scene["first_sample_token"]
194
205
 
195
206
  # * Extract scene's samples
196
- scene_samples = []
207
+ scene_samples: Dict[str, helpers.Sample] = {}
197
208
  for i in range(scene["nbr_samples"]):
198
209
  sample = nuscenes.get("sample", sample_token)
199
210
  lidar_path, boxes, _ = nuscenes.get_sample_data(sample["data"]["LIDAR_TOP"])
@@ -207,25 +218,25 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
207
218
  current_instance_token = inst_token["token"]
208
219
  parent_token = inst_token["prev"]
209
220
 
210
- # get category, attributes and visibility
211
221
  ann = nuscenes.get("sample_annotation", current_instance_token)
212
222
  category = ann["category_name"]
213
223
  attributes = [
214
224
  nuscenes.get("attribute", attr)["name"] for attr in ann["attribute_tokens"]
215
225
  ]
216
226
  visibility = nuscenes.get("visibility", ann["visibility_token"])["level"]
217
-
218
- anns.append(
219
- helpers.AnnotationObject(
220
- name,
221
- box,
222
- current_instance_token,
223
- parent_token,
224
- category,
225
- attributes,
226
- visibility,
227
- )
227
+ ann_token = ann["token"]
228
+
229
+ ann = helpers.AnnotationObject(
230
+ name=name,
231
+ bbox=box,
232
+ token=ann_token,
233
+ instance_token=current_instance_token,
234
+ parent_token=parent_token,
235
+ category=category,
236
+ attributes=attributes,
237
+ visibility=visibility,
228
238
  )
239
+ anns.append(ann)
229
240
 
230
241
  # get camera data
231
242
  sample_data = nuscenes.get("sample_data", sample["data"]["LIDAR_TOP"])
@@ -239,15 +250,18 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
239
250
  for sensor, token in sample["data"].items()
240
251
  if sensor.startswith("CAM")
241
252
  ]
242
- scene_samples.append(helpers.Sample(timestamp, lidar_path, anns, camera_data))
253
+ sample_token = sample["token"]
254
+ scene_samples[sample_token] = helpers.Sample(
255
+ timestamp, lidar_path, anns, camera_data
256
+ )
243
257
  sample_token = sample["next"]
244
258
 
245
259
  # * Convert and upload pointclouds
246
260
  frame_to_pointcloud_ids = {}
247
- for idx, sample in enumerate(scene_samples):
261
+ for idx, sample in enumerate(scene_samples.values()):
248
262
  pcd_path = sample.convert_lidar_to_supervisely()
249
263
 
250
- pcd_name = fs.get_file_name(pcd_path)
264
+ pcd_name = fs.get_file_name_with_ext(pcd_path)
251
265
  pcd_meta = {
252
266
  "frame": idx,
253
267
  "vehicle": log["vehicle"],
@@ -287,9 +301,10 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
287
301
 
288
302
  # * Convert and upload annotations
289
303
  pcd_ann = self.to_supervisely(scene_samples, meta, renamed_classes, renamed_tags)
304
+
290
305
  try:
291
306
  api.pointcloud_episode.annotation.append(
292
- current_dataset_id, pcd_ann, frame_to_pointcloud_ids
307
+ current_dataset_id, pcd_ann, frame_to_pointcloud_ids, key_id_map=key_id_map
293
308
  )
294
309
  logger.info(f"Dataset ID:{current_dataset_id} has been successfully uploaded.")
295
310
  except Exception as e:
@@ -297,6 +312,15 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
297
312
  logger.warning(
298
313
  f"Failed to upload annotation for scene: {scene['name']}. Message: {error_msg}"
299
314
  )
315
+ key_id_map = key_id_map.to_dict()
316
+ key_id_map.pop("tags")
317
+ key_id_map.pop("videos")
318
+ self._custom_data["key_id_map"] = key_id_map
319
+
320
+ project_id = dataset_info.project_id
321
+ current_custom_data = api.project.get_custom_data(project_id)
322
+ current_custom_data.update(self._custom_data)
323
+ api.project.update_custom_data(project_id, current_custom_data)
300
324
 
301
325
  if log_progress:
302
326
  if is_development():
@@ -1,7 +1,8 @@
1
+ from dataclasses import dataclass, field
1
2
  from datetime import datetime
2
3
  from os import path as osp
3
4
  from pathlib import Path
4
- from typing import List
5
+ from typing import Dict, Generator, List, Tuple
5
6
 
6
7
  import numpy as np
7
8
 
@@ -40,113 +41,50 @@ TABLE_NAMES = [
40
41
  ]
41
42
 
42
43
 
43
- class Sample:
44
- """
45
- A class to represent a sample from the NuScenes dataset.
46
- """
47
-
48
- def __init__(self, timestamp, lidar_path, anns, cam_data):
49
- self.timestamp = datetime.utcfromtimestamp(timestamp / 1e6).isoformat()
50
- self.lidar_path = lidar_path
51
- self.anns = anns
52
- self.cam_data = cam_data
53
-
54
- @staticmethod
55
- def generate_boxes(nuscenes, boxes):
56
- """
57
- Generate ground truth boxes for a given set of boxes.
58
-
59
- Yields:
60
- tuple: A tuple containing:
61
- - gt_box (np.ndarray): A numpy array representing the ground truth box with concatenated location,
62
- dimensions, and rotation.
63
- - name (str): The name of the object.
64
- - instance_token (str): The instance token associated with the box.
65
- """
66
- locs = np.array([b.center for b in boxes]).reshape(-1, 3)
67
- dims = np.array([b.wlh for b in boxes]).reshape(-1, 3)
68
- rots = np.array([b.orientation.yaw_pitch_roll[0] for b in boxes]).reshape(-1, 1)
69
-
70
- gt_boxes = np.concatenate([locs, dims, -rots - np.pi / 2], axis=1)
71
- names = np.array([b.name for b in boxes])
72
- instance_tokens = [nuscenes.get("sample_annotation", box.token) for box in boxes]
73
-
74
- yield from zip(gt_boxes, names, instance_tokens)
75
-
76
- def convert_lidar_to_supervisely(self):
77
- """
78
- Converts a LiDAR point cloud file to the Supervisely format and saves it as a .pcd file.
79
-
80
- Returns:
81
- str: The file path of the saved .pcd file.
82
- """
83
- import open3d as o3d # pylint: disable=import-error
84
-
85
- bin_file = Path(self.lidar_path)
86
- save_path = str(bin_file.with_suffix(".pcd"))
87
-
88
- b = np.fromfile(bin_file, dtype=np.float32).reshape(-1, 5)
89
- points = b[:, 0:3]
90
- intensity = b[:, 3]
91
- ring_index = b[:, 4]
92
- intensity_fake_rgb = np.zeros((intensity.shape[0], 3))
93
- intensity_fake_rgb[:, 0] = (
94
- intensity # red The intensity measures the reflectivity of the objects
95
- )
96
- intensity_fake_rgb[:, 1] = (
97
- ring_index # green ring index is the index of the laser ranging from 0 to 31
98
- )
99
- try:
100
- pc = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points))
101
- pc.colors = o3d.utility.Vector3dVector(intensity_fake_rgb)
102
- o3d.io.write_point_cloud(save_path, pc)
103
- except Exception as e:
104
- logger.warning(f"Error converting lidar to supervisely format: {e}")
105
- return save_path
44
+ def trim_description(description: str, max_length: int = 255) -> str:
45
+ if len(description) > max_length:
46
+ sentences = description.split(".")
47
+ trimmed_description = ""
48
+ for sentence in sentences:
49
+ if len(trimmed_description) + len(sentence) + 1 > max_length:
50
+ break
51
+ trimmed_description += sentence + "."
52
+ description = trimmed_description.strip()
53
+ return description
106
54
 
107
55
 
56
+ @dataclass
108
57
  class AnnotationObject:
109
58
  """
110
59
  A class to represent an annotation object in the NuScenes dataset.
111
60
 
112
- Attributes:
113
- -----------
114
- name : str
115
- The name of the annotation object.
116
- bbox : np.ndarray
117
- The bounding box coordinates.
118
- instance_token : str
119
- The instance token associated with the annotation object.
120
- parent_token : str
121
- The token of instance preceding the current object instance.
122
- category : str
123
- The class name of the annotation object.
124
- attributes : List[str]
125
- The attribute names associated with the annotation object.
126
- visibility : str
127
- The visibility level of the annotation object.
61
+ :param name: The name of the annotation object
62
+ :type name: str
63
+ :param bbox: The bounding box coordinates in NuScenes format
64
+ :type bbox: np.ndarray
65
+ :param token: The unique token identifying the annotation object
66
+ :type token: str
67
+ :param instance_token: The instance token associated with the annotation object
68
+ :type instance_token: str
69
+ :param parent_token: The token of instance preceding the current object instance
70
+ :type parent_token: str
71
+ :param category: The class name of the annotation object
72
+ :type category: str
73
+ :param attributes: The attribute names associated with the annotation object
74
+ :type attributes: List[str]
75
+ :param visibility: The visibility level of the annotation object
76
+ :type visibility: str
128
77
  """
129
-
130
- def __init__(
131
- self,
132
- name: str,
133
- bbox: np.ndarray,
134
- instance_token: str,
135
- parent_token: str,
136
- category: str,
137
- attributes: List[str],
138
- visibility: str,
139
- ):
140
- self.name = name
141
- self.bbox = bbox
142
- self.instance_token = instance_token
143
- self.parent_token = parent_token
144
-
145
- self.category = category
146
- self.attributes = attributes
147
- self.visibility = visibility
148
-
149
- def to_supervisely(self):
78
+ name: str
79
+ bbox: np.ndarray
80
+ token: str
81
+ instance_token: str
82
+ parent_token: str
83
+ category: str
84
+ attributes: List[str]
85
+ visibility: str
86
+
87
+ def to_supervisely(self) -> Cuboid3d:
150
88
  box = self.convert_nuscenes_to_BEVBox3D()
151
89
 
152
90
  bbox = box.to_xyzwhlr()
@@ -176,29 +114,33 @@ class AnnotationObject:
176
114
 
177
115
  class CamData:
178
116
  """
179
- A class to represent camera data and perform transformations between different coordinate systems.
180
-
181
- Attributes:
182
- -----------
183
- name : str
184
- The name of the sensor.
185
- path : str
186
- The path to the image file.
187
- imsize : tuple
188
- The size of the image (width, height).
189
- extrinsic : np.ndarray
190
- The extrinsic matrix (4x4) representing the transformation from the lidar to the camera coordinate system.
191
- intrinsic : np.ndarray
192
- The intrinsic matrix (3x3) representing the camera's intrinsic parameters.
117
+ This class handles camera sensor data from the nuScenes dataset, including coordinate system
118
+ transformations from lidar to camera space and extraction of camera calibration parameters.
119
+
120
+ :param nuscenes: The nuScenes dataset instance
121
+ :type nuscenes: NuScenes
122
+ :param sensor_name: The name of the camera sensor
123
+ :type sensor_name: str
124
+ :param sensor_token: The token identifying the specific sensor sample
125
+ :type sensor_token: str
126
+ :param cs_record: The calibrated sensor record for the lidar
127
+ :type cs_record: dict
128
+ :param ego_record: The ego pose record for the lidar
129
+ :type ego_record: dict
193
130
  """
194
131
 
195
- def __init__(self, nuscenes, sensor_name, sensor_token, cs_record, ego_record):
132
+ def __init__(
133
+ self, nuscenes, sensor_name: str, sensor_token: str, cs_record: dict, ego_record: dict
134
+ ):
135
+ from nuscenes import NuScenes # pylint: disable=import-error
196
136
  from nuscenes.utils.data_classes import ( # pylint: disable=import-error
197
137
  transform_matrix,
198
138
  )
199
139
  from pyquaternion import Quaternion # pylint: disable=import-error
200
140
 
201
- img_path, boxes, cam_intrinsic = nuscenes.get_sample_data(sensor_token)
141
+ nuscenes: NuScenes = nuscenes
142
+
143
+ img_path, _, _ = nuscenes.get_sample_data(sensor_token)
202
144
  if not osp.exists(img_path):
203
145
  return None
204
146
 
@@ -237,15 +179,14 @@ class CamData:
237
179
  self.extrinsic = np.hstack((velo_to_cam_rot, velo_to_cam_trans.reshape(3, 1)))
238
180
  self.intrinsic = np.asarray(cs_record_cam["camera_intrinsic"])
239
181
 
240
- def get_info(self, timestamp):
182
+ def get_info(self, timestamp: str) -> Tuple[str, Dict]:
241
183
  """
242
- Retrieves information about the image and its metadata.
243
-
244
- Args:
245
- timestamp (int): The timestamp associated with the image.
184
+ Generates image info based on the camera data.
246
185
 
247
- Returns:
248
- tuple: A tuple containing the image path and a dictionary with image metadata.
186
+ :param timestamp: The timestamp associated with the image
187
+ :type timestamp: str
188
+ :return: A tuple containing the image path and a dictionary with image metadata.
189
+ :rtype: tuple
249
190
  """
250
191
  sensors_to_skip = ["_intrinsic", "_extrinsic", "_imsize"]
251
192
  if not any([self.name.endswith(s) for s in sensors_to_skip]):
@@ -263,3 +204,75 @@ class CamData:
263
204
  },
264
205
  }
265
206
  return (sly_path_img, img_info)
207
+
208
+
209
+ @dataclass
210
+ class Sample:
211
+ """
212
+ A class to represent a sample from the NuScenes dataset.
213
+ """
214
+ timestamp_us: float
215
+ lidar_path: str
216
+ anns: List[AnnotationObject]
217
+ cam_data: List[CamData]
218
+ timestamp: str = field(init=False)
219
+
220
+ def __post_init__(self):
221
+ self.timestamp = datetime.utcfromtimestamp(self.timestamp_us / 1e6).isoformat()
222
+
223
+ @staticmethod
224
+ def generate_boxes(nuscenes, boxes: List) -> Generator:
225
+ """
226
+ Generate ground truth boxes for a given set of boxes.
227
+
228
+ :param nuscenes: The nuScenes dataset instance
229
+ :type nuscenes: NuScenes
230
+ :param boxes: A list of boxes to generate ground truth for
231
+ :type boxes: List
232
+ :return: A generator that yields tuples containing the ground truth box, name, and instance token.
233
+ :rtype: generator
234
+ """
235
+ from nuscenes.utils.data_classes import Box # pylint: disable=import-error
236
+
237
+ boxes: List[Box] = boxes
238
+
239
+ locs = np.array([b.center for b in boxes]).reshape(-1, 3)
240
+ dims = np.array([b.wlh for b in boxes]).reshape(-1, 3)
241
+ rots = np.array([b.orientation.yaw_pitch_roll[0] for b in boxes]).reshape(-1, 1)
242
+
243
+ gt_boxes = np.concatenate([locs, dims, -rots - np.pi / 2], axis=1)
244
+ names = np.array([b.name for b in boxes])
245
+ instance_tokens = [nuscenes.get("sample_annotation", box.token) for box in boxes]
246
+
247
+ yield from zip(gt_boxes, names, instance_tokens)
248
+
249
+ def convert_lidar_to_supervisely(self) -> str:
250
+ """
251
+ Converts a LiDAR point cloud file to the Supervisely format and saves it as a .pcd file.
252
+
253
+ :return: The file path of the saved .pcd file.
254
+ :rtype: str
255
+ """
256
+ import open3d as o3d # pylint: disable=import-error
257
+
258
+ bin_file = Path(self.lidar_path)
259
+ save_path = str(bin_file.with_suffix(".pcd"))
260
+
261
+ b = np.fromfile(bin_file, dtype=np.float32).reshape(-1, 5)
262
+ points = b[:, 0:3]
263
+ intensity = b[:, 3]
264
+ ring_index = b[:, 4]
265
+ intensity_fake_rgb = np.zeros((intensity.shape[0], 3))
266
+ intensity_fake_rgb[:, 0] = (
267
+ intensity # red The intensity measures the reflectivity of the objects
268
+ )
269
+ intensity_fake_rgb[:, 1] = (
270
+ ring_index # green ring index is the index of the laser ranging from 0 to 31
271
+ )
272
+ try:
273
+ pc = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points))
274
+ pc.colors = o3d.utility.Vector3dVector(intensity_fake_rgb)
275
+ o3d.io.write_point_cloud(save_path, pc)
276
+ except Exception as e:
277
+ logger.warning(f"Error converting lidar to supervisely format: {e}")
278
+ return save_path
@@ -266,8 +266,8 @@ class VideoConverter(BaseConverter):
266
266
  if codec_type not in ["video", "audio"]:
267
267
  continue
268
268
  codec_name = stream["codecName"]
269
- if codec_type == "video" and codec_name != "h264":
270
- logger.info(f"Video codec is not h264, transcoding is required: {codec_name}")
269
+ if codec_type == "video" and codec_name not in ["h264", "h265", "hevc", "av1"]:
270
+ logger.info(f"Video codec is not h264/h265/hevc/av1, transcoding is required: {codec_name}")
271
271
  need_video_transc = True
272
272
  elif codec_type == "audio" and codec_name != "aac":
273
273
  logger.info(f"Audio codec is not aac, transcoding is required: {codec_name}")