supervisely 6.73.274__py3-none-any.whl → 6.73.276__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 (20) hide show
  1. supervisely/app/widgets/custom_models_selector/custom_models_selector.py +54 -44
  2. supervisely/convert/__init__.py +4 -1
  3. supervisely/convert/base_converter.py +2 -9
  4. supervisely/convert/pointcloud/nuscenes_conv/__init__.py +0 -0
  5. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +227 -0
  6. supervisely/convert/pointcloud/pointcloud_converter.py +52 -1
  7. supervisely/convert/pointcloud/sly/sly_pointcloud_converter.py +8 -21
  8. supervisely/convert/pointcloud/sly/sly_pointcloud_helper.py +4 -2
  9. supervisely/convert/pointcloud_episodes/lyft/lyft_converter.py +19 -20
  10. supervisely/convert/pointcloud_episodes/nuscenes_conv/__init__.py +0 -0
  11. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +305 -0
  12. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +265 -0
  13. supervisely/convert/pointcloud_episodes/pointcloud_episodes_converter.py +82 -27
  14. supervisely/convert/pointcloud_episodes/sly/sly_pointcloud_episodes_converter.py +9 -8
  15. {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/METADATA +1 -1
  16. {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/RECORD +20 -15
  17. {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/LICENSE +0 -0
  18. {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/WHEEL +0 -0
  19. {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/entry_points.txt +0 -0
  20. {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/top_level.txt +0 -0
@@ -30,8 +30,6 @@ from supervisely import (
30
30
  PointcloudEpisodeFrame,
31
31
  TagMeta,
32
32
  TagValueType,
33
- VideoTagCollection,
34
- VideoTag,
35
33
  )
36
34
  from supervisely.io import fs
37
35
  from supervisely.convert.pointcloud.lyft import lyft_helper
@@ -40,6 +38,8 @@ from datetime import datetime
40
38
  from supervisely.geometry.cuboid_3d import Cuboid3d
41
39
  from collections import defaultdict
42
40
 
41
+ # from supervisely.annotation.tag_meta import TagTargetType as TagTT
42
+
43
43
 
44
44
  class LyftEpisodesConverter(LyftConverter, PointcloudEpisodeConverter):
45
45
  """Converter for LYFT pointcloud episodes format."""
@@ -78,7 +78,7 @@ class LyftEpisodesConverter(LyftConverter, PointcloudEpisodeConverter):
78
78
  for scene_name, items in scene_name_to_items.items():
79
79
  token_to_obj = {}
80
80
  frames = []
81
- tags = [] # todo tags that belong to the whole scene if any
81
+ tags = [] # todo tags that belong to the scene if any
82
82
  # * Iterate over each sample in the scene
83
83
  for i, item in enumerate(items):
84
84
  ann = item.ann_data
@@ -93,23 +93,23 @@ class LyftEpisodesConverter(LyftConverter, PointcloudEpisodeConverter):
93
93
  obj_class_name = renamed_classes.get(class_name, class_name)
94
94
  obj_class = meta.get_obj_class(obj_class_name)
95
95
 
96
- # # * Get tags for the object
96
+ # * Get tags for the object
97
97
  # tag_names = [
98
- # lyft.get("attribute", attr_token).get("name", None)
99
- # for attr_token in instance_token["attribute_tokens"]
98
+ # lyft.get("attribute", attr_token).get("name", None)
99
+ # for attr_token in instance_token["attribute_tokens"]
100
100
  # ]
101
101
  # if len(tag_names) > 0 and all(
102
- # [tag_name is not None for tag_name in tag_names]
102
+ # [tag_name is not None for tag_name in tag_names]
103
103
  # ):
104
- # tags = [TagMeta(tag_name, TagValueType.NONE) for tag_name in tag_names]
105
- # tag_meta_names = [renamed_tags.get(name, name) for name in tag_names]
106
- # tag_metas = [
107
- # meta.get_tag_meta(tag_meta_name) for tag_meta_name in tag_meta_names
108
- # ]
109
- # obj_tags = PointcloudEpisodeTagCollection(
110
- # [PointcloudEpisodeTag(tag_meta, None) for tag_meta in tag_metas]
111
- # ) # todo: implement after fixing
112
- obj_tags = None
104
+ # tags = [TagMeta(tag_name, TagValueType.NONE) for tag_name in tag_names]
105
+ # tag_meta_names = [renamed_tags.get(name, name) for name in tag_names]
106
+ # tag_metas = [
107
+ # meta.get_tag_meta(tag_meta_name) for tag_meta_name in tag_meta_names
108
+ # ]
109
+ # obj_tags = PointcloudEpisodeTagCollection(
110
+ # [PointcloudEpisodeTag(tag_meta, None) for tag_meta in tag_metas]
111
+ # )
112
+ obj_tags = None # todo remove after fixing tags
113
113
  pcd_ep_obj = PointcloudEpisodeObject(obj_class, obj_tags)
114
114
  # * Assign the object to the starting token
115
115
  token_to_obj[instance_token["token"]] = pcd_ep_obj
@@ -137,9 +137,10 @@ class LyftEpisodesConverter(LyftConverter, PointcloudEpisodeConverter):
137
137
  def upload_dataset(self, api: Api, dataset_id: int, batch_size: int = 1, log_progress=True):
138
138
  unique_names = {name for item in self._items for name in item.ann_data["names"]}
139
139
  tag_names = {tag["name"] for tag in self._lyft.attribute}
140
+ target_type = None # TagTT.GLOBAL # todo remove after fixing tags
140
141
  self._meta = ProjectMeta(
141
142
  [ObjClass(name, Cuboid3d) for name in unique_names],
142
- [TagMeta(tag, TagValueType.NONE) for tag in tag_names],
143
+ [TagMeta(tag, TagValueType.NONE, target_type=target_type) for tag in tag_names],
143
144
  )
144
145
  meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
145
146
 
@@ -234,9 +235,7 @@ class LyftEpisodesConverter(LyftConverter, PointcloudEpisodeConverter):
234
235
  )
235
236
  except Exception as e:
236
237
  error_msg = getattr(getattr(e, "response", e), "text", str(e))
237
- logger.warn(
238
- f"Failed to upload annotation for scene: {scene}. Message: {error_msg}"
239
- )
238
+ logger.warn(f"Failed to upload annotation for scene: {scene}. Message: {error_msg}")
240
239
  logger.info(f"Dataset ID:{current_dataset_id} has been successfully uploaded.")
241
240
 
242
241
  if log_progress:
@@ -0,0 +1,305 @@
1
+ from os import path as osp
2
+ from pathlib import Path
3
+ from typing import Dict, Optional
4
+
5
+ import supervisely.convert.pointcloud_episodes.nuscenes_conv.nuscenes_helper as helpers
6
+ import supervisely.io.fs as fs
7
+ from supervisely._utils import is_development
8
+ from supervisely.annotation.obj_class import ObjClass
9
+ from supervisely.annotation.tag_meta import TagMeta, TagValueType
10
+ from supervisely.api.api import Api, ApiField
11
+ from supervisely.convert.base_converter import AvailablePointcloudConverters
12
+ from supervisely.convert.pointcloud_episodes.pointcloud_episodes_converter import (
13
+ PointcloudEpisodeConverter,
14
+ )
15
+ from supervisely.geometry.cuboid_3d import Cuboid3d
16
+ from supervisely.pointcloud_annotation.pointcloud_episode_annotation import (
17
+ PointcloudEpisodeAnnotation,
18
+ )
19
+ from supervisely.pointcloud_annotation.pointcloud_episode_frame import (
20
+ PointcloudEpisodeFrame,
21
+ )
22
+ from supervisely.pointcloud_annotation.pointcloud_episode_frame_collection import (
23
+ PointcloudEpisodeFrameCollection,
24
+ )
25
+ from supervisely.pointcloud_annotation.pointcloud_episode_object import (
26
+ PointcloudEpisodeObject,
27
+ )
28
+ from supervisely.pointcloud_annotation.pointcloud_episode_object_collection import (
29
+ PointcloudEpisodeObjectCollection,
30
+ )
31
+ from supervisely.pointcloud_annotation.pointcloud_episode_tag_collection import (
32
+ PointcloudEpisodeTagCollection,
33
+ )
34
+ from supervisely.pointcloud_annotation.pointcloud_figure import PointcloudFigure
35
+ from supervisely.project.project_meta import ProjectMeta
36
+ from supervisely.sly_logger import logger
37
+ from supervisely.tiny_timer import TinyTimer
38
+
39
+
40
+ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
41
+ """Converter for NuScenes pointcloud episodes format."""
42
+
43
+ def __init__(
44
+ self,
45
+ input_data: str,
46
+ labeling_interface: str,
47
+ upload_as_links: bool,
48
+ remote_files_map: Optional[Dict[str, str]] = None,
49
+ ):
50
+ super().__init__(input_data, labeling_interface, upload_as_links, remote_files_map)
51
+ self._nuscenes = None
52
+
53
+ def __str__(self) -> str:
54
+ return AvailablePointcloudConverters.NUSCENES
55
+
56
+ def validate_format(self) -> bool:
57
+ try:
58
+ from nuscenes import NuScenes
59
+ except ImportError:
60
+ logger.warning("Please, run 'pip install nuscenes-devkit' to import NuScenes data.")
61
+ return False
62
+
63
+ def filter_fn(path):
64
+ return all(
65
+ [
66
+ (Path(path) / name).exists()
67
+ for name in ["maps", "samples", "sweeps", "v1.0-mini"]
68
+ ]
69
+ )
70
+
71
+ try:
72
+ input_path = [d for d in fs.dirs_filter(self._input_data, filter_fn)].pop()
73
+ except IndexError:
74
+ return False
75
+
76
+ sample_dir = input_path + "/samples/"
77
+ if any([not fs.dir_exists(f"{sample_dir}/{d}") for d in helpers.DIR_NAMES]):
78
+ return False
79
+
80
+ sweeps_dir = input_path + "/sweeps/"
81
+ if any([not fs.dir_exists(f"{sweeps_dir}/{d}") for d in helpers.DIR_NAMES]):
82
+ return False
83
+
84
+ ann_dir = input_path + "/v1.0-mini/"
85
+ if any([not fs.file_exists(f"{ann_dir}/{d}.json") for d in helpers.TABLE_NAMES]):
86
+ return False
87
+
88
+ try:
89
+ t = TinyTimer()
90
+ nuscenes = NuScenes(dataroot=input_path, verbose=False)
91
+ self._nuscenes: NuScenes = nuscenes
92
+ logger.info(f"NuScenes initialization took {t.get_sec():.3f} sec")
93
+ except Exception as e:
94
+ logger.debug(f"Failed to initialize NuScenes: {e}")
95
+ return False
96
+
97
+ return True
98
+
99
+ def to_supervisely(
100
+ self,
101
+ scene_samples,
102
+ meta: ProjectMeta,
103
+ renamed_classes: dict = {},
104
+ renamed_tags: dict = {},
105
+ ) -> PointcloudEpisodeAnnotation:
106
+ token_to_obj = {}
107
+ frames = []
108
+ tags = []
109
+ for sample_i, sample in enumerate(scene_samples):
110
+ figures = []
111
+ for obj in sample.anns:
112
+ instance_token = obj.instance_token
113
+ class_name = obj.category
114
+ parent_obj_token = obj.parent_token
115
+ parent_object = None
116
+ if parent_obj_token == "":
117
+ # * Create a new object
118
+ obj_class_name = renamed_classes.get(class_name, class_name)
119
+ obj_class = meta.get_obj_class(obj_class_name)
120
+ obj_tags = None # ! TODO: fix tags
121
+ pcd_ep_obj = PointcloudEpisodeObject(obj_class, obj_tags)
122
+ # * Assign the object to the starting token
123
+ token_to_obj[instance_token] = pcd_ep_obj
124
+ parent_object = pcd_ep_obj
125
+ else:
126
+ # * -> Figure has a parent object, get it
127
+ token_to_obj[instance_token] = token_to_obj[parent_obj_token]
128
+ parent_object = token_to_obj[parent_obj_token]
129
+ geom = obj.to_supervisely()
130
+ pcd_figure = PointcloudFigure(parent_object, geom, sample_i)
131
+ figures.append(pcd_figure)
132
+ frame = PointcloudEpisodeFrame(sample_i, figures)
133
+ frames.append(frame)
134
+ tag_collection = PointcloudEpisodeTagCollection(tags) if len(tags) > 0 else None
135
+ return PointcloudEpisodeAnnotation(
136
+ len(frames),
137
+ PointcloudEpisodeObjectCollection(list(set(token_to_obj.values()))),
138
+ PointcloudEpisodeFrameCollection(frames),
139
+ tag_collection,
140
+ )
141
+
142
+ def upload_dataset(self, api: Api, dataset_id: int, batch_size: int = 1, log_progress=True):
143
+ nuscenes = self._nuscenes
144
+
145
+ tag_metas = [TagMeta(attr["name"], TagValueType.NONE) for attr in nuscenes.attribute]
146
+ obj_classes = []
147
+ for category in nuscenes.category:
148
+ color = nuscenes.colormap[category["name"]]
149
+ description = category["description"]
150
+ if len(description) > 255:
151
+ # * Trim description to fit into 255 characters limit
152
+ sentences = description.split(".")
153
+ trimmed_description = ""
154
+ for sentence in sentences:
155
+ if len(trimmed_description) + len(sentence) + 1 > 255:
156
+ break
157
+ trimmed_description += sentence + "."
158
+ description = trimmed_description.strip()
159
+ obj_classes.append(ObjClass(category["name"], Cuboid3d, color, description=description))
160
+
161
+ self._meta = ProjectMeta(obj_classes, tag_metas)
162
+ meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
163
+
164
+ dataset_info = api.dataset.get_info_by_id(dataset_id)
165
+ scene_name_to_dataset = {}
166
+
167
+ scene_names = [scene["name"] for scene in nuscenes.scene]
168
+ scene_cnt = len(scene_names)
169
+ total_sample_cnt = sum([scene["nbr_samples"] for scene in nuscenes.scene])
170
+
171
+ multiple_scenes = len(scene_names) > 1
172
+ if multiple_scenes:
173
+ logger.info(f"Found {scene_cnt} scenes ({total_sample_cnt} samples) in the input data.")
174
+ # * Create a nested dataset for each scene
175
+ for name in scene_names:
176
+ ds = api.dataset.create(
177
+ dataset_info.project_id,
178
+ name,
179
+ change_name_if_conflict=True,
180
+ parent_id=dataset_id,
181
+ )
182
+ scene_name_to_dataset[name] = ds
183
+ else:
184
+ scene_name_to_dataset[scene_names[0]] = dataset_info
185
+
186
+ if log_progress:
187
+ progress, progress_cb = self.get_progress(total_sample_cnt, "Converting episode scenes...")
188
+ else:
189
+ progress_cb = None
190
+
191
+ for scene in nuscenes.scene:
192
+ current_dataset_id = scene_name_to_dataset[scene["name"]].id
193
+
194
+ log = nuscenes.get("log", scene["log_token"])
195
+ sample_token = scene["first_sample_token"]
196
+
197
+ # * Extract scene's samples
198
+ scene_samples = []
199
+ for i in range(scene["nbr_samples"]):
200
+ sample = nuscenes.get("sample", sample_token)
201
+ lidar_path, boxes, _ = nuscenes.get_sample_data(sample["data"]["LIDAR_TOP"])
202
+ if not osp.exists(lidar_path):
203
+ logger.warning(f'Scene "{scene["name"]}" has no LIDAR data.')
204
+ continue
205
+
206
+ timestamp = sample["timestamp"]
207
+ anns = []
208
+ for box, name, inst_token in helpers.Sample.generate_boxes(nuscenes, boxes):
209
+ current_instance_token = inst_token["token"]
210
+ parent_token = inst_token["prev"]
211
+
212
+ # get category, attributes and visibility
213
+ ann = nuscenes.get("sample_annotation", current_instance_token)
214
+ category = ann["category_name"]
215
+ attributes = [
216
+ nuscenes.get("attribute", attr)["name"] for attr in ann["attribute_tokens"]
217
+ ]
218
+ visibility = nuscenes.get("visibility", ann["visibility_token"])["level"]
219
+
220
+ anns.append(
221
+ helpers.AnnotationObject(
222
+ name,
223
+ box,
224
+ current_instance_token,
225
+ parent_token,
226
+ category,
227
+ attributes,
228
+ visibility,
229
+ )
230
+ )
231
+
232
+ # get camera data
233
+ sample_data = nuscenes.get("sample_data", sample["data"]["LIDAR_TOP"])
234
+ cal_sensor = nuscenes.get(
235
+ "calibrated_sensor", sample_data["calibrated_sensor_token"]
236
+ )
237
+ ego_pose = nuscenes.get("ego_pose", sample_data["ego_pose_token"])
238
+
239
+ camera_data = [
240
+ helpers.CamData(nuscenes, sensor, token, cal_sensor, ego_pose)
241
+ for sensor, token in sample["data"].items()
242
+ if sensor.startswith("CAM")
243
+ ]
244
+ scene_samples.append(helpers.Sample(timestamp, lidar_path, anns, camera_data))
245
+ sample_token = sample["next"]
246
+
247
+ # * Convert and upload pointclouds
248
+ frame_to_pointcloud_ids = {}
249
+ for idx, sample in enumerate(scene_samples):
250
+ pcd_path = sample.convert_lidar_to_supervisely()
251
+
252
+ pcd_name = fs.get_file_name(pcd_path)
253
+ pcd_meta = {
254
+ "frame": idx,
255
+ "vehicle": log["vehicle"],
256
+ "date": log["date_captured"],
257
+ "location": log["location"],
258
+ "description": scene["description"],
259
+ }
260
+ info = api.pointcloud_episode.upload_path(
261
+ current_dataset_id, pcd_name, pcd_path, pcd_meta
262
+ )
263
+ fs.silent_remove(pcd_path)
264
+
265
+ pcd_id = info.id
266
+ frame_to_pointcloud_ids[idx] = pcd_id
267
+
268
+ # * Upload related images
269
+ image_jsons = []
270
+ camera_names = []
271
+ for img_path, rimage_info in [
272
+ data.get_info(sample.timestamp) for data in sample.cam_data
273
+ ]:
274
+ img = api.pointcloud_episode.upload_related_image(img_path)
275
+ image_jsons.append(
276
+ {
277
+ ApiField.ENTITY_ID: pcd_id,
278
+ ApiField.NAME: rimage_info[ApiField.NAME],
279
+ ApiField.HASH: img,
280
+ ApiField.META: rimage_info[ApiField.META],
281
+ }
282
+ )
283
+ camera_names.append(rimage_info[ApiField.META]["deviceId"])
284
+ if len(image_jsons) > 0:
285
+ api.pointcloud_episode.add_related_images(image_jsons, camera_names)
286
+
287
+ if log_progress:
288
+ progress_cb(1)
289
+
290
+ # * Convert and upload annotations
291
+ pcd_ann = self.to_supervisely(scene_samples, meta, renamed_classes, renamed_tags)
292
+ try:
293
+ api.pointcloud_episode.annotation.append(
294
+ current_dataset_id, pcd_ann, frame_to_pointcloud_ids
295
+ )
296
+ logger.info(f"Dataset ID:{current_dataset_id} has been successfully uploaded.")
297
+ except Exception as e:
298
+ error_msg = getattr(getattr(e, "response", e), "text", str(e))
299
+ logger.warning(
300
+ f"Failed to upload annotation for scene: {scene['name']}. Message: {error_msg}"
301
+ )
302
+
303
+ if log_progress:
304
+ if is_development():
305
+ progress.close()
@@ -0,0 +1,265 @@
1
+ from datetime import datetime
2
+ from os import path as osp
3
+ from pathlib import Path
4
+ from typing import List
5
+
6
+ import numpy as np
7
+
8
+ from supervisely import fs, logger
9
+ from supervisely.geometry.cuboid_3d import Cuboid3d, Vector3d
10
+
11
+ DIR_NAMES = [
12
+ "CAM_BACK",
13
+ "CAM_BACK_LEFT",
14
+ "CAM_BACK_RIGHT",
15
+ "CAM_FRONT",
16
+ "CAM_FRONT_LEFT",
17
+ "CAM_FRONT_RIGHT",
18
+ "LIDAR_TOP",
19
+ "RADAR_FRONT",
20
+ "RADAR_FRONT_LEFT",
21
+ "RADAR_FRONT_RIGHT",
22
+ "RADAR_BACK_LEFT",
23
+ "RADAR_BACK_RIGHT",
24
+ ]
25
+
26
+ TABLE_NAMES = [
27
+ "category",
28
+ "attribute",
29
+ "visibility",
30
+ "instance",
31
+ "sensor",
32
+ "calibrated_sensor",
33
+ "ego_pose",
34
+ "log",
35
+ "scene",
36
+ "sample",
37
+ "sample_data",
38
+ "sample_annotation",
39
+ "map",
40
+ ]
41
+
42
+
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
106
+
107
+
108
+ class AnnotationObject:
109
+ """
110
+ A class to represent an annotation object in the NuScenes dataset.
111
+
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.
128
+ """
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):
150
+ box = self.convert_nuscenes_to_BEVBox3D()
151
+
152
+ bbox = box.to_xyzwhlr()
153
+ dim = bbox[[3, 5, 4]]
154
+ pos = bbox[:3] + [0, 0, dim[1] / 2]
155
+ yaw = bbox[-1]
156
+
157
+ position = Vector3d(float(pos[0]), float(pos[1]), float(pos[2]))
158
+ rotation = Vector3d(0, 0, float(-yaw))
159
+ dimension = Vector3d(float(dim[0]), float(dim[2]), float(dim[1]))
160
+ geometry = Cuboid3d(position, rotation, dimension)
161
+
162
+ return geometry
163
+
164
+ def convert_nuscenes_to_BEVBox3D(self):
165
+ import open3d as o3d # pylint: disable=import-error
166
+
167
+ box = self.bbox
168
+ center = [float(box[0]), float(box[1]), float(box[2])]
169
+ size = [float(box[3]), float(box[5]), float(box[4])]
170
+ ry = float(box[6])
171
+ yaw = ry - np.pi
172
+ yaw = yaw - np.floor(yaw / (2 * np.pi) + 0.5) * 2 * np.pi
173
+ world_cam = None
174
+ return o3d.ml.datasets.utils.BEVBox3D(center, size, yaw, self.name, -1.0, world_cam)
175
+
176
+
177
+ class CamData:
178
+ """
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.
193
+ """
194
+
195
+ def __init__(self, nuscenes, sensor_name, sensor_token, cs_record, ego_record):
196
+ from nuscenes.utils.data_classes import ( # pylint: disable=import-error
197
+ transform_matrix,
198
+ )
199
+ from pyquaternion import Quaternion # pylint: disable=import-error
200
+
201
+ img_path, boxes, cam_intrinsic = nuscenes.get_sample_data(sensor_token)
202
+ if not osp.exists(img_path):
203
+ return None
204
+
205
+ sd_record_cam = nuscenes.get("sample_data", sensor_token)
206
+ cs_record_cam = nuscenes.get("calibrated_sensor", sd_record_cam["calibrated_sensor_token"])
207
+ ego_record_cam = nuscenes.get("ego_pose", sd_record_cam["ego_pose_token"])
208
+ lid_to_ego = transform_matrix(
209
+ cs_record["translation"],
210
+ Quaternion(cs_record["rotation"]),
211
+ inverse=False,
212
+ )
213
+ lid_ego_to_world = transform_matrix(
214
+ ego_record["translation"],
215
+ Quaternion(ego_record["rotation"]),
216
+ inverse=False,
217
+ )
218
+ world_to_cam_ego = transform_matrix(
219
+ ego_record_cam["translation"],
220
+ Quaternion(ego_record_cam["rotation"]),
221
+ inverse=True,
222
+ )
223
+ ego_to_cam = transform_matrix(
224
+ cs_record_cam["translation"],
225
+ Quaternion(cs_record_cam["rotation"]),
226
+ inverse=True,
227
+ )
228
+ velo_to_cam = np.dot(
229
+ ego_to_cam, np.dot(world_to_cam_ego, np.dot(lid_ego_to_world, lid_to_ego))
230
+ )
231
+ velo_to_cam_rot = velo_to_cam[:3, :3]
232
+ velo_to_cam_trans = velo_to_cam[:3, 3]
233
+
234
+ self.name = sensor_name
235
+ self.path = str(img_path)
236
+ self.imsize = (sd_record_cam["width"], sd_record_cam["height"])
237
+ self.extrinsic = np.hstack((velo_to_cam_rot, velo_to_cam_trans.reshape(3, 1)))
238
+ self.intrinsic = np.asarray(cs_record_cam["camera_intrinsic"])
239
+
240
+ def get_info(self, timestamp):
241
+ """
242
+ Retrieves information about the image and its metadata.
243
+
244
+ Args:
245
+ timestamp (int): The timestamp associated with the image.
246
+
247
+ Returns:
248
+ tuple: A tuple containing the image path and a dictionary with image metadata.
249
+ """
250
+ sensors_to_skip = ["_intrinsic", "_extrinsic", "_imsize"]
251
+ if not any([self.name.endswith(s) for s in sensors_to_skip]):
252
+ image_name = fs.get_file_name_with_ext(self.path)
253
+ sly_path_img = osp.join(osp.dirname(self.path), image_name)
254
+ img_info = {
255
+ "name": image_name,
256
+ "meta": {
257
+ "deviceId": self.name,
258
+ "timestamp": timestamp,
259
+ "sensorsData": {
260
+ "extrinsicMatrix": list(self.extrinsic.flatten().astype(float)),
261
+ "intrinsicMatrix": list(self.intrinsic.flatten().astype(float)),
262
+ },
263
+ },
264
+ }
265
+ return (sly_path_img, img_info)