supervisely 6.73.410__py3-none-any.whl → 6.73.470__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of supervisely might be problematic. Click here for more details.
- supervisely/__init__.py +136 -1
- supervisely/_utils.py +81 -0
- supervisely/annotation/json_geometries_map.py +2 -0
- supervisely/annotation/label.py +80 -3
- supervisely/api/annotation_api.py +9 -9
- supervisely/api/api.py +67 -43
- supervisely/api/app_api.py +72 -5
- supervisely/api/dataset_api.py +108 -33
- supervisely/api/entity_annotation/figure_api.py +113 -49
- supervisely/api/image_api.py +82 -0
- supervisely/api/module_api.py +10 -0
- supervisely/api/nn/deploy_api.py +15 -9
- supervisely/api/nn/ecosystem_models_api.py +201 -0
- supervisely/api/nn/neural_network_api.py +12 -3
- supervisely/api/pointcloud/pointcloud_api.py +38 -0
- supervisely/api/pointcloud/pointcloud_episode_annotation_api.py +3 -0
- supervisely/api/project_api.py +213 -6
- supervisely/api/task_api.py +11 -1
- supervisely/api/video/video_annotation_api.py +4 -2
- supervisely/api/video/video_api.py +79 -1
- supervisely/api/video/video_figure_api.py +24 -11
- supervisely/api/volume/volume_api.py +38 -0
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +14 -6
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +88 -0
- supervisely/app/fastapi/subapp.py +175 -42
- supervisely/app/fastapi/templating.py +1 -1
- supervisely/app/fastapi/websocket.py +77 -9
- supervisely/app/singleton.py +21 -0
- supervisely/app/v1/app_service.py +18 -2
- supervisely/app/v1/constants.py +7 -1
- supervisely/app/widgets/__init__.py +11 -1
- supervisely/app/widgets/agent_selector/template.html +1 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/dataset_thumbnail/dataset_thumbnail.py +11 -2
- supervisely/app/widgets/dataset_thumbnail/template.html +3 -1
- supervisely/app/widgets/deploy_model/deploy_model.py +750 -0
- supervisely/app/widgets/dialog/dialog.py +12 -0
- supervisely/app/widgets/dialog/template.html +2 -1
- supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
- supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
- supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
- supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +195 -0
- supervisely/app/widgets/experiment_selector/experiment_selector.py +454 -263
- supervisely/app/widgets/fast_table/fast_table.py +713 -126
- supervisely/app/widgets/fast_table/script.js +492 -95
- supervisely/app/widgets/fast_table/style.css +54 -0
- supervisely/app/widgets/fast_table/template.html +45 -5
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +523 -0
- supervisely/app/widgets/heatmap/script.js +378 -0
- supervisely/app/widgets/heatmap/style.css +227 -0
- supervisely/app/widgets/heatmap/template.html +21 -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/app/widgets/radio_table/radio_table.py +10 -2
- supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
- supervisely/app/widgets/radio_tabs/template.html +1 -0
- supervisely/app/widgets/select/select.py +6 -4
- supervisely/app/widgets/select_dataset/select_dataset.py +6 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +83 -7
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tabs/tabs.py +22 -6
- supervisely/app/widgets/tabs/template.html +5 -1
- supervisely/app/widgets/transfer/style.css +3 -0
- supervisely/app/widgets/transfer/template.html +3 -1
- supervisely/app/widgets/transfer/transfer.py +48 -45
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/convert/image/csv/csv_converter.py +24 -15
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +43 -41
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +75 -51
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +137 -124
- supervisely/convert/video/video_converter.py +2 -2
- supervisely/geometry/polyline_3d.py +110 -0
- supervisely/io/env.py +161 -1
- supervisely/nn/artifacts/__init__.py +1 -1
- supervisely/nn/artifacts/artifacts.py +10 -2
- supervisely/nn/artifacts/detectron2.py +1 -0
- supervisely/nn/artifacts/hrda.py +1 -0
- supervisely/nn/artifacts/mmclassification.py +20 -0
- supervisely/nn/artifacts/mmdetection.py +5 -3
- supervisely/nn/artifacts/mmsegmentation.py +1 -0
- supervisely/nn/artifacts/ritm.py +1 -0
- supervisely/nn/artifacts/rtdetr.py +1 -0
- supervisely/nn/artifacts/unet.py +1 -0
- supervisely/nn/artifacts/utils.py +3 -0
- supervisely/nn/artifacts/yolov5.py +2 -0
- supervisely/nn/artifacts/yolov8.py +1 -0
- supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
- supervisely/nn/experiments.py +9 -0
- supervisely/nn/inference/cache.py +37 -17
- supervisely/nn/inference/gui/serving_gui_template.py +39 -13
- supervisely/nn/inference/inference.py +953 -211
- supervisely/nn/inference/inference_request.py +15 -8
- supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
- supervisely/nn/inference/object_detection/object_detection.py +1 -0
- supervisely/nn/inference/predict_app/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/classes_selector.py +160 -0
- supervisely/nn/inference/predict_app/gui/gui.py +915 -0
- supervisely/nn/inference/predict_app/gui/input_selector.py +344 -0
- supervisely/nn/inference/predict_app/gui/model_selector.py +77 -0
- supervisely/nn/inference/predict_app/gui/output_selector.py +179 -0
- supervisely/nn/inference/predict_app/gui/preview.py +93 -0
- supervisely/nn/inference/predict_app/gui/settings_selector.py +881 -0
- supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
- supervisely/nn/inference/predict_app/gui/utils.py +399 -0
- supervisely/nn/inference/predict_app/predict_app.py +176 -0
- supervisely/nn/inference/session.py +47 -39
- supervisely/nn/inference/tracking/bbox_tracking.py +5 -1
- supervisely/nn/inference/tracking/point_tracking.py +5 -1
- supervisely/nn/inference/tracking/tracker_interface.py +4 -0
- supervisely/nn/inference/uploader.py +9 -5
- supervisely/nn/model/model_api.py +44 -22
- supervisely/nn/model/prediction.py +15 -1
- supervisely/nn/model/prediction_session.py +70 -14
- supervisely/nn/prediction_dto.py +7 -0
- supervisely/nn/tracker/__init__.py +6 -8
- supervisely/nn/tracker/base_tracker.py +54 -0
- supervisely/nn/tracker/botsort/__init__.py +1 -0
- supervisely/nn/tracker/botsort/botsort_config.yaml +30 -0
- supervisely/nn/tracker/botsort/osnet_reid/__init__.py +0 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
- supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
- supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
- supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
- supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
- supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
- supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
- supervisely/nn/tracker/botsort_tracker.py +273 -0
- supervisely/nn/tracker/calculate_metrics.py +264 -0
- supervisely/nn/tracker/utils.py +273 -0
- supervisely/nn/tracker/visualize.py +520 -0
- supervisely/nn/training/gui/gui.py +152 -49
- supervisely/nn/training/gui/hyperparameters_selector.py +1 -1
- supervisely/nn/training/gui/model_selector.py +8 -6
- supervisely/nn/training/gui/train_val_splits_selector.py +144 -71
- supervisely/nn/training/gui/training_artifacts.py +3 -1
- supervisely/nn/training/train_app.py +225 -46
- supervisely/project/pointcloud_episode_project.py +12 -8
- supervisely/project/pointcloud_project.py +12 -8
- supervisely/project/project.py +221 -75
- supervisely/template/experiment/experiment.html.jinja +105 -55
- supervisely/template/experiment/experiment_generator.py +258 -112
- supervisely/template/experiment/header.html.jinja +31 -13
- supervisely/template/experiment/sly-style.css +7 -2
- supervisely/versions.json +3 -1
- supervisely/video/sampling.py +42 -20
- supervisely/video/video.py +41 -12
- supervisely/video_annotation/video_figure.py +38 -4
- supervisely/volume/stl_converter.py +2 -0
- supervisely/worker_api/agent_rpc.py +24 -1
- supervisely/worker_api/rpc_servicer.py +31 -7
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/METADATA +22 -14
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/RECORD +167 -148
- supervisely_lib/__init__.py +6 -1
- supervisely/app/widgets/experiment_selector/style.css +0 -27
- supervisely/app/widgets/experiment_selector/template.html +0 -61
- supervisely/nn/tracker/bot_sort/__init__.py +0 -21
- supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
- supervisely/nn/tracker/bot_sort/matching.py +0 -127
- supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
- supervisely/nn/tracker/deep_sort/__init__.py +0 -6
- supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
- supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
- supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
- supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
- supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
- supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
- supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
- supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
- supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
- supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
- supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
- supervisely/nn/tracker/tracker.py +0 -285
- supervisely/nn/tracker/utils/kalman_filter.py +0 -492
- supervisely/nn/tracking/__init__.py +0 -1
- supervisely/nn/tracking/boxmot.py +0 -114
- supervisely/nn/tracking/tracking.py +0 -24
- /supervisely/{nn/tracker/utils → app/widgets/deploy_model}/__init__.py +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/LICENSE +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/WHEEL +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,7 @@ from supervisely.api.api import Api
|
|
|
5
5
|
from supervisely.app.widgets import Widget
|
|
6
6
|
from supervisely.app.widgets.checkbox.checkbox import Checkbox
|
|
7
7
|
from supervisely.app.widgets.container.container import Container
|
|
8
|
+
from supervisely.app.widgets.field.field import Field
|
|
8
9
|
from supervisely.app.widgets.select.select import Select
|
|
9
10
|
from supervisely.app.widgets.tree_select.tree_select import TreeSelect
|
|
10
11
|
from supervisely.project.project_type import ProjectType
|
|
@@ -97,6 +98,7 @@ class SelectDatasetTree(Widget):
|
|
|
97
98
|
widget_id: Union[str, None] = None,
|
|
98
99
|
show_select_all_datasets_checkbox: bool = True,
|
|
99
100
|
width: int = 193,
|
|
101
|
+
show_selectors_labels: bool = False,
|
|
100
102
|
):
|
|
101
103
|
self._api = Api.from_env()
|
|
102
104
|
|
|
@@ -114,11 +116,29 @@ class SelectDatasetTree(Widget):
|
|
|
114
116
|
# Using environment variables to set the default values if they are not provided.
|
|
115
117
|
self._project_id = project_id or env.project_id(raise_not_found=False)
|
|
116
118
|
self._dataset_id = default_id or env.dataset_id(raise_not_found=False)
|
|
119
|
+
if self._project_id:
|
|
120
|
+
project_info = self._api.project.get_info_by_id(self._project_id)
|
|
121
|
+
if allowed_project_types is not None:
|
|
122
|
+
allowed_values = []
|
|
123
|
+
if not isinstance(allowed_project_types, list):
|
|
124
|
+
allowed_project_types = [allowed_project_types]
|
|
125
|
+
|
|
126
|
+
for pt in allowed_project_types:
|
|
127
|
+
if isinstance(pt, (ProjectType, str)):
|
|
128
|
+
allowed_values.append(str(pt))
|
|
129
|
+
|
|
130
|
+
if project_info.type not in allowed_values:
|
|
131
|
+
self._project_id = None
|
|
117
132
|
|
|
118
133
|
self._multiselect = multiselect
|
|
119
134
|
self._compact = compact
|
|
120
135
|
self._append_to_body = append_to_body
|
|
121
136
|
|
|
137
|
+
# User-defined callbacks
|
|
138
|
+
self._team_changed_callbacks = []
|
|
139
|
+
self._workspace_changed_callbacks = []
|
|
140
|
+
self._project_changed_callbacks = []
|
|
141
|
+
|
|
122
142
|
# Extract values from Enum to match the .type property of the ProjectInfo object.
|
|
123
143
|
self._project_types = None
|
|
124
144
|
if allowed_project_types is not None:
|
|
@@ -141,6 +161,10 @@ class SelectDatasetTree(Widget):
|
|
|
141
161
|
self._select_dataset = None
|
|
142
162
|
self._width = width
|
|
143
163
|
|
|
164
|
+
# Flags
|
|
165
|
+
self._team_is_selectable = team_is_selectable
|
|
166
|
+
self._workspace_is_selectable = workspace_is_selectable
|
|
167
|
+
|
|
144
168
|
# List of widgets will be used to create a Container.
|
|
145
169
|
self._widgets = []
|
|
146
170
|
|
|
@@ -156,6 +180,7 @@ class SelectDatasetTree(Widget):
|
|
|
156
180
|
if show_select_all_datasets_checkbox:
|
|
157
181
|
self._create_select_all_datasets_checkbox(select_all_datasets)
|
|
158
182
|
|
|
183
|
+
self._show_selectors_labels = show_selectors_labels
|
|
159
184
|
# Group the selectors and the dataset selector into a container.
|
|
160
185
|
self._content = Container(self._widgets)
|
|
161
186
|
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
@@ -165,11 +190,25 @@ class SelectDatasetTree(Widget):
|
|
|
165
190
|
for widget in self._widgets:
|
|
166
191
|
widget.disable()
|
|
167
192
|
|
|
193
|
+
if self._select_team is not None:
|
|
194
|
+
if not self._team_is_selectable:
|
|
195
|
+
self._select_team.disable()
|
|
196
|
+
if self._select_workspace is not None:
|
|
197
|
+
if not self._workspace_is_selectable:
|
|
198
|
+
self._select_workspace.disable()
|
|
199
|
+
|
|
168
200
|
def enable(self) -> None:
|
|
169
201
|
"""Enable the widget in the UI."""
|
|
170
202
|
for widget in self._widgets:
|
|
171
203
|
widget.enable()
|
|
172
204
|
|
|
205
|
+
if self._select_team is not None:
|
|
206
|
+
if not self._team_is_selectable:
|
|
207
|
+
self._select_team.disable()
|
|
208
|
+
if self._select_workspace is not None:
|
|
209
|
+
if not self._workspace_is_selectable:
|
|
210
|
+
self._select_workspace.disable()
|
|
211
|
+
|
|
173
212
|
@property
|
|
174
213
|
def team_id(self) -> int:
|
|
175
214
|
"""The ID of the team selected in the widget.
|
|
@@ -290,8 +329,30 @@ class SelectDatasetTree(Widget):
|
|
|
290
329
|
"""
|
|
291
330
|
if not self._multiselect:
|
|
292
331
|
raise ValueError("This method can only be called when multiselect is enabled.")
|
|
332
|
+
self._select_all_datasets_checkbox.uncheck()
|
|
293
333
|
self._select_dataset.set_selected_by_id(dataset_ids)
|
|
294
334
|
|
|
335
|
+
def team_changed(self, func: Callable) -> Callable:
|
|
336
|
+
"""Decorator to set the callback function for the team changed event."""
|
|
337
|
+
if self._compact:
|
|
338
|
+
raise ValueError("callback 'team_changed' is not available in compact mode.")
|
|
339
|
+
self._team_changed_callbacks.append(func)
|
|
340
|
+
return func
|
|
341
|
+
|
|
342
|
+
def workspace_changed(self, func: Callable) -> Callable:
|
|
343
|
+
"""Decorator to set the callback function for the workspace changed event."""
|
|
344
|
+
if self._compact:
|
|
345
|
+
raise ValueError("callback 'workspace_changed' is not available in compact mode.")
|
|
346
|
+
self._workspace_changed_callbacks.append(func)
|
|
347
|
+
return func
|
|
348
|
+
|
|
349
|
+
def project_changed(self, func: Callable) -> Callable:
|
|
350
|
+
"""Decorator to set the callback function for the project changed event."""
|
|
351
|
+
if self._compact:
|
|
352
|
+
raise ValueError("callback 'project_changed' is not available in compact mode.")
|
|
353
|
+
self._project_changed_callbacks.append(func)
|
|
354
|
+
return func
|
|
355
|
+
|
|
295
356
|
def value_changed(self, func: Callable) -> Callable:
|
|
296
357
|
"""Decorator to set the callback function for the value changed event.
|
|
297
358
|
|
|
@@ -335,13 +396,13 @@ class SelectDatasetTree(Widget):
|
|
|
335
396
|
|
|
336
397
|
if checked:
|
|
337
398
|
self._select_dataset.select_all()
|
|
338
|
-
self.
|
|
399
|
+
self._select_dataset_field.hide()
|
|
339
400
|
else:
|
|
340
401
|
self._select_dataset.clear_selected()
|
|
341
|
-
self.
|
|
402
|
+
self._select_dataset_field.show()
|
|
342
403
|
|
|
343
404
|
if select_all_datasets:
|
|
344
|
-
self.
|
|
405
|
+
self._select_dataset_field.hide()
|
|
345
406
|
select_all_datasets_checkbox.check()
|
|
346
407
|
|
|
347
408
|
self._widgets.append(select_all_datasets_checkbox)
|
|
@@ -372,9 +433,10 @@ class SelectDatasetTree(Widget):
|
|
|
372
433
|
self._select_dataset.set_selected_by_id(self._dataset_id)
|
|
373
434
|
if select_all_datasets:
|
|
374
435
|
self._select_dataset.select_all()
|
|
436
|
+
self._select_dataset_field = Field(self._select_dataset, title="Dataset")
|
|
375
437
|
|
|
376
438
|
# Adding the dataset selector to the list of widgets to be added to the container.
|
|
377
|
-
self._widgets.append(self.
|
|
439
|
+
self._widgets.append(self._select_dataset_field)
|
|
378
440
|
|
|
379
441
|
def _create_selectors(self, team_is_selectable: bool, workspace_is_selectable: bool):
|
|
380
442
|
"""Create the team, workspace, and project selectors.
|
|
@@ -394,6 +456,9 @@ class SelectDatasetTree(Widget):
|
|
|
394
456
|
self._select_workspace.set(items=self._get_select_items(team_id=team_id))
|
|
395
457
|
self._team_id = team_id
|
|
396
458
|
|
|
459
|
+
for callback in self._team_changed_callbacks:
|
|
460
|
+
callback(team_id)
|
|
461
|
+
|
|
397
462
|
def workspace_selector_handler(workspace_id: int):
|
|
398
463
|
"""Handler function for the event when the workspace selector value changes.
|
|
399
464
|
|
|
@@ -403,6 +468,9 @@ class SelectDatasetTree(Widget):
|
|
|
403
468
|
self._select_project.set(items=self._get_select_items(workspace_id=workspace_id))
|
|
404
469
|
self._workspace_id = workspace_id
|
|
405
470
|
|
|
471
|
+
for callback in self._workspace_changed_callbacks:
|
|
472
|
+
callback(workspace_id)
|
|
473
|
+
|
|
406
474
|
def project_selector_handler(project_id: int):
|
|
407
475
|
"""Handler function for the event when the project selector value changes.
|
|
408
476
|
|
|
@@ -417,7 +485,10 @@ class SelectDatasetTree(Widget):
|
|
|
417
485
|
and self._select_all_datasets_checkbox.is_checked()
|
|
418
486
|
):
|
|
419
487
|
self._select_dataset.select_all()
|
|
420
|
-
self.
|
|
488
|
+
self._select_dataset_field.hide()
|
|
489
|
+
|
|
490
|
+
for callback in self._project_changed_callbacks:
|
|
491
|
+
callback(project_id)
|
|
421
492
|
|
|
422
493
|
self._select_team = Select(
|
|
423
494
|
items=self._get_select_items(),
|
|
@@ -428,6 +499,7 @@ class SelectDatasetTree(Widget):
|
|
|
428
499
|
self._select_team.set_value(self._team_id)
|
|
429
500
|
if not team_is_selectable:
|
|
430
501
|
self._select_team.disable()
|
|
502
|
+
self._select_team_field = Field(self._select_team, title="Team")
|
|
431
503
|
|
|
432
504
|
self._select_workspace = Select(
|
|
433
505
|
items=self._get_select_items(team_id=self._team_id),
|
|
@@ -438,6 +510,7 @@ class SelectDatasetTree(Widget):
|
|
|
438
510
|
self._select_workspace.set_value(self._workspace_id)
|
|
439
511
|
if not workspace_is_selectable:
|
|
440
512
|
self._select_workspace.disable()
|
|
513
|
+
self._select_workspace_field = Field(self._select_workspace, title="Workspace")
|
|
441
514
|
|
|
442
515
|
self._select_project = Select(
|
|
443
516
|
items=self._get_select_items(workspace_id=self._workspace_id),
|
|
@@ -446,14 +519,17 @@ class SelectDatasetTree(Widget):
|
|
|
446
519
|
width_px=self._width,
|
|
447
520
|
)
|
|
448
521
|
self._select_project.set_value(self._project_id)
|
|
522
|
+
self._select_project_field = Field(self._select_project, title="Project")
|
|
449
523
|
|
|
450
|
-
# Register the event handlers.
|
|
524
|
+
# Register the event handlers._select_project
|
|
451
525
|
self._select_team.value_changed(team_selector_handler)
|
|
452
526
|
self._select_workspace.value_changed(workspace_selector_handler)
|
|
453
527
|
self._select_project.value_changed(project_selector_handler)
|
|
454
528
|
|
|
455
529
|
# Adding widgets to the list, so they can be added to the container.
|
|
456
|
-
self._widgets.extend(
|
|
530
|
+
self._widgets.extend(
|
|
531
|
+
[self._select_team_field, self._select_workspace_field, self._select_project_field]
|
|
532
|
+
)
|
|
457
533
|
|
|
458
534
|
def _get_select_items(self, **kwargs) -> List[Select.Item]:
|
|
459
535
|
"""Get the list of items for the team, workspace, and project selectors.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# isort: skip_file
|
|
2
|
+
|
|
1
3
|
import copy
|
|
2
4
|
import io
|
|
3
5
|
|
|
@@ -54,9 +56,8 @@ class PackerUnpacker:
|
|
|
54
56
|
|
|
55
57
|
@staticmethod
|
|
56
58
|
def pandas_unpacker(data: pd.DataFrame):
|
|
57
|
-
data
|
|
58
|
-
#
|
|
59
|
-
|
|
59
|
+
# Keep None/NaN values in source data, don't replace them
|
|
60
|
+
# They will be converted to "" only when sending to frontend
|
|
60
61
|
unpacked_data = {
|
|
61
62
|
"columns": data.columns.to_list(),
|
|
62
63
|
"data": data.values.tolist(),
|
|
@@ -169,9 +170,35 @@ class Table(Widget):
|
|
|
169
170
|
|
|
170
171
|
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
171
172
|
|
|
173
|
+
def _prepare_data_for_frontend(self, data_dict):
|
|
174
|
+
"""Convert None and NaN values to empty strings for frontend display.
|
|
175
|
+
This preserves the original None/NaN values in _parsed_data.
|
|
176
|
+
"""
|
|
177
|
+
import math
|
|
178
|
+
|
|
179
|
+
display_data = copy.deepcopy(data_dict)
|
|
180
|
+
|
|
181
|
+
# Convert None/NaN in data rows
|
|
182
|
+
for row in display_data.get("data", []):
|
|
183
|
+
for i in range(len(row)):
|
|
184
|
+
value = row[i]
|
|
185
|
+
# Check for None or NaN (NaN is a float that doesn't equal itself)
|
|
186
|
+
if value is None or (isinstance(value, float) and math.isnan(value)):
|
|
187
|
+
row[i] = ""
|
|
188
|
+
|
|
189
|
+
# Convert None/NaN in summary row if present
|
|
190
|
+
if "summaryRow" in display_data and display_data["summaryRow"] is not None:
|
|
191
|
+
summary_row = display_data["summaryRow"]
|
|
192
|
+
for i in range(len(summary_row)):
|
|
193
|
+
value = summary_row[i]
|
|
194
|
+
if value is None or (isinstance(value, float) and math.isnan(value)):
|
|
195
|
+
summary_row[i] = ""
|
|
196
|
+
|
|
197
|
+
return display_data
|
|
198
|
+
|
|
172
199
|
def get_json_data(self):
|
|
173
200
|
return {
|
|
174
|
-
"table_data": self._parsed_data,
|
|
201
|
+
"table_data": self._prepare_data_for_frontend(self._parsed_data),
|
|
175
202
|
"table_options": {
|
|
176
203
|
"perPage": self._per_page,
|
|
177
204
|
"pageSizes": self._page_sizes,
|
|
@@ -255,13 +282,17 @@ class Table(Widget):
|
|
|
255
282
|
|
|
256
283
|
def read_json(self, value: dict) -> None:
|
|
257
284
|
self._update_table_data(input_data=value)
|
|
258
|
-
DataJson()[self.widget_id]["table_data"] = self.
|
|
285
|
+
DataJson()[self.widget_id]["table_data"] = self._prepare_data_for_frontend(
|
|
286
|
+
self._parsed_data
|
|
287
|
+
)
|
|
259
288
|
DataJson().send_changes()
|
|
260
289
|
self.clear_selection()
|
|
261
290
|
|
|
262
291
|
def read_pandas(self, value: pd.DataFrame) -> None:
|
|
263
292
|
self._update_table_data(input_data=value)
|
|
264
|
-
DataJson()[self.widget_id]["table_data"] = self.
|
|
293
|
+
DataJson()[self.widget_id]["table_data"] = self._prepare_data_for_frontend(
|
|
294
|
+
self._parsed_data
|
|
295
|
+
)
|
|
265
296
|
DataJson().send_changes()
|
|
266
297
|
self.clear_selection()
|
|
267
298
|
|
|
@@ -272,7 +303,9 @@ class Table(Widget):
|
|
|
272
303
|
index = len(table_data) if index > len(table_data) or index < 0 else index
|
|
273
304
|
|
|
274
305
|
self._parsed_data["data"].insert(index, data)
|
|
275
|
-
DataJson()[self.widget_id]["table_data"] = self.
|
|
306
|
+
DataJson()[self.widget_id]["table_data"] = self._prepare_data_for_frontend(
|
|
307
|
+
self._parsed_data
|
|
308
|
+
)
|
|
276
309
|
DataJson().send_changes()
|
|
277
310
|
|
|
278
311
|
def pop_row(self, index=-1):
|
|
@@ -284,7 +317,9 @@ class Table(Widget):
|
|
|
284
317
|
|
|
285
318
|
if len(self._parsed_data["data"]) != 0:
|
|
286
319
|
popped_row = self._parsed_data["data"].pop(index)
|
|
287
|
-
DataJson()[self.widget_id]["table_data"] = self.
|
|
320
|
+
DataJson()[self.widget_id]["table_data"] = self._prepare_data_for_frontend(
|
|
321
|
+
self._parsed_data
|
|
322
|
+
)
|
|
288
323
|
DataJson().send_changes()
|
|
289
324
|
return popped_row
|
|
290
325
|
|
|
@@ -382,11 +417,27 @@ class Table(Widget):
|
|
|
382
417
|
StateJson()[self.widget_id]["selected_row"] = {}
|
|
383
418
|
StateJson().send_changes()
|
|
384
419
|
|
|
420
|
+
@staticmethod
|
|
421
|
+
def _values_equal(val1, val2):
|
|
422
|
+
"""Compare two values, handling NaN specially."""
|
|
423
|
+
import math
|
|
424
|
+
|
|
425
|
+
# Check if both are NaN
|
|
426
|
+
is_nan1 = isinstance(val1, float) and math.isnan(val1)
|
|
427
|
+
is_nan2 = isinstance(val2, float) and math.isnan(val2)
|
|
428
|
+
if is_nan1 and is_nan2:
|
|
429
|
+
return True
|
|
430
|
+
# Check if both are None
|
|
431
|
+
if val1 is None and val2 is None:
|
|
432
|
+
return True
|
|
433
|
+
# Regular comparison
|
|
434
|
+
return val1 == val2
|
|
435
|
+
|
|
385
436
|
def delete_row(self, key_column_name, key_cell_value):
|
|
386
437
|
col_index = self._parsed_data["columns"].index(key_column_name)
|
|
387
438
|
row_indices = []
|
|
388
439
|
for idx, row in enumerate(self._parsed_data["data"]):
|
|
389
|
-
if row[col_index]
|
|
440
|
+
if self._values_equal(row[col_index], key_cell_value):
|
|
390
441
|
row_indices.append(idx)
|
|
391
442
|
if len(row_indices) == 0:
|
|
392
443
|
raise ValueError('Column "{key_column_name}" does not have value "{key_cell_value}"')
|
|
@@ -400,7 +451,7 @@ class Table(Widget):
|
|
|
400
451
|
key_col_index = self._parsed_data["columns"].index(key_column_name)
|
|
401
452
|
row_indices = []
|
|
402
453
|
for idx, row in enumerate(self._parsed_data["data"]):
|
|
403
|
-
if row[key_col_index]
|
|
454
|
+
if self._values_equal(row[key_col_index], key_cell_value):
|
|
404
455
|
row_indices.append(idx)
|
|
405
456
|
if len(row_indices) == 0:
|
|
406
457
|
raise ValueError('Column "{key_column_name}" does not have value "{key_cell_value}"')
|
|
@@ -411,20 +462,24 @@ class Table(Widget):
|
|
|
411
462
|
|
|
412
463
|
col_index = self._parsed_data["columns"].index(column_name)
|
|
413
464
|
self._parsed_data["data"][row_indices[0]][col_index] = new_value
|
|
414
|
-
DataJson()[self.widget_id]["table_data"] = self.
|
|
465
|
+
DataJson()[self.widget_id]["table_data"] = self._prepare_data_for_frontend(
|
|
466
|
+
self._parsed_data
|
|
467
|
+
)
|
|
415
468
|
DataJson().send_changes()
|
|
416
469
|
|
|
417
470
|
def update_matching_cells(self, key_column_name, key_cell_value, column_name, new_value):
|
|
418
471
|
key_col_index = self._parsed_data["columns"].index(key_column_name)
|
|
419
472
|
row_indices = []
|
|
420
473
|
for idx, row in enumerate(self._parsed_data["data"]):
|
|
421
|
-
if row[key_col_index]
|
|
474
|
+
if self._values_equal(row[key_col_index], key_cell_value):
|
|
422
475
|
row_indices.append(idx)
|
|
423
476
|
|
|
424
477
|
col_index = self._parsed_data["columns"].index(column_name)
|
|
425
478
|
for row_idx in row_indices:
|
|
426
479
|
self._parsed_data["data"][row_idx][col_index] = new_value
|
|
427
|
-
DataJson()[self.widget_id]["table_data"] = self.
|
|
480
|
+
DataJson()[self.widget_id]["table_data"] = self._prepare_data_for_frontend(
|
|
481
|
+
self._parsed_data
|
|
482
|
+
)
|
|
428
483
|
DataJson().send_changes()
|
|
429
484
|
|
|
430
485
|
def sort(self, column_id: int = None, direction: Optional[Literal["asc", "desc"]] = None):
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
from typing import List, Optional, Dict
|
|
2
|
-
from supervisely.app import StateJson
|
|
3
|
-
from supervisely.app.widgets import Widget
|
|
4
1
|
import traceback
|
|
5
|
-
from
|
|
2
|
+
from typing import Dict, List, Optional
|
|
6
3
|
|
|
4
|
+
from supervisely import logger
|
|
5
|
+
from supervisely.app import StateJson
|
|
6
|
+
from supervisely.app.content import DataJson
|
|
7
|
+
from supervisely.app.widgets import Widget
|
|
7
8
|
|
|
8
9
|
try:
|
|
9
10
|
from typing import Literal
|
|
@@ -34,7 +35,7 @@ class Tabs(Widget):
|
|
|
34
35
|
raise ValueError("You can specify up to 10 tabs.")
|
|
35
36
|
if len(set(labels)) != len(labels):
|
|
36
37
|
raise ValueError("All of tab labels should be unique.")
|
|
37
|
-
self._items = []
|
|
38
|
+
self._items: List[Tabs.TabPane] = []
|
|
38
39
|
for label, widget in zip(labels, contents):
|
|
39
40
|
self._items.append(Tabs.TabPane(label=label, content=widget))
|
|
40
41
|
self._value = labels[0]
|
|
@@ -43,7 +44,10 @@ class Tabs(Widget):
|
|
|
43
44
|
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
44
45
|
|
|
45
46
|
def get_json_data(self) -> Dict:
|
|
46
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
"type": self._type,
|
|
49
|
+
"tabsOptions": {item.name: {"disabled": False} for item in self._items},
|
|
50
|
+
}
|
|
47
51
|
|
|
48
52
|
def get_json_state(self) -> Dict:
|
|
49
53
|
return {"value": self._value}
|
|
@@ -56,6 +60,18 @@ class Tabs(Widget):
|
|
|
56
60
|
def get_active_tab(self) -> str:
|
|
57
61
|
return StateJson()[self.widget_id]["value"]
|
|
58
62
|
|
|
63
|
+
def disable_tab(self, tab_name: str):
|
|
64
|
+
if tab_name not in [item.name for item in self._items]:
|
|
65
|
+
raise ValueError(f"Tab with name '{tab_name}' does not exist.")
|
|
66
|
+
DataJson()[self.widget_id]["tabsOptions"][tab_name]["disabled"] = True
|
|
67
|
+
DataJson().send_changes()
|
|
68
|
+
|
|
69
|
+
def enable_tab(self, tab_name: str):
|
|
70
|
+
if tab_name not in [item.name for item in self._items]:
|
|
71
|
+
raise ValueError(f"Tab with name '{tab_name}' does not exist.")
|
|
72
|
+
DataJson()[self.widget_id]["tabsOptions"][tab_name]["disabled"] = False
|
|
73
|
+
DataJson().send_changes()
|
|
74
|
+
|
|
59
75
|
def click(self, func):
|
|
60
76
|
route_path = self.get_route_path(Tabs.Routes.CLICK)
|
|
61
77
|
server = self._sly_app.get_server()
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
%}
|
|
12
12
|
>
|
|
13
13
|
{% for tab_pane in widget._items %}
|
|
14
|
-
<el-tab-pane
|
|
14
|
+
<el-tab-pane
|
|
15
|
+
label="{{{tab_pane.label}}}"
|
|
16
|
+
name="{{{tab_pane.name}}}"
|
|
17
|
+
:disabled="data.{{{widget.widget_id}}}.tabsOptions['{{{tab_pane.name}}}'].disabled"
|
|
18
|
+
>
|
|
15
19
|
{{{ tab_pane.content }}}
|
|
16
20
|
</el-tab-pane>
|
|
17
21
|
{% endfor %}
|