supervisely 6.73.426__py3-none-any.whl → 6.73.428__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/api/annotation_api.py +1 -1
- supervisely/app/widgets/__init__.py +2 -0
- supervisely/app/widgets/input_tag/input_tag.py +102 -15
- supervisely/app/widgets/input_tag_list/__init__.py +0 -0
- supervisely/app/widgets/input_tag_list/input_tag_list.py +274 -0
- supervisely/app/widgets/input_tag_list/template.html +70 -0
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +16 -25
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +17 -29
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +143 -104
- supervisely/video/sampling.py +4 -1
- {supervisely-6.73.426.dist-info → supervisely-6.73.428.dist-info}/METADATA +1 -1
- {supervisely-6.73.426.dist-info → supervisely-6.73.428.dist-info}/RECORD +16 -13
- {supervisely-6.73.426.dist-info → supervisely-6.73.428.dist-info}/LICENSE +0 -0
- {supervisely-6.73.426.dist-info → supervisely-6.73.428.dist-info}/WHEEL +0 -0
- {supervisely-6.73.426.dist-info → supervisely-6.73.428.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.426.dist-info → supervisely-6.73.428.dist-info}/top_level.txt +0 -0
|
@@ -865,7 +865,7 @@ class AnnotationApi(ModuleApi):
|
|
|
865
865
|
return
|
|
866
866
|
if len(img_ids) != len(anns):
|
|
867
867
|
raise RuntimeError(
|
|
868
|
-
'
|
|
868
|
+
f'Lists "img_ids" and "anns" have different lengths: {len(img_ids)} != {len(anns)}.'
|
|
869
869
|
)
|
|
870
870
|
|
|
871
871
|
# use context to avoid redundant API calls
|
|
@@ -62,6 +62,7 @@ from supervisely.app.widgets.video_player.video_player import VideoPlayer
|
|
|
62
62
|
from supervisely.app.widgets.radio_group.radio_group import RadioGroup
|
|
63
63
|
from supervisely.app.widgets.switch.switch import Switch
|
|
64
64
|
from supervisely.app.widgets.input_tag.input_tag import InputTag
|
|
65
|
+
|
|
65
66
|
from supervisely.app.widgets.file_viewer.file_viewer import FileViewer
|
|
66
67
|
from supervisely.app.widgets.switch.switch import Switch
|
|
67
68
|
from supervisely.app.widgets.folder_thumbnail.folder_thumbnail import FolderThumbnail
|
|
@@ -152,6 +153,7 @@ from supervisely.app.widgets.bokeh.bokeh import Bokeh
|
|
|
152
153
|
from supervisely.app.widgets.run_app_button.run_app_button import RunAppButton
|
|
153
154
|
from supervisely.app.widgets.select_collection.select_collection import SelectCollection
|
|
154
155
|
from supervisely.app.widgets.sampling.sampling import Sampling
|
|
156
|
+
from supervisely.app.widgets.input_tag_list.input_tag_list import InputTagList
|
|
155
157
|
from supervisely.app.widgets.deploy_model.deploy_model import DeployModel
|
|
156
158
|
from supervisely.app.widgets.dropdown_checkbox_selector.dropdown_checkbox_selector import (
|
|
157
159
|
DropdownCheckboxSelector,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Dict, Union
|
|
1
|
+
from typing import Dict, Union, Callable
|
|
2
2
|
|
|
3
3
|
from supervisely.annotation.tag import Tag
|
|
4
4
|
from supervisely.annotation.tag_meta import TagMeta, TagValueType
|
|
@@ -30,6 +30,18 @@ VALUE_TYPES = [
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class InputTag(Widget):
|
|
33
|
+
"""Widget for inputting a single tag value based on its TagMeta information. Accepts various input types depending on the tag's value type. Returns the tag value when requested.
|
|
34
|
+
|
|
35
|
+
:param tag_meta: Tag metadata
|
|
36
|
+
:type tag_meta: TagMeta
|
|
37
|
+
:param max_width: Maximum width of the widget in pixels, defaults to 300
|
|
38
|
+
:type max_width: int
|
|
39
|
+
:param hide_switch: Whether to hide the activation switch, defaults to False
|
|
40
|
+
:type hide_switch: bool
|
|
41
|
+
:param widget_id: Unique identifier for the widget, defaults to None
|
|
42
|
+
:type widget_id: int
|
|
43
|
+
"""
|
|
44
|
+
|
|
33
45
|
def __init__(
|
|
34
46
|
self,
|
|
35
47
|
tag_meta: TagMeta,
|
|
@@ -74,35 +86,79 @@ class InputTag(Widget):
|
|
|
74
86
|
self._input_widgets[str(TagValueType.ANY_STRING)] = Input(type="textarea")
|
|
75
87
|
self._input_widgets[str(TagValueType.ONEOF_STRING)] = RadioGroup(items=[])
|
|
76
88
|
|
|
77
|
-
def _get_max_width(self, value):
|
|
89
|
+
def _get_max_width(self, value) -> str:
|
|
90
|
+
"""Get the maximum width for the widget.
|
|
91
|
+
Ensures the width is at least 150 pixels.
|
|
92
|
+
|
|
93
|
+
:param value: Desired maximum width in pixels.
|
|
94
|
+
:type value: int
|
|
95
|
+
:return: Maximum width for the widget
|
|
96
|
+
:rtype: str
|
|
97
|
+
"""
|
|
78
98
|
if value < 150:
|
|
79
99
|
value = 150
|
|
80
100
|
return f"{value}px"
|
|
81
101
|
|
|
82
|
-
def get_tag_meta(self):
|
|
102
|
+
def get_tag_meta(self) -> TagMeta:
|
|
103
|
+
"""Get the tag metadata.
|
|
104
|
+
|
|
105
|
+
:return: Tag metadata
|
|
106
|
+
:rtype: TagMeta
|
|
107
|
+
"""
|
|
83
108
|
return self._tag_meta
|
|
84
109
|
|
|
85
|
-
def activate(self):
|
|
110
|
+
def activate(self) -> None:
|
|
111
|
+
"""Activate the widget."""
|
|
86
112
|
self._activation_widget.on()
|
|
87
113
|
|
|
88
|
-
def deactivate(self):
|
|
114
|
+
def deactivate(self) -> None:
|
|
115
|
+
"""Deactivate the widget."""
|
|
89
116
|
self._activation_widget.off()
|
|
90
117
|
|
|
91
|
-
def is_active(self):
|
|
118
|
+
def is_active(self) -> bool:
|
|
119
|
+
"""Check if the widget is active.
|
|
120
|
+
|
|
121
|
+
:return: True if the widget is active, False otherwise
|
|
122
|
+
:rtype: bool
|
|
123
|
+
"""
|
|
92
124
|
return self._activation_widget.is_switched()
|
|
93
125
|
|
|
94
126
|
@property
|
|
95
|
-
def value(self):
|
|
127
|
+
def value(self) -> Union[str, int, None]:
|
|
128
|
+
"""Get the current value of the tag.
|
|
129
|
+
|
|
130
|
+
:return: Current value of the tag
|
|
131
|
+
:rtype: Union[str, int, None]
|
|
132
|
+
"""
|
|
96
133
|
return self._get_value()
|
|
97
134
|
|
|
98
135
|
@value.setter
|
|
99
|
-
def value(self, value):
|
|
136
|
+
def value(self, value: Union[str, int, None]) -> None:
|
|
137
|
+
"""Set the current value of the tag.
|
|
138
|
+
|
|
139
|
+
:param value: Current value of the tag
|
|
140
|
+
:type value: Union[str, int, None]
|
|
141
|
+
:return: None
|
|
142
|
+
"""
|
|
100
143
|
self._set_value(value)
|
|
101
144
|
|
|
102
|
-
def is_valid_value(self, value):
|
|
145
|
+
def is_valid_value(self, value: Union[str, int, None]) -> bool:
|
|
146
|
+
"""Check if the value is valid for the tag.
|
|
147
|
+
|
|
148
|
+
:param value: Value to check
|
|
149
|
+
:type value: Union[str, int, None]
|
|
150
|
+
:return: True if the value is valid, False otherwise
|
|
151
|
+
:rtype: bool
|
|
152
|
+
"""
|
|
103
153
|
return self._tag_meta.is_valid_value(value)
|
|
104
154
|
|
|
105
|
-
def set(self, tag: Union[Tag, None]):
|
|
155
|
+
def set(self, tag: Union[Tag, None]) -> None:
|
|
156
|
+
"""Set the tag value.
|
|
157
|
+
|
|
158
|
+
:param tag: Tag to set
|
|
159
|
+
:type tag: Union[Tag, None]
|
|
160
|
+
:return: None
|
|
161
|
+
"""
|
|
106
162
|
if tag is None:
|
|
107
163
|
self._set_default_value()
|
|
108
164
|
self.deactivate()
|
|
@@ -110,13 +166,23 @@ class InputTag(Widget):
|
|
|
110
166
|
self._set_value(tag.value)
|
|
111
167
|
self.activate()
|
|
112
168
|
|
|
113
|
-
def get_tag(self):
|
|
169
|
+
def get_tag(self) -> Union[Tag, None]:
|
|
170
|
+
"""Get the current tag.
|
|
171
|
+
|
|
172
|
+
:return: Current tag
|
|
173
|
+
:rtype: Union[Tag, None]
|
|
174
|
+
"""
|
|
114
175
|
if not self._hide_switch and not self.is_active():
|
|
115
176
|
return None
|
|
116
177
|
tag_value = self._get_value()
|
|
117
178
|
return Tag(self._tag_meta, tag_value)
|
|
118
179
|
|
|
119
|
-
def _get_value(self):
|
|
180
|
+
def _get_value(self) -> Union[str, int, None]:
|
|
181
|
+
"""Get the current value of the tag.
|
|
182
|
+
|
|
183
|
+
:return: Current value of the tag
|
|
184
|
+
:rtype: Union[str, int, None]
|
|
185
|
+
"""
|
|
120
186
|
input_widget = self._input_widgets[self._tag_meta.value_type]
|
|
121
187
|
if isinstance(input_widget, Empty):
|
|
122
188
|
return None
|
|
@@ -143,7 +209,12 @@ class InputTag(Widget):
|
|
|
143
209
|
if isinstance(input_widget, RadioGroup):
|
|
144
210
|
input_widget.set_value(None)
|
|
145
211
|
|
|
146
|
-
def get_json_data(self):
|
|
212
|
+
def get_json_data(self) -> Dict:
|
|
213
|
+
"""Get the JSON representation of the tag.
|
|
214
|
+
|
|
215
|
+
:return: JSON representation of the tag
|
|
216
|
+
:rtype: Dict
|
|
217
|
+
"""
|
|
147
218
|
return {
|
|
148
219
|
"name": self._name,
|
|
149
220
|
"valueType": self._value_type_name,
|
|
@@ -151,9 +222,19 @@ class InputTag(Widget):
|
|
|
151
222
|
}
|
|
152
223
|
|
|
153
224
|
def get_json_state(self) -> Dict:
|
|
225
|
+
"""Get the JSON representation of the tag state.
|
|
226
|
+
|
|
227
|
+
:return: JSON representation of the tag state
|
|
228
|
+
:rtype: Dict
|
|
229
|
+
"""
|
|
154
230
|
return None
|
|
155
231
|
|
|
156
|
-
def value_changed(self, func):
|
|
232
|
+
def value_changed(self, func) -> Callable:
|
|
233
|
+
"""Decorator to register a callback function for selection changes.
|
|
234
|
+
|
|
235
|
+
:param func: Callback function
|
|
236
|
+
:type func: Callable
|
|
237
|
+
"""
|
|
157
238
|
for value_type, input_widget in self._input_widgets.items():
|
|
158
239
|
if isinstance(input_widget, Empty):
|
|
159
240
|
self._value_changed_callbacks[value_type] = func
|
|
@@ -168,7 +249,13 @@ class InputTag(Widget):
|
|
|
168
249
|
def selection_changed(self, func):
|
|
169
250
|
return self._activation_widget.value_changed(func)
|
|
170
251
|
|
|
171
|
-
def set_tag_meta(self, tag_meta: TagMeta):
|
|
252
|
+
def set_tag_meta(self, tag_meta: TagMeta) -> None:
|
|
253
|
+
"""Set the tag metadata.
|
|
254
|
+
|
|
255
|
+
:param tag_meta: Tag metadata to set
|
|
256
|
+
:type tag_meta: TagMeta
|
|
257
|
+
:return: None
|
|
258
|
+
"""
|
|
172
259
|
self._tag_meta = tag_meta
|
|
173
260
|
self._value_type_name = VALUE_TYPE_NAME[self._tag_meta.value_type]
|
|
174
261
|
self._name = f"<b>{self._tag_meta.name}</b>"
|
|
File without changes
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
from typing import List, Union, Dict, Callable
|
|
2
|
+
|
|
3
|
+
from supervisely.app.widgets import Widget
|
|
4
|
+
from supervisely import TagMeta, TagMetaCollection, Tag
|
|
5
|
+
from supervisely.annotation.tag_meta import TagValueType
|
|
6
|
+
from supervisely.app.content import DataJson, StateJson
|
|
7
|
+
from supervisely.imaging.color import rgb2hex
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InputTagList(Widget):
|
|
11
|
+
"""Store and manage a list of input tags. Class accepts a list of TagMeta objects and provides methods to interact with them.
|
|
12
|
+
|
|
13
|
+
:param tag_metas: List of TagMeta objects or a TagMetaCollection, defaults to an empty list
|
|
14
|
+
:type tag_metas: Union[List[TagMeta], TagMetaCollection], optional
|
|
15
|
+
:param max_width: Maximum width of the widget in pixels, defaults to 300
|
|
16
|
+
:type max_width: int, optional
|
|
17
|
+
:param max_height: Maximum height of the widget in pixels, defaults to 50
|
|
18
|
+
:type max_height: int, optional
|
|
19
|
+
:param multiple: Whether to allow multiple tags to be selected, defaults to False
|
|
20
|
+
:type multiple: bool, optional
|
|
21
|
+
:param widget_id: Unique identifier for the widget, defaults to None
|
|
22
|
+
:type widget_id: int, optional
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
class VALUE_TYPES:
|
|
26
|
+
"""Value types for input tags. Classifies the different types of values that tags can have."""
|
|
27
|
+
|
|
28
|
+
none = str(TagValueType.NONE)
|
|
29
|
+
any_string = str(TagValueType.ANY_STRING)
|
|
30
|
+
one_of = str(TagValueType.ONEOF_STRING)
|
|
31
|
+
number = str(TagValueType.ANY_NUMBER)
|
|
32
|
+
|
|
33
|
+
VALUE_TYPE_NAME = {
|
|
34
|
+
str(TagValueType.NONE): "NONE",
|
|
35
|
+
str(TagValueType.ANY_STRING): "TEXT",
|
|
36
|
+
str(TagValueType.ONEOF_STRING): "ONE OF",
|
|
37
|
+
str(TagValueType.ANY_NUMBER): "NUMBER",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def get_default_value(self, tag_meta: TagMeta) -> Union[str, int, None]:
|
|
41
|
+
"""Get default value for the tag based on its meta information.
|
|
42
|
+
If the tag has a predefined set of possible values (ONEOF_STRING), return the first one.
|
|
43
|
+
For other types, return a standard default value:
|
|
44
|
+
1. NONE: None
|
|
45
|
+
2. ANY_STRING: ""
|
|
46
|
+
3. ANY_NUMBER: 0
|
|
47
|
+
|
|
48
|
+
:param tag_meta: Tag metadata
|
|
49
|
+
:type tag_meta: TagMeta
|
|
50
|
+
:return: Default value for the tag
|
|
51
|
+
:rtype: Union[str, int, None]
|
|
52
|
+
"""
|
|
53
|
+
DEFAULT_VALUES = {
|
|
54
|
+
str(TagValueType.NONE): None,
|
|
55
|
+
str(TagValueType.ANY_STRING): "",
|
|
56
|
+
str(TagValueType.ANY_NUMBER): 0,
|
|
57
|
+
}
|
|
58
|
+
if tag_meta.value_type == str(TagValueType.ONEOF_STRING):
|
|
59
|
+
return tag_meta.possible_values[0]
|
|
60
|
+
else:
|
|
61
|
+
return DEFAULT_VALUES[tag_meta.value_type]
|
|
62
|
+
|
|
63
|
+
class Routes:
|
|
64
|
+
"""Routes for the widget events. Classifies the different types of events that can occur within the widget."""
|
|
65
|
+
|
|
66
|
+
CHECKBOX_CHANGED = "checkbox_cb"
|
|
67
|
+
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
tag_metas: Union[List[TagMeta], TagMetaCollection] = [],
|
|
71
|
+
max_width: int = 300,
|
|
72
|
+
max_height: int = 50,
|
|
73
|
+
multiple: bool = False,
|
|
74
|
+
widget_id: int = None,
|
|
75
|
+
):
|
|
76
|
+
self._tag_metas = tag_metas
|
|
77
|
+
self._max_width = self._get_max_width(max_width)
|
|
78
|
+
self._max_height = self._get_max_height(max_height)
|
|
79
|
+
self._multiple = multiple
|
|
80
|
+
|
|
81
|
+
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
82
|
+
|
|
83
|
+
def _get_max_width(self, value) -> str:
|
|
84
|
+
"""Get the maximum width for the widget.
|
|
85
|
+
Ensures the width is at least 150 pixels.
|
|
86
|
+
|
|
87
|
+
:param value: Desired maximum width in pixels.
|
|
88
|
+
:type value: int
|
|
89
|
+
:return: Maximum width for the widget
|
|
90
|
+
:rtype: str
|
|
91
|
+
"""
|
|
92
|
+
if value < 150:
|
|
93
|
+
value = 150
|
|
94
|
+
return f"{value}px"
|
|
95
|
+
|
|
96
|
+
def _get_max_height(self, value) -> str:
|
|
97
|
+
"""Get the maximum height for the widget.
|
|
98
|
+
Ensures the height is at least 100 pixels.
|
|
99
|
+
|
|
100
|
+
:param value: Desired maximum height in pixels.
|
|
101
|
+
:type value: int
|
|
102
|
+
:return: Maximum height for the widget
|
|
103
|
+
:rtype: str
|
|
104
|
+
"""
|
|
105
|
+
if value < 100:
|
|
106
|
+
value = 100
|
|
107
|
+
return f"{value}px"
|
|
108
|
+
|
|
109
|
+
def get_json_data(self) -> Dict:
|
|
110
|
+
"""Get JSON data for the widget.
|
|
111
|
+
|
|
112
|
+
:return: JSON data for the widget
|
|
113
|
+
:rtype: Dict
|
|
114
|
+
"""
|
|
115
|
+
return {
|
|
116
|
+
"maxWidth": self._max_width,
|
|
117
|
+
"maxHeight": self._max_height,
|
|
118
|
+
"tags": [
|
|
119
|
+
{
|
|
120
|
+
"name": tag_meta.name,
|
|
121
|
+
"valueType": tag_meta.value_type,
|
|
122
|
+
"valueTypeText": self.VALUE_TYPE_NAME[tag_meta.value_type],
|
|
123
|
+
"color": rgb2hex(tag_meta.color),
|
|
124
|
+
"possible_values": tag_meta.possible_values,
|
|
125
|
+
}
|
|
126
|
+
for tag_meta in self._tag_metas
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
def get_json_state(self) -> Dict:
|
|
131
|
+
"""Get JSON state for the widget.
|
|
132
|
+
|
|
133
|
+
:return: JSON state for the widget
|
|
134
|
+
:rtype: Dict
|
|
135
|
+
"""
|
|
136
|
+
return {
|
|
137
|
+
"selected": [False for _ in self._tag_metas],
|
|
138
|
+
"values": [self.get_default_value(tm) for tm in self._tag_metas],
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def get_selected_tag_metas(self) -> List[TagMeta]:
|
|
142
|
+
"""Get selected tag metas for the widget.
|
|
143
|
+
|
|
144
|
+
:return: List of selected tag metas
|
|
145
|
+
:rtype: List[TagMeta]
|
|
146
|
+
"""
|
|
147
|
+
return [
|
|
148
|
+
tm
|
|
149
|
+
for selected, tm in zip(StateJson()[self.widget_id]["selected"], self._tag_metas)
|
|
150
|
+
if selected
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
def get_selected_tags(self) -> List[Tag]:
|
|
154
|
+
"""Get selected tags for the widget.
|
|
155
|
+
|
|
156
|
+
:return: List of selected tags
|
|
157
|
+
:rtype: List[Tag]
|
|
158
|
+
"""
|
|
159
|
+
return [
|
|
160
|
+
Tag(meta=tm, value=value)
|
|
161
|
+
for selected, value, tm in zip(
|
|
162
|
+
StateJson()[self.widget_id]["selected"],
|
|
163
|
+
StateJson()[self.widget_id]["values"],
|
|
164
|
+
self._tag_metas,
|
|
165
|
+
)
|
|
166
|
+
if selected
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
def get_all_tags(self) -> Union[List[TagMeta], TagMetaCollection]:
|
|
170
|
+
"""Get all tags for the widget.
|
|
171
|
+
|
|
172
|
+
:return: List of all tag metas
|
|
173
|
+
:rtype: Union[List[TagMeta], TagMetaCollection]
|
|
174
|
+
"""
|
|
175
|
+
return [
|
|
176
|
+
Tag(meta=tm, value=value)
|
|
177
|
+
for value, tm in zip(
|
|
178
|
+
StateJson()[self.widget_id]["values"],
|
|
179
|
+
self._tag_metas,
|
|
180
|
+
)
|
|
181
|
+
]
|
|
182
|
+
|
|
183
|
+
def set(self, tag_metas: Union[List[TagMeta], TagMetaCollection]) -> None:
|
|
184
|
+
"""Set tag metas for the widget.
|
|
185
|
+
|
|
186
|
+
:param tag_metas: Tag metas to set
|
|
187
|
+
:type tag_metas: Union[List[TagMeta], TagMetaCollection]
|
|
188
|
+
:return: None
|
|
189
|
+
"""
|
|
190
|
+
self._tag_metas = tag_metas
|
|
191
|
+
DataJson()[self.widget_id] = self.get_json_data()
|
|
192
|
+
DataJson().send_changes()
|
|
193
|
+
StateJson()[self.widget_id] = self.get_json_state()
|
|
194
|
+
StateJson().send_changes()
|
|
195
|
+
|
|
196
|
+
def set_values(self, values_dict: Dict) -> None:
|
|
197
|
+
"""Set values for the widget.
|
|
198
|
+
|
|
199
|
+
:param values_dict: Dictionary of values to set
|
|
200
|
+
:type values_dict: Dict
|
|
201
|
+
:return: None
|
|
202
|
+
"""
|
|
203
|
+
current_values = StateJson()[self.widget_id]["values"]
|
|
204
|
+
values = [
|
|
205
|
+
values_dict.get(tm.name, current_values[idx]) for idx, tm in enumerate(self._tag_metas)
|
|
206
|
+
]
|
|
207
|
+
StateJson()[self.widget_id]["values"] = values
|
|
208
|
+
StateJson().send_changes()
|
|
209
|
+
|
|
210
|
+
def select_all(self) -> None:
|
|
211
|
+
"""Select all tags for the widget.
|
|
212
|
+
|
|
213
|
+
:return: None
|
|
214
|
+
"""
|
|
215
|
+
StateJson()[self.widget_id]["selected"] = [True for _ in self._tag_metas]
|
|
216
|
+
StateJson().send_changes()
|
|
217
|
+
|
|
218
|
+
def deselect_all(self) -> None:
|
|
219
|
+
"""Deselect all tags for the widget.
|
|
220
|
+
|
|
221
|
+
:return: None
|
|
222
|
+
"""
|
|
223
|
+
StateJson()[self.widget_id]["selected"] = [False for _ in self._tag_metas]
|
|
224
|
+
StateJson().send_changes()
|
|
225
|
+
|
|
226
|
+
def select(self, names: List[str]) -> None:
|
|
227
|
+
"""Select tags for the widget.
|
|
228
|
+
|
|
229
|
+
:param names: List of tag names to select
|
|
230
|
+
:type names: List[str]
|
|
231
|
+
:return: None
|
|
232
|
+
"""
|
|
233
|
+
selected = [tm.name in names for tm in self._tag_metas]
|
|
234
|
+
StateJson()[self.widget_id]["selected"] = selected
|
|
235
|
+
StateJson().send_changes()
|
|
236
|
+
|
|
237
|
+
def deselect(self, names: List[str]) -> None:
|
|
238
|
+
"""Deselect tags for the widget.
|
|
239
|
+
|
|
240
|
+
:param names: List of tag names to deselect
|
|
241
|
+
:type names: List[str]
|
|
242
|
+
:return: None
|
|
243
|
+
"""
|
|
244
|
+
selected = StateJson()[self.widget_id]["selected"]
|
|
245
|
+
for idx, tm in enumerate(self._tag_metas):
|
|
246
|
+
if tm.name in names:
|
|
247
|
+
selected[idx] = False
|
|
248
|
+
StateJson()[self.widget_id]["selected"] = selected
|
|
249
|
+
StateJson().send_changes()
|
|
250
|
+
|
|
251
|
+
def get_all_tag_metas(self) -> List[TagMeta]:
|
|
252
|
+
"""Get all tag metas for the widget.
|
|
253
|
+
|
|
254
|
+
:return: List of all tag metas
|
|
255
|
+
:rtype: List[TagMeta]
|
|
256
|
+
"""
|
|
257
|
+
return self._tag_metas
|
|
258
|
+
|
|
259
|
+
def selection_changed(self, func: Callable) -> Callable:
|
|
260
|
+
"""Decorator to register a callback function for selection changes.
|
|
261
|
+
|
|
262
|
+
:param func: Callback function
|
|
263
|
+
:type func: Callable
|
|
264
|
+
"""
|
|
265
|
+
route_path = self.get_route_path(InputTagList.Routes.CHECKBOX_CHANGED)
|
|
266
|
+
server = self._sly_app.get_server()
|
|
267
|
+
self._checkboxes_handled = True
|
|
268
|
+
|
|
269
|
+
@server.post(route_path)
|
|
270
|
+
def _click():
|
|
271
|
+
selected = self.get_selected_tag_metas()
|
|
272
|
+
func(selected)
|
|
273
|
+
|
|
274
|
+
return _click
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<div :style="{
|
|
2
|
+
'display': 'flex',
|
|
3
|
+
'flex-direction': 'column',
|
|
4
|
+
'max-width': data.{{{widget.widget_id}}}.maxWidth,
|
|
5
|
+
'max-height': data.{{{widget.widget_id}}}.maxHeight,
|
|
6
|
+
}">
|
|
7
|
+
{% if widget._multiple %}
|
|
8
|
+
<div>
|
|
9
|
+
<el-button style="margin-right: 10px" type="text" {% if widget._checkboxes_handled %} @click="{
|
|
10
|
+
state.{{{widget.widget_id}}}.selected = state.{{{widget.widget_id}}}.selected.map(() => true);
|
|
11
|
+
post('/{{{widget.widget_id}}}/checkbox_cb');
|
|
12
|
+
}" {% else %}
|
|
13
|
+
@click="state.{{{widget.widget_id}}}.selected = state.{{{widget.widget_id}}}.selected.map(() => true)" {% endif
|
|
14
|
+
%}>
|
|
15
|
+
<i class="zmdi zmdi-check-all"></i>
|
|
16
|
+
Select all
|
|
17
|
+
</el-button>
|
|
18
|
+
<el-button style="margin-right: 10px" type="text" {% if widget._checkboxes_handled %} @click="{
|
|
19
|
+
state.{{{widget.widget_id}}}.selected = state.{{{widget.widget_id}}}.selected.map(() => false);
|
|
20
|
+
post('/{{{widget.widget_id}}}/checkbox_cb');
|
|
21
|
+
}" {% else %}
|
|
22
|
+
@click="state.{{{widget.widget_id}}}.selected = state.{{{widget.widget_id}}}.selected.map(() => false)" {% endif
|
|
23
|
+
%}>
|
|
24
|
+
<i class="zmdi zmdi-square-o"></i>
|
|
25
|
+
Deselect all
|
|
26
|
+
</el-button>
|
|
27
|
+
</div>
|
|
28
|
+
{% endif %}
|
|
29
|
+
<div style="
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-direction: column;
|
|
32
|
+
gap: 2px;
|
|
33
|
+
overflow-y: auto;
|
|
34
|
+
overflow-x: hidden;
|
|
35
|
+
">
|
|
36
|
+
<div v-for="(tag, idx) in data.{{{widget.widget_id}}}.tags" class="fflex" style="gap: 4px">
|
|
37
|
+
<el-checkbox style="flex: none" v-model="state.{{{widget.widget_id}}}.selected[idx]" {% if
|
|
38
|
+
widget._checkboxes_handled %} {% if not widget._multiple %} @change="() => {
|
|
39
|
+
if ($event.target.checked) {
|
|
40
|
+
state.{{{widget.widget_id}}}.selected = state.{{{widget.widget_id}}}.selected.map((_, i) => i == idx);
|
|
41
|
+
}
|
|
42
|
+
post('/{{{widget.widget_id}}}/checkbox_cb');
|
|
43
|
+
}" {% else %} @change="() => {
|
|
44
|
+
post('/{{{widget.widget_id}}}/checkbox_cb');
|
|
45
|
+
}" {% endif %} {% else %} {% if not widget._multiple %}
|
|
46
|
+
@change="if ($event.target.checked) {state.{{{widget.widget_id}}}.selected = state.{{{widget.widget_id}}}.selected.map((_, i) => i == idx);}"
|
|
47
|
+
{% endif %} {% endif %}>
|
|
48
|
+
</el-checkbox>
|
|
49
|
+
<i class="zmdi zmdi-label" :style="{color: tag.color}" style="flex: none"></i>
|
|
50
|
+
<div class="fflex" style="flex: 1; overflow: hidden; text-overflow: ellipsis">
|
|
51
|
+
<b style="width: 100%; overflow: hidden; text-overflow: ellipsis">{{tag.name}}</b>
|
|
52
|
+
</div>
|
|
53
|
+
<div v-if="state.{{{widget.widget_id}}}.selected[idx]">
|
|
54
|
+
<div v-if="tag.valueType == '{{{widget.VALUE_TYPES.any_string}}}'">
|
|
55
|
+
<el-input v-model="state.{{{widget.widget_id}}}.values[idx]" size="mini" style="width: 140px"></el-input>
|
|
56
|
+
</div>
|
|
57
|
+
<div v-if="tag.valueType == '{{{widget.VALUE_TYPES.one_of}}}'">
|
|
58
|
+
<el-select v-model="state.{{{widget.widget_id}}}.values[idx]" size="mini" style="width: 140px">
|
|
59
|
+
<el-option v-for="item in tag.possible_values" :key="item" :label="item" :value="item">
|
|
60
|
+
</el-option>
|
|
61
|
+
</el-select>
|
|
62
|
+
</div>
|
|
63
|
+
<div v-if="tag.valueType == '{{{widget.VALUE_TYPES.number}}}'">
|
|
64
|
+
<el-input-number :controls="false" v-model="state.{{{widget.widget_id}}}.values[idx]" size="mini"
|
|
65
|
+
style="width: 140px"></el-input-number>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import Dict, Optional
|
|
2
|
+
from typing import Dict, List, Optional
|
|
3
3
|
|
|
4
4
|
import supervisely.convert.pointcloud_episodes.nuscenes_conv.nuscenes_helper as helpers
|
|
5
5
|
import supervisely.io.fs as fs
|
|
@@ -8,6 +8,7 @@ from supervisely._utils import is_development
|
|
|
8
8
|
from supervisely.annotation.obj_class import ObjClass
|
|
9
9
|
from supervisely.annotation.tag_meta import TagMeta, TagValueType
|
|
10
10
|
from supervisely.api.api import Api, ApiField
|
|
11
|
+
from supervisely.api.dataset_api import DatasetInfo
|
|
11
12
|
from supervisely.convert.base_converter import AvailablePointcloudConverters
|
|
12
13
|
from supervisely.convert.pointcloud.pointcloud_converter import PointcloudConverter
|
|
13
14
|
from supervisely.convert.pointcloud_episodes.nuscenes_conv.nuscenes_converter import (
|
|
@@ -44,7 +45,7 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
44
45
|
|
|
45
46
|
def to_supervisely(
|
|
46
47
|
self,
|
|
47
|
-
scene_sample,
|
|
48
|
+
scene_sample: helpers.Sample,
|
|
48
49
|
meta: ProjectMeta,
|
|
49
50
|
renamed_classes: dict = {},
|
|
50
51
|
renamed_tags: dict = {},
|
|
@@ -74,23 +75,14 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
74
75
|
obj_classes = []
|
|
75
76
|
for category in nuscenes.category:
|
|
76
77
|
color = nuscenes.colormap[category["name"]]
|
|
77
|
-
description = category["description"]
|
|
78
|
-
if len(description) > 255:
|
|
79
|
-
# * Trim description to fit into 255 characters limit
|
|
80
|
-
sentences = description.split(".")
|
|
81
|
-
trimmed_description = ""
|
|
82
|
-
for sentence in sentences:
|
|
83
|
-
if len(trimmed_description) + len(sentence) + 1 > 255:
|
|
84
|
-
break
|
|
85
|
-
trimmed_description += sentence + "."
|
|
86
|
-
description = trimmed_description.strip()
|
|
78
|
+
description = helpers.trim_description(category["description"])
|
|
87
79
|
obj_classes.append(ObjClass(category["name"], Cuboid3d, color, description=description))
|
|
88
80
|
|
|
89
81
|
self._meta = ProjectMeta(obj_classes, tag_metas)
|
|
90
82
|
meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
|
|
91
83
|
|
|
92
84
|
dataset_info = api.dataset.get_info_by_id(dataset_id)
|
|
93
|
-
scene_name_to_dataset = {}
|
|
85
|
+
scene_name_to_dataset: Dict[str, DatasetInfo] = {}
|
|
94
86
|
|
|
95
87
|
scene_names = [scene["name"] for scene in nuscenes.scene]
|
|
96
88
|
scene_cnt = len(scene_names)
|
|
@@ -123,7 +115,7 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
123
115
|
sample_token = scene["first_sample_token"]
|
|
124
116
|
|
|
125
117
|
# * Extract scene's samples
|
|
126
|
-
scene_samples = []
|
|
118
|
+
scene_samples: List[helpers.Sample] = []
|
|
127
119
|
for i in range(scene["nbr_samples"]):
|
|
128
120
|
sample = nuscenes.get("sample", sample_token)
|
|
129
121
|
lidar_path, boxes, _ = nuscenes.get_sample_data(sample["data"]["LIDAR_TOP"])
|
|
@@ -145,17 +137,16 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
145
137
|
]
|
|
146
138
|
visibility = nuscenes.get("visibility", ann["visibility_token"])["level"]
|
|
147
139
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
visibility,
|
|
157
|
-
)
|
|
140
|
+
ann = helpers.AnnotationObject(
|
|
141
|
+
name=name,
|
|
142
|
+
bbox=box,
|
|
143
|
+
instance_token=current_instance_token,
|
|
144
|
+
parent_token=parent_token,
|
|
145
|
+
category=category,
|
|
146
|
+
attributes=attributes,
|
|
147
|
+
visibility=visibility,
|
|
158
148
|
)
|
|
149
|
+
anns.append(ann)
|
|
159
150
|
|
|
160
151
|
# get camera data
|
|
161
152
|
sample_data = nuscenes.get("sample_data", sample["data"]["LIDAR_TOP"])
|
|
@@ -177,7 +168,7 @@ class NuscenesConverter(NuscenesEpisodesConverter, PointcloudConverter):
|
|
|
177
168
|
pcd_ann = self.to_supervisely(sample, meta, renamed_classes, renamed_tags)
|
|
178
169
|
|
|
179
170
|
pcd_path = sample.convert_lidar_to_supervisely()
|
|
180
|
-
pcd_name = fs.
|
|
171
|
+
pcd_name = fs.get_file_name_with_ext(pcd_path)
|
|
181
172
|
pcd_meta = {
|
|
182
173
|
"frame": idx,
|
|
183
174
|
"vehicle": log["vehicle"],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from os import path as osp
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Dict, Optional
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
5
|
|
|
6
6
|
import supervisely.convert.pointcloud_episodes.nuscenes_conv.nuscenes_helper as helpers
|
|
7
7
|
import supervisely.io.fs as fs
|
|
@@ -9,6 +9,7 @@ from supervisely._utils import is_development
|
|
|
9
9
|
from supervisely.annotation.obj_class import ObjClass
|
|
10
10
|
from supervisely.annotation.tag_meta import TagMeta, TagValueType
|
|
11
11
|
from supervisely.api.api import Api, ApiField
|
|
12
|
+
from supervisely.api.dataset_api import DatasetInfo
|
|
12
13
|
from supervisely.convert.base_converter import AvailablePointcloudConverters
|
|
13
14
|
from supervisely.convert.pointcloud_episodes.pointcloud_episodes_converter import (
|
|
14
15
|
PointcloudEpisodeConverter,
|
|
@@ -49,7 +50,6 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
49
50
|
remote_files_map: Optional[Dict[str, str]] = None,
|
|
50
51
|
):
|
|
51
52
|
super().__init__(input_data, labeling_interface, upload_as_links, remote_files_map)
|
|
52
|
-
self._nuscenes = None
|
|
53
53
|
|
|
54
54
|
def __str__(self) -> str:
|
|
55
55
|
return AvailablePointcloudConverters.NUSCENES
|
|
@@ -83,8 +83,7 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
83
83
|
version = osp.basename(ann_dir)
|
|
84
84
|
try:
|
|
85
85
|
t = TinyTimer()
|
|
86
|
-
|
|
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}")
|
|
@@ -94,7 +93,7 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
94
93
|
|
|
95
94
|
def to_supervisely(
|
|
96
95
|
self,
|
|
97
|
-
scene_samples,
|
|
96
|
+
scene_samples: List[helpers.Sample],
|
|
98
97
|
meta: ProjectMeta,
|
|
99
98
|
renamed_classes: dict = {},
|
|
100
99
|
renamed_tags: dict = {},
|
|
@@ -142,23 +141,14 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
142
141
|
obj_classes = []
|
|
143
142
|
for category in nuscenes.category:
|
|
144
143
|
color = nuscenes.colormap[category["name"]]
|
|
145
|
-
description = category
|
|
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()
|
|
144
|
+
description = helpers.trim_description(category.get("description", ""))
|
|
155
145
|
obj_classes.append(ObjClass(category["name"], Cuboid3d, color, description=description))
|
|
156
146
|
|
|
157
147
|
self._meta = ProjectMeta(obj_classes, tag_metas)
|
|
158
148
|
meta, renamed_classes, renamed_tags = self.merge_metas_with_conflicts(api, dataset_id)
|
|
159
149
|
|
|
160
150
|
dataset_info = api.dataset.get_info_by_id(dataset_id)
|
|
161
|
-
scene_name_to_dataset = {}
|
|
151
|
+
scene_name_to_dataset: Dict[str, DatasetInfo] = {}
|
|
162
152
|
|
|
163
153
|
scene_names = [scene["name"] for scene in nuscenes.scene]
|
|
164
154
|
scene_cnt = len(scene_names)
|
|
@@ -193,7 +183,7 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
193
183
|
sample_token = scene["first_sample_token"]
|
|
194
184
|
|
|
195
185
|
# * Extract scene's samples
|
|
196
|
-
scene_samples = []
|
|
186
|
+
scene_samples: List[helpers.Sample] = []
|
|
197
187
|
for i in range(scene["nbr_samples"]):
|
|
198
188
|
sample = nuscenes.get("sample", sample_token)
|
|
199
189
|
lidar_path, boxes, _ = nuscenes.get_sample_data(sample["data"]["LIDAR_TOP"])
|
|
@@ -207,7 +197,6 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
207
197
|
current_instance_token = inst_token["token"]
|
|
208
198
|
parent_token = inst_token["prev"]
|
|
209
199
|
|
|
210
|
-
# get category, attributes and visibility
|
|
211
200
|
ann = nuscenes.get("sample_annotation", current_instance_token)
|
|
212
201
|
category = ann["category_name"]
|
|
213
202
|
attributes = [
|
|
@@ -215,17 +204,16 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
215
204
|
]
|
|
216
205
|
visibility = nuscenes.get("visibility", ann["visibility_token"])["level"]
|
|
217
206
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
visibility,
|
|
227
|
-
)
|
|
207
|
+
ann = helpers.AnnotationObject(
|
|
208
|
+
name=name,
|
|
209
|
+
bbox=box,
|
|
210
|
+
instance_token=current_instance_token,
|
|
211
|
+
parent_token=parent_token,
|
|
212
|
+
category=category,
|
|
213
|
+
attributes=attributes,
|
|
214
|
+
visibility=visibility,
|
|
228
215
|
)
|
|
216
|
+
anns.append(ann)
|
|
229
217
|
|
|
230
218
|
# get camera data
|
|
231
219
|
sample_data = nuscenes.get("sample_data", sample["data"]["LIDAR_TOP"])
|
|
@@ -247,7 +235,7 @@ class NuscenesEpisodesConverter(PointcloudEpisodeConverter):
|
|
|
247
235
|
for idx, sample in enumerate(scene_samples):
|
|
248
236
|
pcd_path = sample.convert_lidar_to_supervisely()
|
|
249
237
|
|
|
250
|
-
pcd_name = fs.
|
|
238
|
+
pcd_name = fs.get_file_name_with_ext(pcd_path)
|
|
251
239
|
pcd_meta = {
|
|
252
240
|
"frame": idx,
|
|
253
241
|
"vehicle": log["vehicle"],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from os import path as osp
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import List
|
|
4
|
+
from typing import Dict, Generator, List, Tuple
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
@@ -40,91 +40,36 @@ TABLE_NAMES = [
|
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
43
|
+
def trim_description(description: str, max_length: int = 255) -> str:
|
|
44
|
+
if len(description) > max_length:
|
|
45
|
+
sentences = description.split(".")
|
|
46
|
+
trimmed_description = ""
|
|
47
|
+
for sentence in sentences:
|
|
48
|
+
if len(trimmed_description) + len(sentence) + 1 > max_length:
|
|
49
|
+
break
|
|
50
|
+
trimmed_description += sentence + "."
|
|
51
|
+
description = trimmed_description.strip()
|
|
52
|
+
return description
|
|
106
53
|
|
|
107
54
|
|
|
108
55
|
class AnnotationObject:
|
|
109
56
|
"""
|
|
110
57
|
A class to represent an annotation object in the NuScenes dataset.
|
|
111
58
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
visibility : str
|
|
127
|
-
The visibility level of the annotation object.
|
|
59
|
+
:param name: The name of the annotation object
|
|
60
|
+
:type name: str
|
|
61
|
+
:param bbox: The bounding box coordinates in NuScenes format
|
|
62
|
+
:type bbox: np.ndarray
|
|
63
|
+
:param instance_token: The instance token associated with the annotation object
|
|
64
|
+
:type instance_token: str
|
|
65
|
+
:param parent_token: The token of instance preceding the current object instance
|
|
66
|
+
:type parent_token: str
|
|
67
|
+
:param category: The class name of the annotation object
|
|
68
|
+
:type category: str
|
|
69
|
+
:param attributes: The attribute names associated with the annotation object
|
|
70
|
+
:type attributes: List[str]
|
|
71
|
+
:param visibility: The visibility level of the annotation object
|
|
72
|
+
:type visibility: str
|
|
128
73
|
"""
|
|
129
74
|
|
|
130
75
|
def __init__(
|
|
@@ -146,7 +91,7 @@ class AnnotationObject:
|
|
|
146
91
|
self.attributes = attributes
|
|
147
92
|
self.visibility = visibility
|
|
148
93
|
|
|
149
|
-
def to_supervisely(self):
|
|
94
|
+
def to_supervisely(self) -> Cuboid3d:
|
|
150
95
|
box = self.convert_nuscenes_to_BEVBox3D()
|
|
151
96
|
|
|
152
97
|
bbox = box.to_xyzwhlr()
|
|
@@ -176,29 +121,33 @@ class AnnotationObject:
|
|
|
176
121
|
|
|
177
122
|
class CamData:
|
|
178
123
|
"""
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
The intrinsic matrix (3x3) representing the camera's intrinsic parameters.
|
|
124
|
+
This class handles camera sensor data from the nuScenes dataset, including coordinate system
|
|
125
|
+
transformations from lidar to camera space and extraction of camera calibration parameters.
|
|
126
|
+
|
|
127
|
+
:param nuscenes: The nuScenes dataset instance
|
|
128
|
+
:type nuscenes: NuScenes
|
|
129
|
+
:param sensor_name: The name of the camera sensor
|
|
130
|
+
:type sensor_name: str
|
|
131
|
+
:param sensor_token: The token identifying the specific sensor sample
|
|
132
|
+
:type sensor_token: str
|
|
133
|
+
:param cs_record: The calibrated sensor record for the lidar
|
|
134
|
+
:type cs_record: dict
|
|
135
|
+
:param ego_record: The ego pose record for the lidar
|
|
136
|
+
:type ego_record: dict
|
|
193
137
|
"""
|
|
194
138
|
|
|
195
|
-
def __init__(
|
|
139
|
+
def __init__(
|
|
140
|
+
self, nuscenes, sensor_name: str, sensor_token: str, cs_record: dict, ego_record: dict
|
|
141
|
+
):
|
|
142
|
+
from nuscenes import NuScenes # pylint: disable=import-error
|
|
196
143
|
from nuscenes.utils.data_classes import ( # pylint: disable=import-error
|
|
197
144
|
transform_matrix,
|
|
198
145
|
)
|
|
199
146
|
from pyquaternion import Quaternion # pylint: disable=import-error
|
|
200
147
|
|
|
201
|
-
|
|
148
|
+
nuscenes: NuScenes = nuscenes
|
|
149
|
+
|
|
150
|
+
img_path, _, _ = nuscenes.get_sample_data(sensor_token)
|
|
202
151
|
if not osp.exists(img_path):
|
|
203
152
|
return None
|
|
204
153
|
|
|
@@ -237,15 +186,14 @@ class CamData:
|
|
|
237
186
|
self.extrinsic = np.hstack((velo_to_cam_rot, velo_to_cam_trans.reshape(3, 1)))
|
|
238
187
|
self.intrinsic = np.asarray(cs_record_cam["camera_intrinsic"])
|
|
239
188
|
|
|
240
|
-
def get_info(self, timestamp):
|
|
189
|
+
def get_info(self, timestamp: str) -> Tuple[str, Dict]:
|
|
241
190
|
"""
|
|
242
|
-
|
|
191
|
+
Generates image info based on the camera data.
|
|
243
192
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
tuple: A tuple containing the image path and a dictionary with image metadata.
|
|
193
|
+
:param timestamp: The timestamp associated with the image
|
|
194
|
+
:type timestamp: str
|
|
195
|
+
:return: A tuple containing the image path and a dictionary with image metadata.
|
|
196
|
+
:rtype: tuple
|
|
249
197
|
"""
|
|
250
198
|
sensors_to_skip = ["_intrinsic", "_extrinsic", "_imsize"]
|
|
251
199
|
if not any([self.name.endswith(s) for s in sensors_to_skip]):
|
|
@@ -263,3 +211,94 @@ class CamData:
|
|
|
263
211
|
},
|
|
264
212
|
}
|
|
265
213
|
return (sly_path_img, img_info)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class Sample:
|
|
217
|
+
"""
|
|
218
|
+
A class to represent a sample from the NuScenes dataset.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
def __init__(
|
|
222
|
+
self,
|
|
223
|
+
timestamp: float,
|
|
224
|
+
lidar_path: str,
|
|
225
|
+
anns: List[AnnotationObject],
|
|
226
|
+
cam_data: List[CamData],
|
|
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
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def generate_boxes(nuscenes, boxes: List) -> Generator:
|
|
251
|
+
"""
|
|
252
|
+
Generate ground truth boxes for a given set of boxes.
|
|
253
|
+
|
|
254
|
+
:param nuscenes: The nuScenes dataset instance
|
|
255
|
+
:type nuscenes: NuScenes
|
|
256
|
+
:param boxes: A list of boxes to generate ground truth for
|
|
257
|
+
:type boxes: List
|
|
258
|
+
:return: A generator that yields tuples containing the ground truth box, name, and instance token.
|
|
259
|
+
:rtype: generator
|
|
260
|
+
"""
|
|
261
|
+
from nuscenes.utils.data_classes import Box # pylint: disable=import-error
|
|
262
|
+
|
|
263
|
+
boxes: List[Box] = boxes
|
|
264
|
+
|
|
265
|
+
locs = np.array([b.center for b in boxes]).reshape(-1, 3)
|
|
266
|
+
dims = np.array([b.wlh for b in boxes]).reshape(-1, 3)
|
|
267
|
+
rots = np.array([b.orientation.yaw_pitch_roll[0] for b in boxes]).reshape(-1, 1)
|
|
268
|
+
|
|
269
|
+
gt_boxes = np.concatenate([locs, dims, -rots - np.pi / 2], axis=1)
|
|
270
|
+
names = np.array([b.name for b in boxes])
|
|
271
|
+
instance_tokens = [nuscenes.get("sample_annotation", box.token) for box in boxes]
|
|
272
|
+
|
|
273
|
+
yield from zip(gt_boxes, names, instance_tokens)
|
|
274
|
+
|
|
275
|
+
def convert_lidar_to_supervisely(self) -> str:
|
|
276
|
+
"""
|
|
277
|
+
Converts a LiDAR point cloud file to the Supervisely format and saves it as a .pcd file.
|
|
278
|
+
|
|
279
|
+
:return: The file path of the saved .pcd file.
|
|
280
|
+
:rtype: str
|
|
281
|
+
"""
|
|
282
|
+
import open3d as o3d # pylint: disable=import-error
|
|
283
|
+
|
|
284
|
+
bin_file = Path(self.lidar_path)
|
|
285
|
+
save_path = str(bin_file.with_suffix(".pcd"))
|
|
286
|
+
|
|
287
|
+
b = np.fromfile(bin_file, dtype=np.float32).reshape(-1, 5)
|
|
288
|
+
points = b[:, 0:3]
|
|
289
|
+
intensity = b[:, 3]
|
|
290
|
+
ring_index = b[:, 4]
|
|
291
|
+
intensity_fake_rgb = np.zeros((intensity.shape[0], 3))
|
|
292
|
+
intensity_fake_rgb[:, 0] = (
|
|
293
|
+
intensity # red The intensity measures the reflectivity of the objects
|
|
294
|
+
)
|
|
295
|
+
intensity_fake_rgb[:, 1] = (
|
|
296
|
+
ring_index # green ring index is the index of the laser ranging from 0 to 31
|
|
297
|
+
)
|
|
298
|
+
try:
|
|
299
|
+
pc = o3d.geometry.PointCloud(o3d.utility.Vector3dVector(points))
|
|
300
|
+
pc.colors = o3d.utility.Vector3dVector(intensity_fake_rgb)
|
|
301
|
+
o3d.io.write_point_cloud(save_path, pc)
|
|
302
|
+
except Exception as e:
|
|
303
|
+
logger.warning(f"Error converting lidar to supervisely format: {e}")
|
|
304
|
+
return save_path
|
supervisely/video/sampling.py
CHANGED
|
@@ -94,13 +94,16 @@ def _frame_to_annotation(frame: Frame, video_annotation: VideoAnnotation) -> Ann
|
|
|
94
94
|
|
|
95
95
|
def _upload_annotations(api: Api, image_ids, frame_indices, video_annotation: VideoAnnotation):
|
|
96
96
|
anns = []
|
|
97
|
-
for
|
|
97
|
+
for frame_index in frame_indices:
|
|
98
98
|
frame = video_annotation.frames.get(frame_index, None)
|
|
99
99
|
if frame is not None:
|
|
100
100
|
anns.append(_frame_to_annotation(frame, video_annotation))
|
|
101
|
+
else:
|
|
102
|
+
anns.append(Annotation(video_annotation.img_size))
|
|
101
103
|
api.annotation.upload_anns(image_ids, anns=anns)
|
|
102
104
|
|
|
103
105
|
|
|
106
|
+
|
|
104
107
|
def _upload_frames(
|
|
105
108
|
api: Api,
|
|
106
109
|
frames: List[np.ndarray],
|
|
@@ -22,7 +22,7 @@ supervisely/annotation/tag_meta_mapper.py,sha256=RWeTrxJ64syodyhXIRSH007bX6Hr3B4
|
|
|
22
22
|
supervisely/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
supervisely/api/advanced_api.py,sha256=Nd5cCnHFWc3PSUrCtENxTGtDjS37_lCHXsgXvUI3Ti8,2054
|
|
24
24
|
supervisely/api/agent_api.py,sha256=8EQBwD6v7KLS0-xKcZ12B7mtzKwG7RRgq1fk1vaN144,8893
|
|
25
|
-
supervisely/api/annotation_api.py,sha256=
|
|
25
|
+
supervisely/api/annotation_api.py,sha256=JdcCKuy_7PvtNcHsRhi4ANhn3aynLY44rtbqau2Y9A4,82891
|
|
26
26
|
supervisely/api/api.py,sha256=gRItQzO6Xj7k_pJIpVUS2dV1vkTTHV25_1Uia6xxZSc,67930
|
|
27
27
|
supervisely/api/app_api.py,sha256=Q6XxLxp3D_Vc3PIVyBmP7wJtTLbgYCPNOLND5UvJhMw,79010
|
|
28
28
|
supervisely/api/constants.py,sha256=WfqIcEpRnU4Mcfb6q0njeRs2VVSoTAJaIyrqBkBjP8I,253
|
|
@@ -117,7 +117,7 @@ supervisely/app/v1/widgets/grid_gallery.py,sha256=hEMC0MNfZ4xG2N118Mou_hptLhrikg
|
|
|
117
117
|
supervisely/app/v1/widgets/predictions_dynamics_gallery.py,sha256=l6Ee8-c14yeSnlu4qFsLbmZ5Su63zacO3wmdtH86TMM,8079
|
|
118
118
|
supervisely/app/v1/widgets/progress_bar.py,sha256=8gvQbAUHXPU8_JgC0JZkEBSRCccvg2l4Gtg8DeBCgC8,3184
|
|
119
119
|
supervisely/app/v1/widgets/single_image_gallery.py,sha256=fyuC4jfCHC5rNL1JrHJCE8NaneH0nv0k-0iVkOnY0Wc,2958
|
|
120
|
-
supervisely/app/widgets/__init__.py,sha256=
|
|
120
|
+
supervisely/app/widgets/__init__.py,sha256=mkizufWgs6VFpErsq6JBp-kKBEjVRqhsVvuXmX0aODs,10729
|
|
121
121
|
supervisely/app/widgets/select_sly_utils.py,sha256=gBenYkJyCl3Fa4u2GI6BKXul-AqnzvGK32Y6hxXKccA,288
|
|
122
122
|
supervisely/app/widgets/widget.py,sha256=e9tyZj7XhqDWiN5Wwk2xScXOmf__vRCoHflpGtv1RS0,9820
|
|
123
123
|
supervisely/app/widgets/agent_selector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -342,8 +342,11 @@ supervisely/app/widgets/input_number/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
|
|
|
342
342
|
supervisely/app/widgets/input_number/input_number.py,sha256=nlpSXFaebLE8gasU8GmOPh85bhY7WEjsup5NdpFG47Q,2515
|
|
343
343
|
supervisely/app/widgets/input_number/template.html,sha256=SO1Bhw7dH058-KWjwjG2YbtlJGtkBxq0RlRJ5IrADog,734
|
|
344
344
|
supervisely/app/widgets/input_tag/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
345
|
-
supervisely/app/widgets/input_tag/input_tag.py,sha256=
|
|
345
|
+
supervisely/app/widgets/input_tag/input_tag.py,sha256=jIqUR7wlj-bHvkjdeDlpYNT0eE-3rLbBxYZNDlDYYZU,9082
|
|
346
346
|
supervisely/app/widgets/input_tag/template.html,sha256=to0z-towcbIAtRI0cXRtm4fLVHz5By0kN9UUiM17sTk,729
|
|
347
|
+
supervisely/app/widgets/input_tag_list/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
348
|
+
supervisely/app/widgets/input_tag_list/input_tag_list.py,sha256=SNCJhNhz4dEhBAYyCuFTqkyeUhbaAjMZzlOXA_WyPng,9227
|
|
349
|
+
supervisely/app/widgets/input_tag_list/template.html,sha256=E9j7p0elaq0WO3cXk3LmZEP7K7Z-LjejEtwXD4kwOMs,3323
|
|
347
350
|
supervisely/app/widgets/labeled_image/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
348
351
|
supervisely/app/widgets/labeled_image/labeled_image.py,sha256=IGcVi5UTPcRA0f85f_6NvnOJaceWmAL5vsOWqsj7DTc,2010
|
|
349
352
|
supervisely/app/widgets/line_chart/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -645,7 +648,7 @@ supervisely/convert/pointcloud/lyft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQ
|
|
|
645
648
|
supervisely/convert/pointcloud/lyft/lyft_converter.py,sha256=bM9R3LLt4l5frK1Lhmy_WnhGUYCC7rH94v94sqmLxjY,11010
|
|
646
649
|
supervisely/convert/pointcloud/lyft/lyft_helper.py,sha256=bTe7ryLPfSkW0MjzFP-6AMyDMBtPu8Xk9cx0g0MomoQ,8521
|
|
647
650
|
supervisely/convert/pointcloud/nuscenes_conv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
648
|
-
supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py,sha256=
|
|
651
|
+
supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py,sha256=eMKtiXt1J0psnEfjbUd76ajISo2gvo-jj1wvyKx2BsY,9832
|
|
649
652
|
supervisely/convert/pointcloud/ply/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
650
653
|
supervisely/convert/pointcloud/ply/ply_converter.py,sha256=2ZCYkhJQzUev-sWGsBwCPtj1TGjdcx8o-Q--RAHavp8,2698
|
|
651
654
|
supervisely/convert/pointcloud/ply/ply_helper.py,sha256=YfLiV9m6a4NNEMs0J32dmMTLffMLX4-JPTThMHOEK4w,268
|
|
@@ -662,8 +665,8 @@ supervisely/convert/pointcloud_episodes/kitti_360/kitti_360_helper.py,sha256=EHy
|
|
|
662
665
|
supervisely/convert/pointcloud_episodes/lyft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
663
666
|
supervisely/convert/pointcloud_episodes/lyft/lyft_converter.py,sha256=QXreWUJ-QhoWgLPqRxCayatYCCCuSV6Z2XCZKScrD3o,10419
|
|
664
667
|
supervisely/convert/pointcloud_episodes/nuscenes_conv/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
665
|
-
supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py,sha256=
|
|
666
|
-
supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py,sha256=
|
|
668
|
+
supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py,sha256=tXOeZYPEuIhrlx39M4IoKQRHoNz5FxZkHIigHEKeOHM,12365
|
|
669
|
+
supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py,sha256=V2M4JmPLZHKFfA4GsiTFNVjX4CO90J5XpdDmCQ1PB20,10317
|
|
667
670
|
supervisely/convert/pointcloud_episodes/sly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
668
671
|
supervisely/convert/pointcloud_episodes/sly/sly_pointcloud_episodes_converter.py,sha256=mHmmxeP63oaaXBTEMsmR4hISAiPVptn6qriNGTPPXzo,6322
|
|
669
672
|
supervisely/convert/pointcloud_episodes/sly/sly_pointcloud_episodes_helper.py,sha256=h4WvNH6cEHtjxxhCnU7Hs2vkyJMye0qwabqXNYVTywE,3570
|
|
@@ -1082,7 +1085,7 @@ supervisely/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
|
1082
1085
|
supervisely/user/user.py,sha256=4GSVIupPAxWjIxZmUtH3Dtms_vGV82-49kM_aaR2gBI,319
|
|
1083
1086
|
supervisely/video/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1084
1087
|
supervisely/video/import_utils.py,sha256=b1Nl0gscNsV0iB9nWPeqt8GrkhOeuTZsN1p-d3gDUmE,544
|
|
1085
|
-
supervisely/video/sampling.py,sha256=
|
|
1088
|
+
supervisely/video/sampling.py,sha256=6w-FjpWbEq_u7zonnPEo0MhXN7RofhdgSZd27h45YMQ,20249
|
|
1086
1089
|
supervisely/video/video.py,sha256=QGV1R3qNOJ0zgsfItqv-e7mbEnWqFpE3rcJwt7izC28,20206
|
|
1087
1090
|
supervisely/video_annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
1088
1091
|
supervisely/video_annotation/constants.py,sha256=_gW9iMhVk1w_dUaFiaiyXn66mt13S6bkxC64xpjP-CU,529
|
|
@@ -1121,9 +1124,9 @@ supervisely/worker_proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
1121
1124
|
supervisely/worker_proto/worker_api_pb2.py,sha256=VQfi5JRBHs2pFCK1snec3JECgGnua3Xjqw_-b3aFxuM,59142
|
|
1122
1125
|
supervisely/worker_proto/worker_api_pb2_grpc.py,sha256=3BwQXOaP9qpdi0Dt9EKG--Lm8KGN0C5AgmUfRv77_Jk,28940
|
|
1123
1126
|
supervisely_lib/__init__.py,sha256=7-3QnN8Zf0wj8NCr2oJmqoQWMKKPKTECvjH9pd2S5vY,159
|
|
1124
|
-
supervisely-6.73.
|
|
1125
|
-
supervisely-6.73.
|
|
1126
|
-
supervisely-6.73.
|
|
1127
|
-
supervisely-6.73.
|
|
1128
|
-
supervisely-6.73.
|
|
1129
|
-
supervisely-6.73.
|
|
1127
|
+
supervisely-6.73.428.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
1128
|
+
supervisely-6.73.428.dist-info/METADATA,sha256=GzysKJg4nHPCZiWzNZM2cXwAjNfMrlh2HmakZioubeg,35433
|
|
1129
|
+
supervisely-6.73.428.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
|
|
1130
|
+
supervisely-6.73.428.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
|
|
1131
|
+
supervisely-6.73.428.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
|
|
1132
|
+
supervisely-6.73.428.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|