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.
- supervisely/app/widgets/custom_models_selector/custom_models_selector.py +54 -44
- supervisely/convert/__init__.py +4 -1
- supervisely/convert/base_converter.py +2 -9
- supervisely/convert/pointcloud/nuscenes_conv/__init__.py +0 -0
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +227 -0
- supervisely/convert/pointcloud/pointcloud_converter.py +52 -1
- supervisely/convert/pointcloud/sly/sly_pointcloud_converter.py +8 -21
- supervisely/convert/pointcloud/sly/sly_pointcloud_helper.py +4 -2
- supervisely/convert/pointcloud_episodes/lyft/lyft_converter.py +19 -20
- supervisely/convert/pointcloud_episodes/nuscenes_conv/__init__.py +0 -0
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +305 -0
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +265 -0
- supervisely/convert/pointcloud_episodes/pointcloud_episodes_converter.py +82 -27
- supervisely/convert/pointcloud_episodes/sly/sly_pointcloud_episodes_converter.py +9 -8
- {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/METADATA +1 -1
- {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/RECORD +20 -15
- {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/LICENSE +0 -0
- {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/WHEEL +0 -0
- {supervisely-6.73.274.dist-info → supervisely-6.73.276.dist-info}/entry_points.txt +0 -0
- {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
|
|
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
|
-
#
|
|
96
|
+
# * Get tags for the object
|
|
97
97
|
# tag_names = [
|
|
98
|
-
#
|
|
99
|
-
#
|
|
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
|
-
#
|
|
102
|
+
# [tag_name is not None for tag_name in tag_names]
|
|
103
103
|
# ):
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
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:
|
|
File without changes
|
|
@@ -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)
|