supervisely 6.73.452__py3-none-any.whl → 6.73.513__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- supervisely/__init__.py +25 -1
- supervisely/annotation/annotation.py +8 -2
- supervisely/annotation/json_geometries_map.py +13 -12
- supervisely/api/annotation_api.py +6 -3
- supervisely/api/api.py +2 -0
- supervisely/api/app_api.py +10 -1
- supervisely/api/dataset_api.py +74 -12
- supervisely/api/entities_collection_api.py +10 -0
- supervisely/api/entity_annotation/figure_api.py +28 -0
- supervisely/api/entity_annotation/object_api.py +3 -3
- supervisely/api/entity_annotation/tag_api.py +63 -12
- supervisely/api/guides_api.py +210 -0
- supervisely/api/image_api.py +4 -0
- supervisely/api/labeling_job_api.py +83 -1
- supervisely/api/labeling_queue_api.py +33 -7
- supervisely/api/module_api.py +5 -0
- supervisely/api/project_api.py +71 -26
- supervisely/api/storage_api.py +3 -1
- supervisely/api/task_api.py +13 -2
- supervisely/api/team_api.py +4 -3
- supervisely/api/video/video_annotation_api.py +119 -3
- supervisely/api/video/video_api.py +65 -14
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +23 -7
- supervisely/app/development/development.py +18 -2
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +105 -0
- supervisely/app/fastapi/subapp.py +88 -42
- supervisely/app/fastapi/websocket.py +77 -9
- supervisely/app/singleton.py +21 -0
- supervisely/app/v1/app_service.py +18 -2
- supervisely/app/v1/constants.py +7 -1
- supervisely/app/widgets/__init__.py +6 -0
- supervisely/app/widgets/activity_feed/__init__.py +0 -0
- supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
- supervisely/app/widgets/activity_feed/style.css +78 -0
- supervisely/app/widgets/activity_feed/template.html +22 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
- supervisely/app/widgets/classes_list_selector/template.html +60 -93
- supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
- supervisely/app/widgets/classes_table/classes_table.py +1 -0
- supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
- supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
- supervisely/app/widgets/fast_table/fast_table.py +184 -60
- supervisely/app/widgets/fast_table/template.html +1 -1
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +564 -0
- supervisely/app/widgets/heatmap/script.js +533 -0
- supervisely/app/widgets/heatmap/style.css +233 -0
- supervisely/app/widgets/heatmap/template.html +21 -0
- supervisely/app/widgets/modal/__init__.py +0 -0
- supervisely/app/widgets/modal/modal.py +198 -0
- supervisely/app/widgets/modal/template.html +10 -0
- supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
- supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
- supervisely/app/widgets/radio_tabs/template.html +1 -0
- supervisely/app/widgets/select/select.py +6 -3
- supervisely/app/widgets/select_class/__init__.py +0 -0
- supervisely/app/widgets/select_class/select_class.py +363 -0
- supervisely/app/widgets/select_class/template.html +50 -0
- supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
- supervisely/app/widgets/select_tag/__init__.py +0 -0
- supervisely/app/widgets/select_tag/select_tag.py +352 -0
- supervisely/app/widgets/select_tag/template.html +64 -0
- supervisely/app/widgets/select_team/select_team.py +37 -4
- supervisely/app/widgets/select_team/template.html +4 -5
- supervisely/app/widgets/select_user/__init__.py +0 -0
- supervisely/app/widgets/select_user/select_user.py +270 -0
- supervisely/app/widgets/select_user/template.html +13 -0
- supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
- supervisely/app/widgets/select_workspace/template.html +9 -12
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/aug/aug.py +6 -2
- supervisely/convert/base_converter.py +1 -0
- supervisely/convert/converter.py +2 -2
- supervisely/convert/image/image_converter.py +3 -1
- supervisely/convert/image/image_helper.py +48 -4
- supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
- supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
- supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
- supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
- supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
- supervisely/convert/pointcloud/las/las_converter.py +13 -1
- supervisely/convert/pointcloud/las/las_helper.py +110 -11
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
- supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
- supervisely/convert/video/__init__.py +1 -0
- supervisely/convert/video/multi_view/__init__.py +0 -0
- supervisely/convert/video/multi_view/multi_view.py +543 -0
- supervisely/convert/video/sly/sly_video_converter.py +359 -3
- supervisely/convert/video/video_converter.py +22 -2
- supervisely/convert/volume/dicom/dicom_converter.py +13 -5
- supervisely/convert/volume/dicom/dicom_helper.py +30 -18
- supervisely/geometry/constants.py +1 -0
- supervisely/geometry/geometry.py +4 -0
- supervisely/geometry/helpers.py +5 -1
- supervisely/geometry/oriented_bbox.py +676 -0
- supervisely/geometry/rectangle.py +2 -1
- supervisely/io/env.py +76 -1
- supervisely/io/fs.py +21 -0
- supervisely/nn/benchmark/base_evaluator.py +104 -11
- supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
- supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
- supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
- supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
- supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
- supervisely/nn/inference/cache.py +43 -18
- supervisely/nn/inference/gui/serving_gui_template.py +5 -2
- supervisely/nn/inference/inference.py +795 -199
- supervisely/nn/inference/inference_request.py +42 -9
- supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
- supervisely/nn/inference/predict_app/gui/gui.py +676 -488
- supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
- supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
- supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
- supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
- supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
- supervisely/nn/inference/predict_app/gui/utils.py +236 -119
- supervisely/nn/inference/predict_app/predict_app.py +2 -2
- supervisely/nn/inference/session.py +43 -35
- supervisely/nn/inference/tracking/bbox_tracking.py +113 -34
- supervisely/nn/inference/tracking/tracker_interface.py +7 -2
- supervisely/nn/inference/uploader.py +139 -12
- supervisely/nn/live_training/__init__.py +7 -0
- supervisely/nn/live_training/api_server.py +111 -0
- supervisely/nn/live_training/artifacts_utils.py +243 -0
- supervisely/nn/live_training/checkpoint_utils.py +229 -0
- supervisely/nn/live_training/dynamic_sampler.py +44 -0
- supervisely/nn/live_training/helpers.py +14 -0
- supervisely/nn/live_training/incremental_dataset.py +146 -0
- supervisely/nn/live_training/live_training.py +497 -0
- supervisely/nn/live_training/loss_plateau_detector.py +111 -0
- supervisely/nn/live_training/request_queue.py +52 -0
- supervisely/nn/model/model_api.py +9 -0
- supervisely/nn/prediction_dto.py +12 -1
- supervisely/nn/tracker/base_tracker.py +11 -1
- supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
- supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
- supervisely/nn/tracker/botsort_tracker.py +94 -65
- supervisely/nn/tracker/visualize.py +87 -90
- supervisely/nn/training/gui/classes_selector.py +16 -1
- supervisely/nn/training/train_app.py +28 -29
- supervisely/project/data_version.py +115 -51
- supervisely/project/download.py +1 -1
- supervisely/project/pointcloud_episode_project.py +37 -8
- supervisely/project/pointcloud_project.py +30 -2
- supervisely/project/project.py +14 -2
- supervisely/project/project_meta.py +27 -1
- supervisely/project/project_settings.py +32 -18
- supervisely/project/versioning/__init__.py +1 -0
- supervisely/project/versioning/common.py +20 -0
- supervisely/project/versioning/schema_fields.py +35 -0
- supervisely/project/versioning/video_schema.py +221 -0
- supervisely/project/versioning/volume_schema.py +87 -0
- supervisely/project/video_project.py +717 -15
- supervisely/project/volume_project.py +623 -5
- supervisely/template/experiment/experiment.html.jinja +4 -4
- supervisely/template/experiment/experiment_generator.py +14 -21
- supervisely/template/live_training/__init__.py +0 -0
- supervisely/template/live_training/header.html.jinja +96 -0
- supervisely/template/live_training/live_training.html.jinja +51 -0
- supervisely/template/live_training/live_training_generator.py +464 -0
- supervisely/template/live_training/sly-style.css +402 -0
- supervisely/template/live_training/template.html.jinja +18 -0
- supervisely/versions.json +28 -26
- supervisely/video/sampling.py +39 -20
- supervisely/video/video.py +40 -11
- supervisely/video_annotation/video_object.py +29 -4
- supervisely/volume/stl_converter.py +2 -0
- supervisely/worker_api/agent_rpc.py +24 -1
- supervisely/worker_api/rpc_servicer.py +31 -7
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/METADATA +56 -39
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/RECORD +189 -142
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
.heatmap-container {
|
|
2
|
+
max-width: 100%;
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.image-wrapper {
|
|
7
|
+
position: relative;
|
|
8
|
+
max-width: 100%;
|
|
9
|
+
cursor: pointer;
|
|
10
|
+
overflow: visible; /* Allow popup to overflow */
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
transition: height 0.2s ease;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.image-wrapper img {
|
|
16
|
+
width: 100%;
|
|
17
|
+
height: 100%;
|
|
18
|
+
display: block;
|
|
19
|
+
object-fit: fill;
|
|
20
|
+
object-position: center;
|
|
21
|
+
-webkit-user-drag: none;
|
|
22
|
+
user-select: none;
|
|
23
|
+
-webkit-user-select: none;
|
|
24
|
+
-moz-user-select: none;
|
|
25
|
+
-ms-user-select: none;
|
|
26
|
+
pointer-events: auto;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.overlay-image {
|
|
30
|
+
position: absolute;
|
|
31
|
+
top: 0;
|
|
32
|
+
left: 0;
|
|
33
|
+
width: 100%;
|
|
34
|
+
height: 100%;
|
|
35
|
+
pointer-events: none;
|
|
36
|
+
object-fit: contain;
|
|
37
|
+
image-rendering: pixelated;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.heatmap-header {
|
|
41
|
+
display: flex;
|
|
42
|
+
justify-content: space-between;
|
|
43
|
+
padding-bottom: 8px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.opacity-slider {
|
|
47
|
+
background: rgba(255, 255, 255);
|
|
48
|
+
border-radius: 8px;
|
|
49
|
+
padding: 10px 10px;
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.opacity-slider .opacity-label {
|
|
57
|
+
display: flex;
|
|
58
|
+
justify-content: space-between;
|
|
59
|
+
width: 100%;
|
|
60
|
+
color: #353a44;
|
|
61
|
+
font-weight: 400;
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.opacity-slider .opacity-label .opacity-value{
|
|
66
|
+
align-self: flex-end;
|
|
67
|
+
font-size: 12px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.opacity-slider .slider {
|
|
71
|
+
width: 145px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.opacity-slider .slider .el-slider__runway {
|
|
75
|
+
margin: 8px 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.legend {
|
|
79
|
+
bottom: 8px;
|
|
80
|
+
background: rgba(255, 255, 255);
|
|
81
|
+
border-radius: 8px;
|
|
82
|
+
padding: 4px 8px;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 6px;
|
|
86
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.legend-gradient {
|
|
90
|
+
width: 120px;
|
|
91
|
+
height: 12px;
|
|
92
|
+
border-radius: 4px;
|
|
93
|
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.legend-label {
|
|
97
|
+
font-size: 0.75rem;
|
|
98
|
+
font-weight: 500;
|
|
99
|
+
color: #333;
|
|
100
|
+
white-space: nowrap;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.click-indicator {
|
|
104
|
+
position: absolute;
|
|
105
|
+
pointer-events: none;
|
|
106
|
+
z-index: 1001;
|
|
107
|
+
transform: translate(-50%, -50%);
|
|
108
|
+
animation: click-appear 0.3s ease;
|
|
109
|
+
transition: opacity 0.3s ease;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.click-indicator.hiding {
|
|
113
|
+
opacity: 0;
|
|
114
|
+
transition: opacity 0.3s ease;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@keyframes click-appear {
|
|
118
|
+
from {
|
|
119
|
+
transform: translate(-50%, -50%) scale(0);
|
|
120
|
+
}
|
|
121
|
+
to {
|
|
122
|
+
transform: translate(-50%, -50%) scale(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.click-dot {
|
|
127
|
+
width: 12px;
|
|
128
|
+
height: 12px;
|
|
129
|
+
background: white;
|
|
130
|
+
border: 2px solid #3b82f6;
|
|
131
|
+
border-radius: 50%;
|
|
132
|
+
box-sizing: border-box;
|
|
133
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), 0 0 0 2px rgba(59, 130, 246, 0.2);
|
|
134
|
+
animation: pulse-ring 1.5s ease-out infinite;
|
|
135
|
+
transform: translate(1px, -1px);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@keyframes pulse-ring {
|
|
139
|
+
0% {
|
|
140
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), 0 0 0 2px rgba(59, 130, 246, 0.4);
|
|
141
|
+
}
|
|
142
|
+
50% {
|
|
143
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), 0 0 0 6px rgba(59, 130, 246, 0.1);
|
|
144
|
+
}
|
|
145
|
+
100% {
|
|
146
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3), 0 0 0 2px rgba(59, 130, 246, 0.4);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.value-popup {
|
|
151
|
+
position: absolute;
|
|
152
|
+
background: rgba(255, 255, 255, 0.95);
|
|
153
|
+
color: #333;
|
|
154
|
+
padding: 6px 12px;
|
|
155
|
+
border-radius: 6px;
|
|
156
|
+
pointer-events: none;
|
|
157
|
+
z-index: 1002;
|
|
158
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
159
|
+
white-space: nowrap;
|
|
160
|
+
animation: popup-fade-in 0.2s ease;
|
|
161
|
+
transition: opacity 0.3s ease;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.value-popup.hiding {
|
|
165
|
+
opacity: 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@keyframes popup-fade-in {
|
|
169
|
+
from {
|
|
170
|
+
opacity: 0;
|
|
171
|
+
}
|
|
172
|
+
to {
|
|
173
|
+
opacity: 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.value-popup-content {
|
|
178
|
+
display: flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
z-index: 9999;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.value-popup-value {
|
|
185
|
+
font-size: 14px;
|
|
186
|
+
font-weight: 600;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.value-popup-arrow {
|
|
190
|
+
position: absolute;
|
|
191
|
+
width: 8px;
|
|
192
|
+
height: 8px;
|
|
193
|
+
background: rgba(255, 255, 255, 0.95);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Arrow positions and rotations for different popup positions */
|
|
197
|
+
.popup-position-top .value-popup-arrow {
|
|
198
|
+
bottom: -4px;
|
|
199
|
+
left: 50%;
|
|
200
|
+
margin-left: -4px;
|
|
201
|
+
transform: rotate(45deg);
|
|
202
|
+
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.popup-position-bottom .value-popup-arrow {
|
|
206
|
+
top: -4px;
|
|
207
|
+
left: 50%;
|
|
208
|
+
margin-left: -4px;
|
|
209
|
+
transform: rotate(45deg);
|
|
210
|
+
box-shadow: -2px -2px 3px rgba(0, 0, 0, 0.1);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.popup-position-right .value-popup-arrow {
|
|
214
|
+
left: -4px;
|
|
215
|
+
top: 50%;
|
|
216
|
+
margin-top: -4px;
|
|
217
|
+
transform: rotate(45deg);
|
|
218
|
+
box-shadow: -2px 2px 3px rgba(0, 0, 0, 0.1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.popup-position-left .value-popup-arrow {
|
|
222
|
+
right: -4px;
|
|
223
|
+
top: 50%;
|
|
224
|
+
margin-top: -4px;
|
|
225
|
+
transform: rotate(45deg);
|
|
226
|
+
box-shadow: 2px -2px 3px rgba(0, 0, 0, 0.1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.image-container {
|
|
230
|
+
position: relative;
|
|
231
|
+
overflow: hidden;
|
|
232
|
+
max-width: 100%;
|
|
233
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<link rel="stylesheet" href="./sly/css/app/widgets/heatmap/style.css" />
|
|
2
|
+
<heatmap-image
|
|
3
|
+
:background-url="data.{{{widget.widget_id}}}.backgroundUrl"
|
|
4
|
+
:mask-url="data.{{{widget.widget_id}}}.heatmapUrl"
|
|
5
|
+
:opacity.sync="state.{{{widget.widget_id}}}.opacity"
|
|
6
|
+
:width="data.{{{widget.widget_id}}}.width"
|
|
7
|
+
:height="data.{{{widget.widget_id}}}.height"
|
|
8
|
+
:mask-width="data.{{{widget.widget_id}}}.maskWidth"
|
|
9
|
+
:mask-height="data.{{{widget.widget_id}}}.maskHeight"
|
|
10
|
+
:min-value="data.{{{widget.widget_id}}}.minValue"
|
|
11
|
+
:max-value="data.{{{widget.widget_id}}}.maxValue"
|
|
12
|
+
:legend-colors="data.{{{widget.widget_id}}}.legendColors"
|
|
13
|
+
:clicked-value="state.{{{widget.widget_id}}}.clickedValue"
|
|
14
|
+
:mask-x="state.{{{widget.widget_id}}}.maskX"
|
|
15
|
+
:mask-y="state.{{{widget.widget_id}}}.maskY"
|
|
16
|
+
@update:clicked-value="state.{{{widget.widget_id}}}.clickedValue = $event"
|
|
17
|
+
@update:mask-x="state.{{{widget.widget_id}}}.maskX = $event"
|
|
18
|
+
@update:mask-y="state.{{{widget.widget_id}}}.maskY = $event"
|
|
19
|
+
:on-image-click="() => {post('/{{{widget.widget_id}}}/heatmap_clicked_cb')}"
|
|
20
|
+
>
|
|
21
|
+
</heatmap-image>
|
|
File without changes
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
from typing import Dict, List, Literal, Optional
|
|
2
|
+
|
|
3
|
+
from supervisely.app.content import DataJson, StateJson
|
|
4
|
+
from supervisely.app.widgets import Widget
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Modal(Widget):
|
|
8
|
+
"""Modal is a widget that displays content in a modal overlay window with a close button.
|
|
9
|
+
It can contain any other widgets, similar to Container, and provides programmatic control
|
|
10
|
+
to show/hide the modal.
|
|
11
|
+
|
|
12
|
+
Read about it in `Developer Portal <https://developer.supervisely.com/app-development/widgets/layouts-and-containers/modal>`_
|
|
13
|
+
(including screenshots and examples).
|
|
14
|
+
|
|
15
|
+
:param title: Modal window title
|
|
16
|
+
:type title: str
|
|
17
|
+
:param widgets: List of widgets to be displayed inside the modal
|
|
18
|
+
:type widgets: Optional[List[Widget]]
|
|
19
|
+
:param size: Modal size, one of: tiny, small, large, full
|
|
20
|
+
:type size: Literal["tiny", "small", "large", "full"]
|
|
21
|
+
:param widget_id: An identifier of the widget.
|
|
22
|
+
:type widget_id: str, optional
|
|
23
|
+
|
|
24
|
+
:Usage example:
|
|
25
|
+
.. code-block:: python
|
|
26
|
+
|
|
27
|
+
from supervisely.app.widgets import Modal, Text, Button, Input, Container
|
|
28
|
+
|
|
29
|
+
# Create widgets to show in modal
|
|
30
|
+
input_widget = Input("Enter value")
|
|
31
|
+
text_widget = Text("This is modal content")
|
|
32
|
+
|
|
33
|
+
# Create modal with multiple widgets
|
|
34
|
+
modal = Modal(
|
|
35
|
+
title="My Modal Window",
|
|
36
|
+
widgets=[text_widget, input_widget],
|
|
37
|
+
size="small"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Show modal programmatically
|
|
41
|
+
modal.show()
|
|
42
|
+
|
|
43
|
+
# Hide modal programmatically
|
|
44
|
+
modal.hide()
|
|
45
|
+
|
|
46
|
+
# Alternative methods
|
|
47
|
+
modal.show_modal()
|
|
48
|
+
modal.close_modal()
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
class Routes:
|
|
52
|
+
ON_CLOSE = "close_cb"
|
|
53
|
+
VALUE_CHANGED = "value_changed"
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
title: Optional[str] = "",
|
|
58
|
+
widgets: Optional[List[Widget]] = None,
|
|
59
|
+
size: Optional[Literal["tiny", "small", "large", "full"]] = "small",
|
|
60
|
+
widget_id: Optional[str] = None,
|
|
61
|
+
):
|
|
62
|
+
self._title = title
|
|
63
|
+
self._widgets = widgets if widgets is not None else []
|
|
64
|
+
self._size = size
|
|
65
|
+
self._value_changed_handled = False
|
|
66
|
+
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
67
|
+
|
|
68
|
+
server = self._sly_app.get_server()
|
|
69
|
+
route = self.get_route_path(Modal.Routes.ON_CLOSE)
|
|
70
|
+
|
|
71
|
+
@server.post(route)
|
|
72
|
+
def _on_close():
|
|
73
|
+
# Change visibility state to False when modal is closed on client side
|
|
74
|
+
visible = StateJson()[self.widget_id]["visible"]
|
|
75
|
+
if visible is True:
|
|
76
|
+
StateJson()[self.widget_id]["visible"] = False
|
|
77
|
+
# no need to call send_changes(), as it is already changed on client side
|
|
78
|
+
|
|
79
|
+
def get_json_data(self) -> Dict[str, str]:
|
|
80
|
+
"""Returns dictionary with widget data, which defines the appearance and behavior of the widget.
|
|
81
|
+
|
|
82
|
+
Dictionary contains the following fields:
|
|
83
|
+
- title: Modal title
|
|
84
|
+
- size: Modal size, one of: tiny, small, large, full
|
|
85
|
+
|
|
86
|
+
:return: Dictionary with widget data
|
|
87
|
+
:rtype: Dict[str, str]
|
|
88
|
+
"""
|
|
89
|
+
return {
|
|
90
|
+
"title": self._title,
|
|
91
|
+
"size": self._size,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
def get_json_state(self) -> Dict[str, bool]:
|
|
95
|
+
"""Returns dictionary with widget state.
|
|
96
|
+
|
|
97
|
+
Dictionary contains the following fields:
|
|
98
|
+
- visible: Modal visibility
|
|
99
|
+
|
|
100
|
+
:return: Dictionary with widget state
|
|
101
|
+
:rtype: Dict[str, bool]
|
|
102
|
+
"""
|
|
103
|
+
return {
|
|
104
|
+
"visible": False,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
def show(self) -> None:
|
|
108
|
+
"""Shows the modal window."""
|
|
109
|
+
StateJson()[self.widget_id]["visible"] = True
|
|
110
|
+
StateJson().send_changes()
|
|
111
|
+
|
|
112
|
+
def hide(self) -> None:
|
|
113
|
+
"""Hides the modal window."""
|
|
114
|
+
StateJson()[self.widget_id]["visible"] = False
|
|
115
|
+
StateJson().send_changes()
|
|
116
|
+
|
|
117
|
+
def show_modal(self) -> None:
|
|
118
|
+
"""Shows the modal window. Alias for show() method."""
|
|
119
|
+
self.show()
|
|
120
|
+
|
|
121
|
+
def close_modal(self) -> None:
|
|
122
|
+
"""Closes the modal window. Alias for hide() method."""
|
|
123
|
+
self.hide()
|
|
124
|
+
|
|
125
|
+
def is_opened(self) -> bool:
|
|
126
|
+
"""Returns whether the modal is currently open.
|
|
127
|
+
|
|
128
|
+
:return: True if modal is visible, False otherwise
|
|
129
|
+
:rtype: bool
|
|
130
|
+
"""
|
|
131
|
+
return StateJson()[self.widget_id]["visible"]
|
|
132
|
+
|
|
133
|
+
def value_changed(self, func):
|
|
134
|
+
"""Decorator to handle modal visibility changes.
|
|
135
|
+
The callback function will receive a boolean parameter: True when opened, False when closed.
|
|
136
|
+
|
|
137
|
+
:param func: Callback function that takes a boolean parameter
|
|
138
|
+
:type func: Callable[[bool], None]
|
|
139
|
+
|
|
140
|
+
:Usage example:
|
|
141
|
+
.. code-block:: python
|
|
142
|
+
|
|
143
|
+
@modal.value_changed
|
|
144
|
+
def on_modal_state_changed(is_opened):
|
|
145
|
+
if is_opened:
|
|
146
|
+
print("Modal opened")
|
|
147
|
+
else:
|
|
148
|
+
print("Modal closed")
|
|
149
|
+
"""
|
|
150
|
+
route_path = self.get_route_path(Modal.Routes.VALUE_CHANGED)
|
|
151
|
+
server = self._sly_app.get_server()
|
|
152
|
+
self._value_changed_handled = True
|
|
153
|
+
|
|
154
|
+
@server.post(route_path)
|
|
155
|
+
def _value_changed():
|
|
156
|
+
is_opened = StateJson()[self.widget_id]["visible"]
|
|
157
|
+
func(is_opened)
|
|
158
|
+
|
|
159
|
+
return _value_changed
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def title(self) -> str:
|
|
163
|
+
"""Returns modal title.
|
|
164
|
+
|
|
165
|
+
:return: Modal title
|
|
166
|
+
:rtype: str
|
|
167
|
+
"""
|
|
168
|
+
return self._title
|
|
169
|
+
|
|
170
|
+
@title.setter
|
|
171
|
+
def title(self, title: str) -> None:
|
|
172
|
+
"""Sets modal title.
|
|
173
|
+
|
|
174
|
+
:param title: Modal title
|
|
175
|
+
:type title: str
|
|
176
|
+
"""
|
|
177
|
+
self._title = title
|
|
178
|
+
DataJson()[self.widget_id]["title"] = title
|
|
179
|
+
DataJson().send_changes()
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def widgets(self) -> List[Widget]:
|
|
183
|
+
"""Returns list of widgets inside the modal.
|
|
184
|
+
|
|
185
|
+
:return: List of widgets
|
|
186
|
+
:rtype: List[Widget]
|
|
187
|
+
"""
|
|
188
|
+
return self._widgets
|
|
189
|
+
|
|
190
|
+
@widgets.setter
|
|
191
|
+
def widgets(self, widgets: List[Widget]) -> None:
|
|
192
|
+
"""Sets the list of widgets to display in the modal.
|
|
193
|
+
Note: Changing widgets dynamically may require re-rendering.
|
|
194
|
+
|
|
195
|
+
:param widgets: List of widgets
|
|
196
|
+
:type widgets: List[Widget]
|
|
197
|
+
"""
|
|
198
|
+
self._widgets = widgets
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<el-dialog :title="data.{{{widget.widget_id}}}.title" :size="data.{{{widget.widget_id}}}.size"
|
|
2
|
+
:visible.sync="state.{{{widget.widget_id}}}.visible" @close="post('/{{{widget.widget_id}}}/close_cb');" {% if
|
|
3
|
+
widget._value_changed_handled==true %} @open="post('/{{{widget.widget_id}}}/value_changed');"
|
|
4
|
+
@close="post('/{{{widget.widget_id}}}/value_changed');" {% endif %}>
|
|
5
|
+
<div>
|
|
6
|
+
{% for item in widget._widgets %}
|
|
7
|
+
{{{item}}}
|
|
8
|
+
{% endfor %}
|
|
9
|
+
</div>
|
|
10
|
+
</el-dialog>
|
|
@@ -17,6 +17,7 @@ from supervisely.geometry.pointcloud import Pointcloud
|
|
|
17
17
|
from supervisely.geometry.point_3d import Point3d
|
|
18
18
|
from supervisely.geometry.multichannel_bitmap import MultichannelBitmap
|
|
19
19
|
from supervisely.geometry.closed_surface_mesh import ClosedSurfaceMesh
|
|
20
|
+
from supervisely.geometry.oriented_bbox import OrientedBBox
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
type_to_zmdi_icon = {
|
|
@@ -32,6 +33,7 @@ type_to_zmdi_icon = {
|
|
|
32
33
|
Pointcloud: "zmdi zmdi-cloud-outline", # # "zmdi zmdi-border-clear"
|
|
33
34
|
MultichannelBitmap: "zmdi zmdi-layers", # "zmdi zmdi-collection-item"
|
|
34
35
|
Point3d: "zmdi zmdi-filter-center-focus", # "zmdi zmdi-select-all"
|
|
36
|
+
OrientedBBox: "zmdi zmdi-rotate-cw",
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
type_to_icons8_icon = {
|
|
@@ -61,6 +63,7 @@ type_to_shape_text = {
|
|
|
61
63
|
Point3d: "point 3d", # "zmdi zmdi-select-all"
|
|
62
64
|
GraphNodes: "keypoints",
|
|
63
65
|
ClosedSurfaceMesh: "volume (3d mask)",
|
|
66
|
+
OrientedBBox: "oriented bbox",
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import traceback
|
|
2
|
+
from typing import Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
from supervisely._utils import logger
|
|
2
5
|
from supervisely.app import StateJson
|
|
6
|
+
from supervisely.app.content import DataJson
|
|
3
7
|
from supervisely.app.widgets import Widget
|
|
4
8
|
|
|
5
9
|
|
|
@@ -65,7 +69,7 @@ class RadioTabs(Widget):
|
|
|
65
69
|
return _value_changed
|
|
66
70
|
|
|
67
71
|
def get_json_data(self) -> Dict:
|
|
68
|
-
return {}
|
|
72
|
+
return {"tabsOptions": {item.name: {"disabled": False} for item in self._items}}
|
|
69
73
|
|
|
70
74
|
def get_json_state(self) -> Dict:
|
|
71
75
|
return {"value": self._value}
|
|
@@ -77,3 +81,15 @@ class RadioTabs(Widget):
|
|
|
77
81
|
|
|
78
82
|
def get_active_tab(self) -> str:
|
|
79
83
|
return StateJson()[self.widget_id]["value"]
|
|
84
|
+
|
|
85
|
+
def disable_tab(self, tab_name: str):
|
|
86
|
+
if tab_name not in [item.name for item in self._items]:
|
|
87
|
+
raise ValueError(f"Tab with name '{tab_name}' does not exist.")
|
|
88
|
+
DataJson()[self.widget_id]["tabsOptions"][tab_name]["disabled"] = True
|
|
89
|
+
DataJson().send_changes()
|
|
90
|
+
|
|
91
|
+
def enable_tab(self, tab_name: str):
|
|
92
|
+
if tab_name not in [item.name for item in self._items]:
|
|
93
|
+
raise ValueError(f"Tab with name '{tab_name}' does not exist.")
|
|
94
|
+
DataJson()[self.widget_id]["tabsOptions"][tab_name]["disabled"] = False
|
|
95
|
+
DataJson().send_changes()
|
|
@@ -156,9 +156,12 @@ class Select(ConditionalWidget):
|
|
|
156
156
|
|
|
157
157
|
def get_json_state(self) -> Dict:
|
|
158
158
|
first_item = self._get_first_value()
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
if self._multiple:
|
|
160
|
+
value = []
|
|
161
|
+
if first_item is not None:
|
|
162
|
+
value = [first_item.value]
|
|
163
|
+
else:
|
|
164
|
+
value = first_item.value if first_item is not None else None
|
|
162
165
|
return {"value": value, "links": self._links}
|
|
163
166
|
|
|
164
167
|
def get_value(self):
|
|
File without changes
|