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.
@@ -865,7 +865,7 @@ class AnnotationApi(ModuleApi):
865
865
  return
866
866
  if len(img_ids) != len(anns):
867
867
  raise RuntimeError(
868
- 'Can not match "img_ids" and "anns" lists, len(img_ids) != len(anns)'
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
- anns.append(
149
- helpers.AnnotationObject(
150
- name,
151
- box,
152
- current_instance_token,
153
- parent_token,
154
- category,
155
- attributes,
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.get_file_name(pcd_path)
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
- nuscenes = NuScenes(version=version, dataroot=input_path, verbose=False)
87
- self._nuscenes: NuScenes = nuscenes
86
+ self._nuscenes: NuScenes = NuScenes(version=version, dataroot=input_path, verbose=False)
88
87
  logger.debug(f"NuScenes initialization took {t.get_sec():.3f} sec")
89
88
  except Exception as e:
90
89
  logger.debug(f"Failed to initialize NuScenes: {e}")
@@ -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["description"]
146
- if len(description) > 255:
147
- # * Trim description to fit into 255 characters limit
148
- sentences = description.split(".")
149
- trimmed_description = ""
150
- for sentence in sentences:
151
- if len(trimmed_description) + len(sentence) + 1 > 255:
152
- break
153
- trimmed_description += sentence + "."
154
- description = trimmed_description.strip()
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
- anns.append(
219
- helpers.AnnotationObject(
220
- name,
221
- box,
222
- current_instance_token,
223
- parent_token,
224
- category,
225
- attributes,
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.get_file_name(pcd_path)
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
- 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
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
- 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.
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
- 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.
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__(self, nuscenes, sensor_name, sensor_token, cs_record, ego_record):
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
- img_path, boxes, cam_intrinsic = nuscenes.get_sample_data(sensor_token)
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
- Retrieves information about the image and its metadata.
191
+ Generates image info based on the camera data.
243
192
 
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.
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
@@ -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 image_id, frame_index in zip(image_ids, frame_indices):
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],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: supervisely
3
- Version: 6.73.426
3
+ Version: 6.73.428
4
4
  Summary: Supervisely Python SDK.
5
5
  Home-page: https://github.com/supervisely/supervisely
6
6
  Author: Supervisely
@@ -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=U6dHUIOt6Fe8XcbX1MA19z-fg91maOumJAawKG5ZJsk,82876
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=1m49TH_P3V5H7yyvpf2m_bgX1TTgAVaR89suCGlNp64,10649
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=GsjO_yFqxCVMBu3CXWb-oz6Ci8j-0uwgMvQIQE_tUgc,6238
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=YZlPjY8F_k270Mqir7yce7KIOh9SlU8i80nr-IaAj1Y,10121
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=O8QIwqwb0DUuYmS8oq6kGv3uTlzS3GyGvAxfL1bYW-s,12764
666
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py,sha256=cJTwhFn1JgblbPjrTrZu30y6FxyjGF-12sMFfvN1xzM,8969
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=jTtXAZrq_hkxFQSSdwFp-lYUKX8HZf1nO-4trzMCQis,20197
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.426.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
1125
- supervisely-6.73.426.dist-info/METADATA,sha256=n8l1EDWAUS40PA5PAItiv2whk9oGDSPg3IkrqWk0shc,35433
1126
- supervisely-6.73.426.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
1127
- supervisely-6.73.426.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
1128
- supervisely-6.73.426.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
1129
- supervisely-6.73.426.dist-info/RECORD,,
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,,