supervisely 6.73.438__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.
- supervisely/__init__.py +137 -1
- supervisely/_utils.py +81 -0
- supervisely/annotation/annotation.py +8 -2
- supervisely/annotation/json_geometries_map.py +14 -11
- supervisely/annotation/label.py +80 -3
- supervisely/api/annotation_api.py +14 -11
- supervisely/api/api.py +59 -38
- supervisely/api/app_api.py +11 -2
- supervisely/api/dataset_api.py +74 -12
- supervisely/api/entities_collection_api.py +10 -0
- supervisely/api/entity_annotation/figure_api.py +52 -4
- supervisely/api/entity_annotation/object_api.py +3 -3
- supervisely/api/entity_annotation/tag_api.py +63 -12
- supervisely/api/guides_api.py +210 -0
- supervisely/api/image_api.py +72 -1
- supervisely/api/labeling_job_api.py +83 -1
- supervisely/api/labeling_queue_api.py +33 -7
- supervisely/api/module_api.py +9 -0
- supervisely/api/project_api.py +71 -26
- supervisely/api/storage_api.py +3 -1
- supervisely/api/task_api.py +13 -2
- supervisely/api/team_api.py +4 -3
- supervisely/api/video/video_annotation_api.py +119 -3
- supervisely/api/video/video_api.py +65 -14
- supervisely/api/video/video_figure_api.py +24 -11
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +23 -7
- supervisely/app/development/development.py +18 -2
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +105 -0
- supervisely/app/fastapi/subapp.py +88 -42
- supervisely/app/fastapi/websocket.py +77 -9
- supervisely/app/singleton.py +21 -0
- supervisely/app/v1/app_service.py +18 -2
- supervisely/app/v1/constants.py +7 -1
- supervisely/app/widgets/__init__.py +6 -0
- supervisely/app/widgets/activity_feed/__init__.py +0 -0
- supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
- supervisely/app/widgets/activity_feed/style.css +78 -0
- supervisely/app/widgets/activity_feed/template.html +22 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
- supervisely/app/widgets/classes_list_selector/template.html +60 -93
- supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
- supervisely/app/widgets/classes_table/classes_table.py +1 -0
- supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
- supervisely/app/widgets/dialog/dialog.py +12 -0
- supervisely/app/widgets/dialog/template.html +2 -1
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
- supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
- supervisely/app/widgets/fast_table/fast_table.py +184 -60
- supervisely/app/widgets/fast_table/template.html +1 -1
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +564 -0
- supervisely/app/widgets/heatmap/script.js +533 -0
- supervisely/app/widgets/heatmap/style.css +233 -0
- supervisely/app/widgets/heatmap/template.html +21 -0
- supervisely/app/widgets/modal/__init__.py +0 -0
- supervisely/app/widgets/modal/modal.py +198 -0
- supervisely/app/widgets/modal/template.html +10 -0
- supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
- supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
- supervisely/app/widgets/radio_tabs/template.html +1 -0
- supervisely/app/widgets/select/select.py +6 -3
- supervisely/app/widgets/select_class/__init__.py +0 -0
- supervisely/app/widgets/select_class/select_class.py +363 -0
- supervisely/app/widgets/select_class/template.html +50 -0
- supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
- supervisely/app/widgets/select_tag/__init__.py +0 -0
- supervisely/app/widgets/select_tag/select_tag.py +352 -0
- supervisely/app/widgets/select_tag/template.html +64 -0
- supervisely/app/widgets/select_team/select_team.py +37 -4
- supervisely/app/widgets/select_team/template.html +4 -5
- supervisely/app/widgets/select_user/__init__.py +0 -0
- supervisely/app/widgets/select_user/select_user.py +270 -0
- supervisely/app/widgets/select_user/template.html +13 -0
- supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
- supervisely/app/widgets/select_workspace/template.html +9 -12
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/aug/aug.py +6 -2
- supervisely/convert/base_converter.py +1 -0
- supervisely/convert/converter.py +2 -2
- supervisely/convert/image/csv/csv_converter.py +24 -15
- supervisely/convert/image/image_converter.py +3 -1
- supervisely/convert/image/image_helper.py +48 -4
- supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
- supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
- supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
- supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
- supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
- supervisely/convert/pointcloud/las/las_converter.py +13 -1
- supervisely/convert/pointcloud/las/las_helper.py +110 -11
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
- supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
- supervisely/convert/video/__init__.py +1 -0
- supervisely/convert/video/multi_view/__init__.py +0 -0
- supervisely/convert/video/multi_view/multi_view.py +543 -0
- supervisely/convert/video/sly/sly_video_converter.py +359 -3
- supervisely/convert/video/video_converter.py +24 -4
- supervisely/convert/volume/dicom/dicom_converter.py +13 -5
- supervisely/convert/volume/dicom/dicom_helper.py +30 -18
- supervisely/geometry/constants.py +1 -0
- supervisely/geometry/geometry.py +4 -0
- supervisely/geometry/helpers.py +5 -1
- supervisely/geometry/oriented_bbox.py +676 -0
- supervisely/geometry/polyline_3d.py +110 -0
- supervisely/geometry/rectangle.py +2 -1
- supervisely/io/env.py +76 -1
- supervisely/io/fs.py +21 -0
- supervisely/nn/benchmark/base_evaluator.py +104 -11
- supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
- supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
- supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
- supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
- supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
- supervisely/nn/inference/cache.py +43 -18
- supervisely/nn/inference/gui/serving_gui_template.py +5 -2
- supervisely/nn/inference/inference.py +916 -222
- supervisely/nn/inference/inference_request.py +55 -10
- supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
- supervisely/nn/inference/predict_app/gui/gui.py +676 -488
- supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
- supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
- supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
- supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
- supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
- supervisely/nn/inference/predict_app/gui/utils.py +236 -119
- supervisely/nn/inference/predict_app/predict_app.py +2 -2
- supervisely/nn/inference/session.py +43 -35
- supervisely/nn/inference/tracking/bbox_tracking.py +118 -35
- supervisely/nn/inference/tracking/point_tracking.py +5 -1
- supervisely/nn/inference/tracking/tracker_interface.py +10 -1
- supervisely/nn/inference/uploader.py +139 -12
- supervisely/nn/live_training/__init__.py +7 -0
- supervisely/nn/live_training/api_server.py +111 -0
- supervisely/nn/live_training/artifacts_utils.py +243 -0
- supervisely/nn/live_training/checkpoint_utils.py +229 -0
- supervisely/nn/live_training/dynamic_sampler.py +44 -0
- supervisely/nn/live_training/helpers.py +14 -0
- supervisely/nn/live_training/incremental_dataset.py +146 -0
- supervisely/nn/live_training/live_training.py +497 -0
- supervisely/nn/live_training/loss_plateau_detector.py +111 -0
- supervisely/nn/live_training/request_queue.py +52 -0
- supervisely/nn/model/model_api.py +9 -0
- supervisely/nn/model/prediction.py +2 -1
- supervisely/nn/model/prediction_session.py +26 -14
- supervisely/nn/prediction_dto.py +19 -1
- supervisely/nn/tracker/base_tracker.py +11 -1
- supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
- supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
- supervisely/nn/tracker/botsort_tracker.py +94 -65
- supervisely/nn/tracker/utils.py +4 -5
- supervisely/nn/tracker/visualize.py +93 -93
- supervisely/nn/training/gui/classes_selector.py +16 -1
- supervisely/nn/training/gui/train_val_splits_selector.py +52 -31
- supervisely/nn/training/train_app.py +46 -31
- supervisely/project/data_version.py +115 -51
- supervisely/project/download.py +1 -1
- supervisely/project/pointcloud_episode_project.py +37 -8
- supervisely/project/pointcloud_project.py +30 -2
- supervisely/project/project.py +14 -2
- supervisely/project/project_meta.py +27 -1
- supervisely/project/project_settings.py +32 -18
- supervisely/project/versioning/__init__.py +1 -0
- supervisely/project/versioning/common.py +20 -0
- supervisely/project/versioning/schema_fields.py +35 -0
- supervisely/project/versioning/video_schema.py +221 -0
- supervisely/project/versioning/volume_schema.py +87 -0
- supervisely/project/video_project.py +717 -15
- supervisely/project/volume_project.py +623 -5
- supervisely/template/experiment/experiment.html.jinja +4 -4
- supervisely/template/experiment/experiment_generator.py +14 -21
- supervisely/template/live_training/__init__.py +0 -0
- supervisely/template/live_training/header.html.jinja +96 -0
- supervisely/template/live_training/live_training.html.jinja +51 -0
- supervisely/template/live_training/live_training_generator.py +464 -0
- supervisely/template/live_training/sly-style.css +402 -0
- supervisely/template/live_training/template.html.jinja +18 -0
- supervisely/versions.json +28 -26
- supervisely/video/sampling.py +39 -20
- supervisely/video/video.py +41 -12
- supervisely/video_annotation/video_figure.py +38 -4
- supervisely/video_annotation/video_object.py +29 -4
- supervisely/volume/stl_converter.py +2 -0
- supervisely/worker_api/agent_rpc.py +24 -1
- supervisely/worker_api/rpc_servicer.py +31 -7
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/METADATA +58 -40
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/RECORD +203 -155
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
- supervisely_lib/__init__.py +6 -1
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import uuid
|
|
2
3
|
from typing import Dict, List, Optional
|
|
3
4
|
|
|
4
5
|
import supervisely.convert.pointcloud_episodes.nuscenes_conv.nuscenes_helper as helpers
|
|
5
6
|
import supervisely.io.fs as fs
|
|
6
|
-
from supervisely import PointcloudAnnotation, PointcloudObject
|
|
7
|
+
from supervisely import KeyIdMap, PointcloudAnnotation, PointcloudObject
|
|
7
8
|
from supervisely._utils import is_development
|
|
8
9
|
from supervisely.annotation.obj_class import ObjClass
|
|
9
10
|
from supervisely.annotation.tag_meta import TagMeta, TagValueType
|
|
@@ -30,19 +31,6 @@ from supervisely.sly_logger import logger
|
|
|
30
31
|
class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
31
32
|
"""Converter for NuScenes pointcloud format."""
|
|
32
33
|
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
input_data: str,
|
|
36
|
-
labeling_interface: str,
|
|
37
|
-
upload_as_links: bool,
|
|
38
|
-
remote_files_map: Optional[Dict[str, str]] = None,
|
|
39
|
-
):
|
|
40
|
-
super().__init__(input_data, labeling_interface, upload_as_links, remote_files_map)
|
|
41
|
-
self._nuscenes = None
|
|
42
|
-
|
|
43
|
-
def __str__(self) -> str:
|
|
44
|
-
return AvailablePointcloudConverters.NUSCENES
|
|
45
|
-
|
|
46
34
|
def to_supervisely(
|
|
47
35
|
self,
|
|
48
36
|
scene_sample: helpers.Sample,
|
|
@@ -69,14 +57,21 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
69
57
|
return PointcloudAnnotation(PointcloudObjectCollection(objs), figures)
|
|
70
58
|
|
|
71
59
|
def upload_dataset(self, api: Api, dataset_id: int, batch_size: int = 1, log_progress=True):
|
|
72
|
-
nuscenes =
|
|
60
|
+
from nuscenes.nuscenes import NuScenes # pylint: disable=import-error
|
|
61
|
+
|
|
62
|
+
nuscenes: NuScenes = self._nuscenes
|
|
63
|
+
|
|
64
|
+
key_id_map = KeyIdMap()
|
|
73
65
|
|
|
74
66
|
tag_metas = [TagMeta(attr["name"], TagValueType.NONE) for attr in nuscenes.attribute]
|
|
75
67
|
obj_classes = []
|
|
68
|
+
classes_token_map = {}
|
|
76
69
|
for category in nuscenes.category:
|
|
77
70
|
color = nuscenes.colormap[category["name"]]
|
|
78
71
|
description = helpers.trim_description(category["description"])
|
|
79
72
|
obj_classes.append(ObjClass(category["name"], Cuboid3d, color, description=description))
|
|
73
|
+
classes_token_map[category["token"]] = category["name"]
|
|
74
|
+
self._custom_data["classes_token_map"] = classes_token_map
|
|
80
75
|
|
|
81
76
|
self._meta = ProjectMeta(obj_classes, tag_metas)
|
|
82
77
|
meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
|
|
@@ -108,6 +103,7 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
108
103
|
else:
|
|
109
104
|
progress_cb = None
|
|
110
105
|
|
|
106
|
+
self._custom_data["frame_token_map"] = {}
|
|
111
107
|
for scene in nuscenes.scene:
|
|
112
108
|
current_dataset_id = scene_name_to_dataset[scene["name"]].id
|
|
113
109
|
|
|
@@ -116,8 +112,10 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
116
112
|
|
|
117
113
|
# * Extract scene's samples
|
|
118
114
|
scene_samples: List[helpers.Sample] = []
|
|
115
|
+
frame_token_map = {}
|
|
119
116
|
for i in range(scene["nbr_samples"]):
|
|
120
117
|
sample = nuscenes.get("sample", sample_token)
|
|
118
|
+
frame_token_map[sample["token"]] = i
|
|
121
119
|
lidar_path, boxes, _ = nuscenes.get_sample_data(sample["data"]["LIDAR_TOP"])
|
|
122
120
|
if not os.path.exists(lidar_path):
|
|
123
121
|
logger.warning(f'Scene "{scene["name"]}" has no LIDAR data.')
|
|
@@ -137,9 +135,11 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
137
135
|
]
|
|
138
136
|
visibility = nuscenes.get("visibility", ann["visibility_token"])["level"]
|
|
139
137
|
|
|
138
|
+
ann_uuid = uuid.UUID(ann["token"])
|
|
140
139
|
ann = helpers.AnnotationObject(
|
|
141
140
|
name=name,
|
|
142
141
|
bbox=box,
|
|
142
|
+
token=ann_uuid,
|
|
143
143
|
instance_token=current_instance_token,
|
|
144
144
|
parent_token=parent_token,
|
|
145
145
|
category=category,
|
|
@@ -162,6 +162,7 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
162
162
|
]
|
|
163
163
|
scene_samples.append(helpers.Sample(timestamp, lidar_path, anns, camera_data))
|
|
164
164
|
sample_token = sample["next"]
|
|
165
|
+
self._custom_data["frame_token_map"][current_dataset_id] = frame_token_map
|
|
165
166
|
|
|
166
167
|
# * Convert and upload pointclouds w/ annotations
|
|
167
168
|
for idx, sample in enumerate(scene_samples):
|
|
@@ -182,7 +183,7 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
182
183
|
pcd_id = info.id
|
|
183
184
|
# * Upload pointcloud annotation
|
|
184
185
|
try:
|
|
185
|
-
api.pointcloud.annotation.append(pcd_id, pcd_ann)
|
|
186
|
+
api.pointcloud.annotation.append(pcd_id, pcd_ann, key_id_map)
|
|
186
187
|
except Exception as e:
|
|
187
188
|
error_msg = getattr(getattr(e, "response", e), "text", str(e))
|
|
188
189
|
logger.warning(
|
|
@@ -213,6 +214,16 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
213
214
|
|
|
214
215
|
logger.info(f"Dataset ID:{current_dataset_id} has been successfully uploaded.")
|
|
215
216
|
|
|
217
|
+
key_id_map = key_id_map.to_dict()
|
|
218
|
+
key_id_map.pop("tags")
|
|
219
|
+
key_id_map.pop("videos")
|
|
220
|
+
self._custom_data["key_id_map"] = key_id_map
|
|
221
|
+
|
|
222
|
+
project_id = dataset_info.project_id
|
|
223
|
+
current_custom_data = api.project.get_custom_data(project_id)
|
|
224
|
+
current_custom_data.update(self._custom_data)
|
|
225
|
+
api.project.update_custom_data(project_id, current_custom_data)
|
|
226
|
+
|
|
216
227
|
if log_progress:
|
|
217
228
|
if is_development():
|
|
218
229
|
progress.close()
|
|
@@ -3,6 +3,8 @@ import os
|
|
|
3
3
|
from typing import Dict, List, Optional, Set, Tuple
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
6
8
|
from supervisely import (
|
|
7
9
|
Api,
|
|
8
10
|
PointcloudAnnotation,
|
|
@@ -20,9 +22,11 @@ from supervisely.pointcloud.pointcloud import validate_ext as validate_pcd_ext
|
|
|
20
22
|
from supervisely.pointcloud_annotation.constants import OBJECT_KEY
|
|
21
23
|
from supervisely.video_annotation.key_id_map import KeyIdMap
|
|
22
24
|
|
|
25
|
+
CONVERTIBLE_EXTENSIONS = [".bin"]
|
|
26
|
+
|
|
23
27
|
|
|
24
28
|
class PointcloudConverter(BaseConverter):
|
|
25
|
-
allowed_exts = ALLOWED_POINTCLOUD_EXTENSIONS
|
|
29
|
+
allowed_exts = ALLOWED_POINTCLOUD_EXTENSIONS + CONVERTIBLE_EXTENSIONS
|
|
26
30
|
modality = "pointclouds"
|
|
27
31
|
|
|
28
32
|
class Item(BaseConverter.BaseItem):
|
|
@@ -252,8 +256,10 @@ class PointcloudConverter(BaseConverter):
|
|
|
252
256
|
rimg_dict[dir_name].append(full_path)
|
|
253
257
|
elif ext.lower() in self.allowed_exts:
|
|
254
258
|
try:
|
|
255
|
-
|
|
256
|
-
|
|
259
|
+
path = self._convert_to_pcd_if_needed(full_path, ext)
|
|
260
|
+
if not path:
|
|
261
|
+
continue
|
|
262
|
+
pcd_list.append(path)
|
|
257
263
|
except:
|
|
258
264
|
pass
|
|
259
265
|
else:
|
|
@@ -277,3 +283,85 @@ class PointcloudConverter(BaseConverter):
|
|
|
277
283
|
item.set_related_images((rimg_path, rimg_ann_path, rimg_fig_path))
|
|
278
284
|
items.append(item)
|
|
279
285
|
return items, only_modality_items, unsupported_exts
|
|
286
|
+
|
|
287
|
+
def _convert_to_pcd_if_needed(self, pcd_path: str, ext: str) -> Optional[str]:
|
|
288
|
+
"""Convert point cloud to .pcd format if it is in another supported format."""
|
|
289
|
+
if ext == ".pcd":
|
|
290
|
+
return pcd_path
|
|
291
|
+
elif ext == ".bin":
|
|
292
|
+
if not self._validate_bin_pointcloud(pcd_path):
|
|
293
|
+
logger.warning(
|
|
294
|
+
f"The .bin pointcloud file '{pcd_path}' is not valid. Skipping conversion to .pcd."
|
|
295
|
+
)
|
|
296
|
+
return None
|
|
297
|
+
pcd_output_path = pcd_path.replace(".bin", ".pcd")
|
|
298
|
+
self._convert_bin_to_pcd(pcd_path, pcd_output_path)
|
|
299
|
+
return pcd_output_path
|
|
300
|
+
|
|
301
|
+
def _validate_bin_pointcloud(self, bin_file: str) -> bool:
|
|
302
|
+
try:
|
|
303
|
+
data = np.fromfile(bin_file, dtype=np.float32)
|
|
304
|
+
if data.size == 0:
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
if data.size % 5 == 0:
|
|
308
|
+
points = data.reshape(-1, 5)
|
|
309
|
+
return bool(np.isfinite(points).all())
|
|
310
|
+
|
|
311
|
+
if data.size % 4 == 0:
|
|
312
|
+
points = data.reshape(-1, 4)
|
|
313
|
+
return bool(np.isfinite(points).all())
|
|
314
|
+
|
|
315
|
+
return False
|
|
316
|
+
except Exception:
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
def _convert_bin_to_pcd(self, bin_file: str, pcd_file: str) -> None:
|
|
320
|
+
"""Convert .bin point cloud file to .pcd format using open3d library (supports 4 or 5 floats per point)."""
|
|
321
|
+
try:
|
|
322
|
+
import open3d as o3d
|
|
323
|
+
except ImportError:
|
|
324
|
+
raise ImportError(
|
|
325
|
+
"open3d is not installed. Cannot convert .bin point cloud to .pcd format. "
|
|
326
|
+
"Please install open3d package to enable this feature."
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
raw = np.fromfile(bin_file, dtype=np.float32)
|
|
331
|
+
if raw.size % 5 == 0:
|
|
332
|
+
arr = raw.reshape(-1, 5)
|
|
333
|
+
points = arr[:, 0:3]
|
|
334
|
+
intensity = arr[:, 3]
|
|
335
|
+
elif raw.size % 4 == 0:
|
|
336
|
+
arr = raw.reshape(-1, 4)
|
|
337
|
+
points = arr[:, 0:3]
|
|
338
|
+
intensity = arr[:, 3]
|
|
339
|
+
else:
|
|
340
|
+
raise ValueError("The number of float32 values is not a multiple of 4 or 5.")
|
|
341
|
+
except ValueError as e:
|
|
342
|
+
raise Exception(
|
|
343
|
+
f"Incorrect data in the BIN pointcloud file: {bin_file}. "
|
|
344
|
+
f"The number of float32 values must be a multiple of 4 (x,y,z,intensity) "
|
|
345
|
+
f"or 5 (x,y,z,intensity,ring_index). Details: {e} "
|
|
346
|
+
"Please ensure that the binary file contains a valid number of elements to be "
|
|
347
|
+
"successfully reshaped into a (N, 4) or (N, 5) array.\n"
|
|
348
|
+
)
|
|
349
|
+
# normalize intensity to [0, 1] for Open3D colors
|
|
350
|
+
intensity = np.nan_to_num(intensity, nan=0.0, posinf=0.0, neginf=0.0)
|
|
351
|
+
if intensity.size > 0:
|
|
352
|
+
i_min = float(np.min(intensity))
|
|
353
|
+
i_max = float(np.max(intensity))
|
|
354
|
+
if i_max > i_min:
|
|
355
|
+
norm_intensity = (intensity - i_min) / (i_max - i_min)
|
|
356
|
+
else:
|
|
357
|
+
norm_intensity = np.zeros_like(intensity)
|
|
358
|
+
else:
|
|
359
|
+
norm_intensity = intensity
|
|
360
|
+
intensity_fake_rgb = np.zeros((norm_intensity.shape[0], 3))
|
|
361
|
+
intensity_fake_rgb[:, 0] = norm_intensity
|
|
362
|
+
pc = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points))
|
|
363
|
+
pc.colors = o3d.utility.Vector3dVector(intensity_fake_rgb)
|
|
364
|
+
o3d.io.write_point_cloud(pcd_file, pc)
|
|
365
|
+
pc = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points))
|
|
366
|
+
pc.colors = o3d.utility.Vector3dVector(intensity_fake_rgb)
|
|
367
|
+
o3d.io.write_point_cloud(pcd_file, pc)
|
|
@@ -1,4 +1,7 @@
|
|
|
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
7
|
from typing import Dict, List, Optional
|
|
@@ -37,19 +40,14 @@ from supervisely.pointcloud_annotation.pointcloud_figure import PointcloudFigure
|
|
|
37
40
|
from supervisely.project.project_meta import ProjectMeta
|
|
38
41
|
from supervisely.sly_logger import logger
|
|
39
42
|
from supervisely.tiny_timer import TinyTimer
|
|
43
|
+
from supervisely.video_annotation.key_id_map import KeyIdMap
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
43
47
|
"""Converter for NuScenes pointcloud episodes format."""
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
input_data: str,
|
|
48
|
-
labeling_interface: str,
|
|
49
|
-
upload_as_links: bool,
|
|
50
|
-
remote_files_map: Optional[Dict[str, str]] = None,
|
|
51
|
-
):
|
|
52
|
-
super().__init__(input_data, labeling_interface, upload_as_links, remote_files_map)
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
@@ -89,11 +89,13 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
89
89
|
logger.debug(f"Failed to initialize NuScenes: {e}")
|
|
90
90
|
return False
|
|
91
91
|
|
|
92
|
+
self._custom_data["nuscenes_version"] = version
|
|
93
|
+
self._custom_data["dataroot"] = input_path
|
|
92
94
|
return True
|
|
93
95
|
|
|
94
96
|
def to_supervisely(
|
|
95
97
|
self,
|
|
96
|
-
scene_samples:
|
|
98
|
+
scene_samples: Dict[str, helpers.Sample],
|
|
97
99
|
meta: ProjectMeta,
|
|
98
100
|
renamed_classes: dict = {},
|
|
99
101
|
renamed_tags: dict = {},
|
|
@@ -101,9 +103,13 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
101
103
|
token_to_obj = {}
|
|
102
104
|
frames = []
|
|
103
105
|
tags = []
|
|
104
|
-
|
|
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()):
|
|
105
110
|
figures = []
|
|
106
111
|
for obj in sample.anns:
|
|
112
|
+
ann_token = uuid.UUID(obj.token)
|
|
107
113
|
instance_token = obj.instance_token
|
|
108
114
|
class_name = obj.category
|
|
109
115
|
parent_obj_token = obj.parent_token
|
|
@@ -113,7 +119,9 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
113
119
|
obj_class_name = renamed_classes.get(class_name, class_name)
|
|
114
120
|
obj_class = meta.get_obj_class(obj_class_name)
|
|
115
121
|
obj_tags = None # ! TODO: fix tags
|
|
116
|
-
pcd_ep_obj = PointcloudEpisodeObject(
|
|
122
|
+
pcd_ep_obj = PointcloudEpisodeObject(
|
|
123
|
+
obj_class, obj_tags, uuid.UUID(instance_token)
|
|
124
|
+
)
|
|
117
125
|
# * Assign the object to the starting token
|
|
118
126
|
token_to_obj[instance_token] = pcd_ep_obj
|
|
119
127
|
parent_object = pcd_ep_obj
|
|
@@ -122,29 +130,41 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
122
130
|
token_to_obj[instance_token] = token_to_obj[parent_obj_token]
|
|
123
131
|
parent_object = token_to_obj[parent_obj_token]
|
|
124
132
|
geom = obj.to_supervisely()
|
|
125
|
-
pcd_figure = PointcloudFigure(parent_object, geom, sample_i)
|
|
133
|
+
pcd_figure = PointcloudFigure(parent_object, geom, sample_i, ann_token)
|
|
126
134
|
figures.append(pcd_figure)
|
|
135
|
+
frame_idx_to_scene_sample_token[sample_i] = token
|
|
127
136
|
frame = PointcloudEpisodeFrame(sample_i, figures)
|
|
128
137
|
frames.append(frame)
|
|
129
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)
|
|
130
141
|
return PointcloudEpisodeAnnotation(
|
|
131
142
|
len(frames),
|
|
132
143
|
PointcloudEpisodeObjectCollection(list(set(token_to_obj.values()))),
|
|
133
144
|
PointcloudEpisodeFrameCollection(frames),
|
|
134
145
|
tag_collection,
|
|
146
|
+
key=key_uuid,
|
|
135
147
|
)
|
|
136
148
|
|
|
137
149
|
def upload_dataset(self, api: Api, dataset_id: int, batch_size: int = 1, log_progress=True):
|
|
138
|
-
nuscenes =
|
|
150
|
+
from nuscenes import NuScenes # pylint: disable=import-error
|
|
151
|
+
|
|
152
|
+
nuscenes: NuScenes = self._nuscenes
|
|
153
|
+
key_id_map = KeyIdMap()
|
|
139
154
|
|
|
140
155
|
tag_metas = [TagMeta(attr["name"], TagValueType.NONE) for attr in nuscenes.attribute]
|
|
141
|
-
obj_classes =
|
|
156
|
+
obj_classes = {}
|
|
142
157
|
for category in nuscenes.category:
|
|
143
158
|
color = nuscenes.colormap[category["name"]]
|
|
144
159
|
description = helpers.trim_description(category.get("description", ""))
|
|
145
|
-
|
|
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()}
|
|
146
166
|
|
|
147
|
-
self._meta = ProjectMeta(obj_classes, tag_metas)
|
|
167
|
+
self._meta = ProjectMeta(list(obj_classes.values()), tag_metas)
|
|
148
168
|
meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
|
|
149
169
|
|
|
150
170
|
dataset_info = api.dataset.get_info_by_id(dataset_id)
|
|
@@ -178,12 +198,13 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
178
198
|
|
|
179
199
|
for scene in nuscenes.scene:
|
|
180
200
|
current_dataset_id = scene_name_to_dataset[scene["name"]].id
|
|
201
|
+
self._current_ds_id = current_dataset_id
|
|
181
202
|
|
|
182
203
|
log = nuscenes.get("log", scene["log_token"])
|
|
183
204
|
sample_token = scene["first_sample_token"]
|
|
184
205
|
|
|
185
206
|
# * Extract scene's samples
|
|
186
|
-
scene_samples:
|
|
207
|
+
scene_samples: Dict[str, helpers.Sample] = {}
|
|
187
208
|
for i in range(scene["nbr_samples"]):
|
|
188
209
|
sample = nuscenes.get("sample", sample_token)
|
|
189
210
|
lidar_path, boxes, _ = nuscenes.get_sample_data(sample["data"]["LIDAR_TOP"])
|
|
@@ -203,10 +224,12 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
203
224
|
nuscenes.get("attribute", attr)["name"] for attr in ann["attribute_tokens"]
|
|
204
225
|
]
|
|
205
226
|
visibility = nuscenes.get("visibility", ann["visibility_token"])["level"]
|
|
227
|
+
ann_token = ann["token"]
|
|
206
228
|
|
|
207
229
|
ann = helpers.AnnotationObject(
|
|
208
230
|
name=name,
|
|
209
231
|
bbox=box,
|
|
232
|
+
token=ann_token,
|
|
210
233
|
instance_token=current_instance_token,
|
|
211
234
|
parent_token=parent_token,
|
|
212
235
|
category=category,
|
|
@@ -227,12 +250,15 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
227
250
|
for sensor, token in sample["data"].items()
|
|
228
251
|
if sensor.startswith("CAM")
|
|
229
252
|
]
|
|
230
|
-
|
|
253
|
+
sample_token = sample["token"]
|
|
254
|
+
scene_samples[sample_token] = helpers.Sample(
|
|
255
|
+
timestamp, lidar_path, anns, camera_data
|
|
256
|
+
)
|
|
231
257
|
sample_token = sample["next"]
|
|
232
258
|
|
|
233
259
|
# * Convert and upload pointclouds
|
|
234
260
|
frame_to_pointcloud_ids = {}
|
|
235
|
-
for idx, sample in enumerate(scene_samples):
|
|
261
|
+
for idx, sample in enumerate(scene_samples.values()):
|
|
236
262
|
pcd_path = sample.convert_lidar_to_supervisely()
|
|
237
263
|
|
|
238
264
|
pcd_name = fs.get_file_name_with_ext(pcd_path)
|
|
@@ -275,9 +301,10 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
275
301
|
|
|
276
302
|
# * Convert and upload annotations
|
|
277
303
|
pcd_ann = self.to_supervisely(scene_samples, meta, renamed_classes, renamed_tags)
|
|
304
|
+
|
|
278
305
|
try:
|
|
279
306
|
api.pointcloud_episode.annotation.append(
|
|
280
|
-
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
|
|
281
308
|
)
|
|
282
309
|
logger.info(f"Dataset ID:{current_dataset_id} has been successfully uploaded.")
|
|
283
310
|
except Exception as e:
|
|
@@ -285,6 +312,15 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
285
312
|
logger.warning(
|
|
286
313
|
f"Failed to upload annotation for scene: {scene['name']}. Message: {error_msg}"
|
|
287
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)
|
|
288
324
|
|
|
289
325
|
if log_progress:
|
|
290
326
|
if is_development():
|
|
@@ -1,3 +1,4 @@
|
|
|
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
|
|
@@ -52,6 +53,7 @@ def trim_description(description: str, max_length: int = 255) -> str:
|
|
|
52
53
|
return description
|
|
53
54
|
|
|
54
55
|
|
|
56
|
+
@dataclass
|
|
55
57
|
class AnnotationObject:
|
|
56
58
|
"""
|
|
57
59
|
A class to represent an annotation object in the NuScenes dataset.
|
|
@@ -60,6 +62,8 @@ class AnnotationObject:
|
|
|
60
62
|
:type name: str
|
|
61
63
|
:param bbox: The bounding box coordinates in NuScenes format
|
|
62
64
|
:type bbox: np.ndarray
|
|
65
|
+
:param token: The unique token identifying the annotation object
|
|
66
|
+
:type token: str
|
|
63
67
|
:param instance_token: The instance token associated with the annotation object
|
|
64
68
|
:type instance_token: str
|
|
65
69
|
:param parent_token: The token of instance preceding the current object instance
|
|
@@ -71,25 +75,14 @@ class AnnotationObject:
|
|
|
71
75
|
:param visibility: The visibility level of the annotation object
|
|
72
76
|
:type visibility: str
|
|
73
77
|
"""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
attributes: List[str],
|
|
83
|
-
visibility: str,
|
|
84
|
-
):
|
|
85
|
-
self.name = name
|
|
86
|
-
self.bbox = bbox
|
|
87
|
-
self.instance_token = instance_token
|
|
88
|
-
self.parent_token = parent_token
|
|
89
|
-
|
|
90
|
-
self.category = category
|
|
91
|
-
self.attributes = attributes
|
|
92
|
-
self.visibility = visibility
|
|
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
|
|
93
86
|
|
|
94
87
|
def to_supervisely(self) -> Cuboid3d:
|
|
95
88
|
box = self.convert_nuscenes_to_BEVBox3D()
|
|
@@ -213,38 +206,19 @@ class CamData:
|
|
|
213
206
|
return (sly_path_img, img_info)
|
|
214
207
|
|
|
215
208
|
|
|
209
|
+
@dataclass
|
|
216
210
|
class Sample:
|
|
217
211
|
"""
|
|
218
212
|
A class to represent a sample from the NuScenes dataset.
|
|
219
213
|
"""
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
self._timestamp = datetime.utcfromtimestamp(timestamp / 1e6).isoformat()
|
|
229
|
-
self._lidar_path = lidar_path
|
|
230
|
-
self._anns = anns
|
|
231
|
-
self._cam_data = cam_data
|
|
232
|
-
|
|
233
|
-
@property
|
|
234
|
-
def timestamp(self) -> str:
|
|
235
|
-
return self._timestamp
|
|
236
|
-
|
|
237
|
-
@property
|
|
238
|
-
def lidar_path(self) -> str:
|
|
239
|
-
return self._lidar_path
|
|
240
|
-
|
|
241
|
-
@property
|
|
242
|
-
def anns(self) -> List[AnnotationObject]:
|
|
243
|
-
return self._anns
|
|
244
|
-
|
|
245
|
-
@property
|
|
246
|
-
def cam_data(self) -> List[CamData]:
|
|
247
|
-
return self._cam_data
|
|
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()
|
|
248
222
|
|
|
249
223
|
@staticmethod
|
|
250
224
|
def generate_boxes(nuscenes, boxes: List) -> Generator:
|
|
File without changes
|