supervisely 6.73.438__py3-none-any.whl → 6.73.513__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. supervisely/__init__.py +137 -1
  2. supervisely/_utils.py +81 -0
  3. supervisely/annotation/annotation.py +8 -2
  4. supervisely/annotation/json_geometries_map.py +14 -11
  5. supervisely/annotation/label.py +80 -3
  6. supervisely/api/annotation_api.py +14 -11
  7. supervisely/api/api.py +59 -38
  8. supervisely/api/app_api.py +11 -2
  9. supervisely/api/dataset_api.py +74 -12
  10. supervisely/api/entities_collection_api.py +10 -0
  11. supervisely/api/entity_annotation/figure_api.py +52 -4
  12. supervisely/api/entity_annotation/object_api.py +3 -3
  13. supervisely/api/entity_annotation/tag_api.py +63 -12
  14. supervisely/api/guides_api.py +210 -0
  15. supervisely/api/image_api.py +72 -1
  16. supervisely/api/labeling_job_api.py +83 -1
  17. supervisely/api/labeling_queue_api.py +33 -7
  18. supervisely/api/module_api.py +9 -0
  19. supervisely/api/project_api.py +71 -26
  20. supervisely/api/storage_api.py +3 -1
  21. supervisely/api/task_api.py +13 -2
  22. supervisely/api/team_api.py +4 -3
  23. supervisely/api/video/video_annotation_api.py +119 -3
  24. supervisely/api/video/video_api.py +65 -14
  25. supervisely/api/video/video_figure_api.py +24 -11
  26. supervisely/app/__init__.py +1 -1
  27. supervisely/app/content.py +23 -7
  28. supervisely/app/development/development.py +18 -2
  29. supervisely/app/fastapi/__init__.py +1 -0
  30. supervisely/app/fastapi/custom_static_files.py +1 -1
  31. supervisely/app/fastapi/multi_user.py +105 -0
  32. supervisely/app/fastapi/subapp.py +88 -42
  33. supervisely/app/fastapi/websocket.py +77 -9
  34. supervisely/app/singleton.py +21 -0
  35. supervisely/app/v1/app_service.py +18 -2
  36. supervisely/app/v1/constants.py +7 -1
  37. supervisely/app/widgets/__init__.py +6 -0
  38. supervisely/app/widgets/activity_feed/__init__.py +0 -0
  39. supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
  40. supervisely/app/widgets/activity_feed/style.css +78 -0
  41. supervisely/app/widgets/activity_feed/template.html +22 -0
  42. supervisely/app/widgets/card/card.py +20 -0
  43. supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
  44. supervisely/app/widgets/classes_list_selector/template.html +60 -93
  45. supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
  46. supervisely/app/widgets/classes_table/classes_table.py +1 -0
  47. supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
  48. supervisely/app/widgets/dialog/dialog.py +12 -0
  49. supervisely/app/widgets/dialog/template.html +2 -1
  50. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
  51. supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
  52. supervisely/app/widgets/fast_table/fast_table.py +184 -60
  53. supervisely/app/widgets/fast_table/template.html +1 -1
  54. supervisely/app/widgets/heatmap/__init__.py +0 -0
  55. supervisely/app/widgets/heatmap/heatmap.py +564 -0
  56. supervisely/app/widgets/heatmap/script.js +533 -0
  57. supervisely/app/widgets/heatmap/style.css +233 -0
  58. supervisely/app/widgets/heatmap/template.html +21 -0
  59. supervisely/app/widgets/modal/__init__.py +0 -0
  60. supervisely/app/widgets/modal/modal.py +198 -0
  61. supervisely/app/widgets/modal/template.html +10 -0
  62. supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
  63. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  64. supervisely/app/widgets/radio_tabs/template.html +1 -0
  65. supervisely/app/widgets/select/select.py +6 -3
  66. supervisely/app/widgets/select_class/__init__.py +0 -0
  67. supervisely/app/widgets/select_class/select_class.py +363 -0
  68. supervisely/app/widgets/select_class/template.html +50 -0
  69. supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
  70. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
  71. supervisely/app/widgets/select_tag/__init__.py +0 -0
  72. supervisely/app/widgets/select_tag/select_tag.py +352 -0
  73. supervisely/app/widgets/select_tag/template.html +64 -0
  74. supervisely/app/widgets/select_team/select_team.py +37 -4
  75. supervisely/app/widgets/select_team/template.html +4 -5
  76. supervisely/app/widgets/select_user/__init__.py +0 -0
  77. supervisely/app/widgets/select_user/select_user.py +270 -0
  78. supervisely/app/widgets/select_user/template.html +13 -0
  79. supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
  80. supervisely/app/widgets/select_workspace/template.html +9 -12
  81. supervisely/app/widgets/table/table.py +68 -13
  82. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  83. supervisely/aug/aug.py +6 -2
  84. supervisely/convert/base_converter.py +1 -0
  85. supervisely/convert/converter.py +2 -2
  86. supervisely/convert/image/csv/csv_converter.py +24 -15
  87. supervisely/convert/image/image_converter.py +3 -1
  88. supervisely/convert/image/image_helper.py +48 -4
  89. supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
  90. supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
  91. supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
  92. supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
  93. supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
  94. supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
  95. supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
  96. supervisely/convert/pointcloud/las/las_converter.py +13 -1
  97. supervisely/convert/pointcloud/las/las_helper.py +110 -11
  98. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
  99. supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
  100. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
  101. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
  102. supervisely/convert/video/__init__.py +1 -0
  103. supervisely/convert/video/multi_view/__init__.py +0 -0
  104. supervisely/convert/video/multi_view/multi_view.py +543 -0
  105. supervisely/convert/video/sly/sly_video_converter.py +359 -3
  106. supervisely/convert/video/video_converter.py +24 -4
  107. supervisely/convert/volume/dicom/dicom_converter.py +13 -5
  108. supervisely/convert/volume/dicom/dicom_helper.py +30 -18
  109. supervisely/geometry/constants.py +1 -0
  110. supervisely/geometry/geometry.py +4 -0
  111. supervisely/geometry/helpers.py +5 -1
  112. supervisely/geometry/oriented_bbox.py +676 -0
  113. supervisely/geometry/polyline_3d.py +110 -0
  114. supervisely/geometry/rectangle.py +2 -1
  115. supervisely/io/env.py +76 -1
  116. supervisely/io/fs.py +21 -0
  117. supervisely/nn/benchmark/base_evaluator.py +104 -11
  118. supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
  119. supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
  120. supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
  121. supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
  122. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
  123. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
  124. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
  125. supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
  126. supervisely/nn/inference/cache.py +43 -18
  127. supervisely/nn/inference/gui/serving_gui_template.py +5 -2
  128. supervisely/nn/inference/inference.py +916 -222
  129. supervisely/nn/inference/inference_request.py +55 -10
  130. supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
  131. supervisely/nn/inference/predict_app/gui/gui.py +676 -488
  132. supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
  133. supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
  134. supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
  135. supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
  136. supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
  137. supervisely/nn/inference/predict_app/gui/utils.py +236 -119
  138. supervisely/nn/inference/predict_app/predict_app.py +2 -2
  139. supervisely/nn/inference/session.py +43 -35
  140. supervisely/nn/inference/tracking/bbox_tracking.py +118 -35
  141. supervisely/nn/inference/tracking/point_tracking.py +5 -1
  142. supervisely/nn/inference/tracking/tracker_interface.py +10 -1
  143. supervisely/nn/inference/uploader.py +139 -12
  144. supervisely/nn/live_training/__init__.py +7 -0
  145. supervisely/nn/live_training/api_server.py +111 -0
  146. supervisely/nn/live_training/artifacts_utils.py +243 -0
  147. supervisely/nn/live_training/checkpoint_utils.py +229 -0
  148. supervisely/nn/live_training/dynamic_sampler.py +44 -0
  149. supervisely/nn/live_training/helpers.py +14 -0
  150. supervisely/nn/live_training/incremental_dataset.py +146 -0
  151. supervisely/nn/live_training/live_training.py +497 -0
  152. supervisely/nn/live_training/loss_plateau_detector.py +111 -0
  153. supervisely/nn/live_training/request_queue.py +52 -0
  154. supervisely/nn/model/model_api.py +9 -0
  155. supervisely/nn/model/prediction.py +2 -1
  156. supervisely/nn/model/prediction_session.py +26 -14
  157. supervisely/nn/prediction_dto.py +19 -1
  158. supervisely/nn/tracker/base_tracker.py +11 -1
  159. supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
  160. supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
  161. supervisely/nn/tracker/botsort_tracker.py +94 -65
  162. supervisely/nn/tracker/utils.py +4 -5
  163. supervisely/nn/tracker/visualize.py +93 -93
  164. supervisely/nn/training/gui/classes_selector.py +16 -1
  165. supervisely/nn/training/gui/train_val_splits_selector.py +52 -31
  166. supervisely/nn/training/train_app.py +46 -31
  167. supervisely/project/data_version.py +115 -51
  168. supervisely/project/download.py +1 -1
  169. supervisely/project/pointcloud_episode_project.py +37 -8
  170. supervisely/project/pointcloud_project.py +30 -2
  171. supervisely/project/project.py +14 -2
  172. supervisely/project/project_meta.py +27 -1
  173. supervisely/project/project_settings.py +32 -18
  174. supervisely/project/versioning/__init__.py +1 -0
  175. supervisely/project/versioning/common.py +20 -0
  176. supervisely/project/versioning/schema_fields.py +35 -0
  177. supervisely/project/versioning/video_schema.py +221 -0
  178. supervisely/project/versioning/volume_schema.py +87 -0
  179. supervisely/project/video_project.py +717 -15
  180. supervisely/project/volume_project.py +623 -5
  181. supervisely/template/experiment/experiment.html.jinja +4 -4
  182. supervisely/template/experiment/experiment_generator.py +14 -21
  183. supervisely/template/live_training/__init__.py +0 -0
  184. supervisely/template/live_training/header.html.jinja +96 -0
  185. supervisely/template/live_training/live_training.html.jinja +51 -0
  186. supervisely/template/live_training/live_training_generator.py +464 -0
  187. supervisely/template/live_training/sly-style.css +402 -0
  188. supervisely/template/live_training/template.html.jinja +18 -0
  189. supervisely/versions.json +28 -26
  190. supervisely/video/sampling.py +39 -20
  191. supervisely/video/video.py +41 -12
  192. supervisely/video_annotation/video_figure.py +38 -4
  193. supervisely/video_annotation/video_object.py +29 -4
  194. supervisely/volume/stl_converter.py +2 -0
  195. supervisely/worker_api/agent_rpc.py +24 -1
  196. supervisely/worker_api/rpc_servicer.py +31 -7
  197. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/METADATA +58 -40
  198. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/RECORD +203 -155
  199. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
  200. supervisely_lib/__init__.py +6 -1
  201. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
  202. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
  203. {supervisely-6.73.438.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
- from typing import List, Optional, Dict
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()
@@ -20,6 +20,7 @@
20
20
  {% for tab_pane in widget._items %}
21
21
  <el-tab-pane
22
22
  name="{{{tab_pane.name}}}"
23
+ :disabled="data.{{{widget.widget_id}}}.tabsOptions['{{{tab_pane.name}}}'].disabled"
23
24
  >
24
25
  <el-radio
25
26
  slot="label"
@@ -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
- value = None
160
- if first_item is not None:
161
- value = first_item.value
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