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.

Files changed (190) hide show
  1. supervisely/__init__.py +136 -1
  2. supervisely/_utils.py +81 -0
  3. supervisely/annotation/json_geometries_map.py +2 -0
  4. supervisely/annotation/label.py +80 -3
  5. supervisely/api/annotation_api.py +9 -9
  6. supervisely/api/api.py +67 -43
  7. supervisely/api/app_api.py +72 -5
  8. supervisely/api/dataset_api.py +108 -33
  9. supervisely/api/entity_annotation/figure_api.py +113 -49
  10. supervisely/api/image_api.py +82 -0
  11. supervisely/api/module_api.py +10 -0
  12. supervisely/api/nn/deploy_api.py +15 -9
  13. supervisely/api/nn/ecosystem_models_api.py +201 -0
  14. supervisely/api/nn/neural_network_api.py +12 -3
  15. supervisely/api/pointcloud/pointcloud_api.py +38 -0
  16. supervisely/api/pointcloud/pointcloud_episode_annotation_api.py +3 -0
  17. supervisely/api/project_api.py +213 -6
  18. supervisely/api/task_api.py +11 -1
  19. supervisely/api/video/video_annotation_api.py +4 -2
  20. supervisely/api/video/video_api.py +79 -1
  21. supervisely/api/video/video_figure_api.py +24 -11
  22. supervisely/api/volume/volume_api.py +38 -0
  23. supervisely/app/__init__.py +1 -1
  24. supervisely/app/content.py +14 -6
  25. supervisely/app/fastapi/__init__.py +1 -0
  26. supervisely/app/fastapi/custom_static_files.py +1 -1
  27. supervisely/app/fastapi/multi_user.py +88 -0
  28. supervisely/app/fastapi/subapp.py +175 -42
  29. supervisely/app/fastapi/templating.py +1 -1
  30. supervisely/app/fastapi/websocket.py +77 -9
  31. supervisely/app/singleton.py +21 -0
  32. supervisely/app/v1/app_service.py +18 -2
  33. supervisely/app/v1/constants.py +7 -1
  34. supervisely/app/widgets/__init__.py +11 -1
  35. supervisely/app/widgets/agent_selector/template.html +1 -0
  36. supervisely/app/widgets/card/card.py +20 -0
  37. supervisely/app/widgets/dataset_thumbnail/dataset_thumbnail.py +11 -2
  38. supervisely/app/widgets/dataset_thumbnail/template.html +3 -1
  39. supervisely/app/widgets/deploy_model/deploy_model.py +750 -0
  40. supervisely/app/widgets/dialog/dialog.py +12 -0
  41. supervisely/app/widgets/dialog/template.html +2 -1
  42. supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
  43. supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
  44. supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
  45. supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
  46. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +195 -0
  47. supervisely/app/widgets/experiment_selector/experiment_selector.py +454 -263
  48. supervisely/app/widgets/fast_table/fast_table.py +713 -126
  49. supervisely/app/widgets/fast_table/script.js +492 -95
  50. supervisely/app/widgets/fast_table/style.css +54 -0
  51. supervisely/app/widgets/fast_table/template.html +45 -5
  52. supervisely/app/widgets/heatmap/__init__.py +0 -0
  53. supervisely/app/widgets/heatmap/heatmap.py +523 -0
  54. supervisely/app/widgets/heatmap/script.js +378 -0
  55. supervisely/app/widgets/heatmap/style.css +227 -0
  56. supervisely/app/widgets/heatmap/template.html +21 -0
  57. supervisely/app/widgets/input_tag/input_tag.py +102 -15
  58. supervisely/app/widgets/input_tag_list/__init__.py +0 -0
  59. supervisely/app/widgets/input_tag_list/input_tag_list.py +274 -0
  60. supervisely/app/widgets/input_tag_list/template.html +70 -0
  61. supervisely/app/widgets/radio_table/radio_table.py +10 -2
  62. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  63. supervisely/app/widgets/radio_tabs/template.html +1 -0
  64. supervisely/app/widgets/select/select.py +6 -4
  65. supervisely/app/widgets/select_dataset/select_dataset.py +6 -0
  66. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +83 -7
  67. supervisely/app/widgets/table/table.py +68 -13
  68. supervisely/app/widgets/tabs/tabs.py +22 -6
  69. supervisely/app/widgets/tabs/template.html +5 -1
  70. supervisely/app/widgets/transfer/style.css +3 -0
  71. supervisely/app/widgets/transfer/template.html +3 -1
  72. supervisely/app/widgets/transfer/transfer.py +48 -45
  73. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  74. supervisely/convert/image/csv/csv_converter.py +24 -15
  75. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +43 -41
  76. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +75 -51
  77. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +137 -124
  78. supervisely/convert/video/video_converter.py +2 -2
  79. supervisely/geometry/polyline_3d.py +110 -0
  80. supervisely/io/env.py +161 -1
  81. supervisely/nn/artifacts/__init__.py +1 -1
  82. supervisely/nn/artifacts/artifacts.py +10 -2
  83. supervisely/nn/artifacts/detectron2.py +1 -0
  84. supervisely/nn/artifacts/hrda.py +1 -0
  85. supervisely/nn/artifacts/mmclassification.py +20 -0
  86. supervisely/nn/artifacts/mmdetection.py +5 -3
  87. supervisely/nn/artifacts/mmsegmentation.py +1 -0
  88. supervisely/nn/artifacts/ritm.py +1 -0
  89. supervisely/nn/artifacts/rtdetr.py +1 -0
  90. supervisely/nn/artifacts/unet.py +1 -0
  91. supervisely/nn/artifacts/utils.py +3 -0
  92. supervisely/nn/artifacts/yolov5.py +2 -0
  93. supervisely/nn/artifacts/yolov8.py +1 -0
  94. supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
  95. supervisely/nn/experiments.py +9 -0
  96. supervisely/nn/inference/cache.py +37 -17
  97. supervisely/nn/inference/gui/serving_gui_template.py +39 -13
  98. supervisely/nn/inference/inference.py +953 -211
  99. supervisely/nn/inference/inference_request.py +15 -8
  100. supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
  101. supervisely/nn/inference/object_detection/object_detection.py +1 -0
  102. supervisely/nn/inference/predict_app/__init__.py +0 -0
  103. supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
  104. supervisely/nn/inference/predict_app/gui/classes_selector.py +160 -0
  105. supervisely/nn/inference/predict_app/gui/gui.py +915 -0
  106. supervisely/nn/inference/predict_app/gui/input_selector.py +344 -0
  107. supervisely/nn/inference/predict_app/gui/model_selector.py +77 -0
  108. supervisely/nn/inference/predict_app/gui/output_selector.py +179 -0
  109. supervisely/nn/inference/predict_app/gui/preview.py +93 -0
  110. supervisely/nn/inference/predict_app/gui/settings_selector.py +881 -0
  111. supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
  112. supervisely/nn/inference/predict_app/gui/utils.py +399 -0
  113. supervisely/nn/inference/predict_app/predict_app.py +176 -0
  114. supervisely/nn/inference/session.py +47 -39
  115. supervisely/nn/inference/tracking/bbox_tracking.py +5 -1
  116. supervisely/nn/inference/tracking/point_tracking.py +5 -1
  117. supervisely/nn/inference/tracking/tracker_interface.py +4 -0
  118. supervisely/nn/inference/uploader.py +9 -5
  119. supervisely/nn/model/model_api.py +44 -22
  120. supervisely/nn/model/prediction.py +15 -1
  121. supervisely/nn/model/prediction_session.py +70 -14
  122. supervisely/nn/prediction_dto.py +7 -0
  123. supervisely/nn/tracker/__init__.py +6 -8
  124. supervisely/nn/tracker/base_tracker.py +54 -0
  125. supervisely/nn/tracker/botsort/__init__.py +1 -0
  126. supervisely/nn/tracker/botsort/botsort_config.yaml +30 -0
  127. supervisely/nn/tracker/botsort/osnet_reid/__init__.py +0 -0
  128. supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
  129. supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
  130. supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
  131. supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
  132. supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
  133. supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
  134. supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
  135. supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
  136. supervisely/nn/tracker/botsort_tracker.py +273 -0
  137. supervisely/nn/tracker/calculate_metrics.py +264 -0
  138. supervisely/nn/tracker/utils.py +273 -0
  139. supervisely/nn/tracker/visualize.py +520 -0
  140. supervisely/nn/training/gui/gui.py +152 -49
  141. supervisely/nn/training/gui/hyperparameters_selector.py +1 -1
  142. supervisely/nn/training/gui/model_selector.py +8 -6
  143. supervisely/nn/training/gui/train_val_splits_selector.py +144 -71
  144. supervisely/nn/training/gui/training_artifacts.py +3 -1
  145. supervisely/nn/training/train_app.py +225 -46
  146. supervisely/project/pointcloud_episode_project.py +12 -8
  147. supervisely/project/pointcloud_project.py +12 -8
  148. supervisely/project/project.py +221 -75
  149. supervisely/template/experiment/experiment.html.jinja +105 -55
  150. supervisely/template/experiment/experiment_generator.py +258 -112
  151. supervisely/template/experiment/header.html.jinja +31 -13
  152. supervisely/template/experiment/sly-style.css +7 -2
  153. supervisely/versions.json +3 -1
  154. supervisely/video/sampling.py +42 -20
  155. supervisely/video/video.py +41 -12
  156. supervisely/video_annotation/video_figure.py +38 -4
  157. supervisely/volume/stl_converter.py +2 -0
  158. supervisely/worker_api/agent_rpc.py +24 -1
  159. supervisely/worker_api/rpc_servicer.py +31 -7
  160. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/METADATA +22 -14
  161. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/RECORD +167 -148
  162. supervisely_lib/__init__.py +6 -1
  163. supervisely/app/widgets/experiment_selector/style.css +0 -27
  164. supervisely/app/widgets/experiment_selector/template.html +0 -61
  165. supervisely/nn/tracker/bot_sort/__init__.py +0 -21
  166. supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
  167. supervisely/nn/tracker/bot_sort/matching.py +0 -127
  168. supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
  169. supervisely/nn/tracker/deep_sort/__init__.py +0 -6
  170. supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
  171. supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
  172. supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
  173. supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
  174. supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
  175. supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
  176. supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
  177. supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
  178. supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
  179. supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
  180. supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
  181. supervisely/nn/tracker/tracker.py +0 -285
  182. supervisely/nn/tracker/utils/kalman_filter.py +0 -492
  183. supervisely/nn/tracking/__init__.py +0 -1
  184. supervisely/nn/tracking/boxmot.py +0 -114
  185. supervisely/nn/tracking/tracking.py +0 -24
  186. /supervisely/{nn/tracker/utils → app/widgets/deploy_model}/__init__.py +0 -0
  187. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/LICENSE +0 -0
  188. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/WHEEL +0 -0
  189. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/entry_points.txt +0 -0
  190. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,110 @@
1
+ from typing import Any, Dict
2
+
3
+ from supervisely.app.widgets import Button, Card, Container, TagsTable, Text
4
+
5
+
6
+ class TagsSelector:
7
+ title = "Tags Selector"
8
+ description = "Select tags that will be used for inference"
9
+ lock_message = "Select previous step to unlock"
10
+
11
+ def __init__(self):
12
+ # Init Step
13
+ self.display_widgets = []
14
+ # -------------------------------- #
15
+
16
+ # Init Base Widgets
17
+ self.validator_text = None
18
+ self.button = None
19
+ self.container = None
20
+ self.card = None
21
+ # -------------------------------- #
22
+
23
+ # Init Step Widgets
24
+ self.tags_table = None
25
+ # -------------------------------- #
26
+
27
+ # Tags
28
+ self.tags_table = TagsTable()
29
+ self.tags_table.hide()
30
+ # Add widgets to display ------------ #
31
+ self.display_widgets.extend([self.tags_table])
32
+ # ----------------------------------- #
33
+
34
+ # Base Widgets
35
+ self.validator_text = Text("")
36
+ self.validator_text.hide()
37
+ self.button = Button("Select")
38
+ self.display_widgets.extend([self.validator_text, self.button])
39
+ # -------------------------------- #
40
+
41
+ # Card Layout
42
+ self.container = Container(self.display_widgets)
43
+ self.card = Card(
44
+ title=self.title,
45
+ description=self.description,
46
+ content=self.container,
47
+ lock_message=self.lock_message,
48
+ )
49
+ # -------------------------------- #
50
+
51
+ @property
52
+ def widgets_to_disable(self) -> list:
53
+ return [self.tags_table]
54
+
55
+ def load_from_json(self, data: Dict[str, Any]) -> None:
56
+ if "tags" in data:
57
+ self.set_tags(data["tags"])
58
+
59
+ def get_selected_tags(self) -> list:
60
+ return self.tags_table.get_selected_tags()
61
+
62
+ def set_tags(self, tags) -> None:
63
+ self.tags_table.select_tags(tags)
64
+
65
+ def select_all_tags(self) -> None:
66
+ self.tags_table.select_all()
67
+
68
+ def get_settings(self) -> Dict[str, Any]:
69
+ return {"tags": self.get_selected_tags()}
70
+
71
+ def validate_step(self) -> bool:
72
+ if self.tags_table.is_hidden():
73
+ return True
74
+
75
+ self.validator_text.hide()
76
+
77
+ project_tags = self.tags_table.project_meta.tag_metas
78
+ if len(project_tags) == 0:
79
+ self.validator_text.set(text="Project has no tags", status="error")
80
+ self.validator_text.show()
81
+ return False
82
+
83
+ selected_tags = self.tags_table.get_selected_tags()
84
+ table_data = self.tags_table._table_data
85
+ empty_tags = [
86
+ row[0]["data"]
87
+ for row in table_data
88
+ if row[0]["data"] in selected_tags and row[2]["data"] == 0 and row[3]["data"] == 0
89
+ ]
90
+
91
+ n_tags = len(selected_tags)
92
+ if n_tags == 0:
93
+ message = "Please select at least one tag"
94
+ status = "error"
95
+ else:
96
+ tag_text = "tag" if n_tags == 1 else "tags"
97
+ message = f"Selected {n_tags} {tag_text}"
98
+ status = "success"
99
+ if empty_tags:
100
+ intersections = set(selected_tags).intersection(empty_tags)
101
+ if intersections:
102
+ tag_text = "tag" if len(intersections) == 1 else "tags"
103
+ message += (
104
+ f". Selected {tag_text} have no annotations: {', '.join(intersections)}"
105
+ )
106
+ status = "warning"
107
+
108
+ self.validator_text.set(text=message, status=status)
109
+ self.validator_text.show()
110
+ return n_tags > 0
@@ -0,0 +1,399 @@
1
+ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
2
+
3
+ from supervisely import logger
4
+ from supervisely.api.api import Api
5
+ from supervisely.api.dataset_api import DatasetInfo
6
+ from supervisely.api.image_api import ImageInfo
7
+ from supervisely.api.project_api import ProjectInfo
8
+ from supervisely.app import DataJson
9
+ from supervisely.app.widgets import Button, Card, Progress, Stepper, Text, Widget
10
+ from supervisely.nn.model.prediction import Prediction
11
+ from supervisely.project.project import ProjectType
12
+ from supervisely.project.project_meta import ProjectMeta
13
+ from supervisely.project.video_project import VideoInfo
14
+ from supervisely.video_annotation.frame import Frame
15
+ from supervisely.video_annotation.frame_collection import FrameCollection
16
+ from supervisely.video_annotation.video_annotation import VideoAnnotation
17
+ from supervisely.video_annotation.video_figure import VideoFigure
18
+ from supervisely.video_annotation.video_object import VideoObject
19
+ from supervisely.video_annotation.video_object_collection import VideoObjectCollection
20
+
21
+ button_clicked = {}
22
+
23
+
24
+ def update_custom_params(
25
+ button: Button,
26
+ params_dct: Dict[str, Any],
27
+ ) -> None:
28
+ button_state = button.get_json_data()
29
+ for key in params_dct.keys():
30
+ if key not in button_state:
31
+ raise AttributeError(f"Parameter {key} doesn't exists.")
32
+ else:
33
+ DataJson()[button.widget_id][key] = params_dct[key]
34
+ DataJson().send_changes()
35
+
36
+
37
+ def update_custom_button_params(
38
+ button: Button,
39
+ params_dct: Dict[str, Any],
40
+ ) -> None:
41
+ params = params_dct.copy()
42
+ if "icon" in params and params["icon"] is not None:
43
+ new_icon = f'<i class="{params["icon"]}" style="margin-right: {button._icon_gap}px"></i>'
44
+ params["icon"] = new_icon
45
+ update_custom_params(button, params)
46
+
47
+
48
+ def disable_enable(widgets: List[Widget], disable: bool = True):
49
+ for w in widgets:
50
+ if disable:
51
+ w.disable()
52
+ else:
53
+ w.enable()
54
+
55
+
56
+ def unlock_lock(cards: List[Card], unlock: bool = True, message: str = None):
57
+ for w in cards:
58
+ if unlock:
59
+ w.unlock()
60
+ # w.uncollapse()
61
+ else:
62
+ w.lock(message)
63
+ # w.collapse()
64
+
65
+
66
+ def collapse_uncollapse(cards: List[Card], collapse: bool = True):
67
+ for w in cards:
68
+ if collapse:
69
+ w.collapse()
70
+ else:
71
+ w.uncollapse()
72
+
73
+
74
+ def wrap_button_click(
75
+ button: Button,
76
+ cards_to_unlock: List[Card],
77
+ widgets_to_disable: List[Widget],
78
+ callback: Optional[Callable] = None,
79
+ lock_msg: str = None,
80
+ upd_params: bool = True,
81
+ validation_text: Text = None,
82
+ validation_func: Optional[Callable] = None,
83
+ on_select_click: Optional[Callable] = None,
84
+ on_reselect_click: Optional[Callable] = None,
85
+ collapse_card: Tuple[Card, bool] = None,
86
+ ) -> Callable[[Optional[bool]], None]:
87
+ global button_clicked
88
+
89
+ select_params = {"icon": None, "plain": False, "text": "Select"}
90
+ reselect_params = {"icon": "zmdi zmdi-refresh", "plain": True, "text": "Reselect"}
91
+ bid = button.widget_id
92
+ button_clicked[bid] = False
93
+
94
+ def button_click(button_clicked_value: Optional[bool] = None, suppress_actions: bool = False):
95
+ if button_clicked_value is None or button_clicked_value is False:
96
+ if validation_func is not None:
97
+ success = validation_func()
98
+ if not success:
99
+ return
100
+
101
+ if button_clicked_value is not None:
102
+ button_clicked[bid] = button_clicked_value
103
+ else:
104
+ button_clicked[bid] = not button_clicked[bid]
105
+
106
+ if button_clicked[bid] and upd_params:
107
+ update_custom_button_params(button, reselect_params)
108
+ if not suppress_actions and on_select_click is not None:
109
+ for func in on_select_click:
110
+ func()
111
+ else:
112
+ update_custom_button_params(button, select_params)
113
+ if not suppress_actions and on_reselect_click is not None:
114
+ for func in on_reselect_click:
115
+ func()
116
+ validation_text.hide()
117
+
118
+ unlock_lock(
119
+ cards_to_unlock,
120
+ unlock=button_clicked[bid],
121
+ message=lock_msg,
122
+ )
123
+ disable_enable(
124
+ widgets_to_disable,
125
+ disable=button_clicked[bid],
126
+ )
127
+ if callback is not None and not button_clicked[bid]:
128
+ callback(False, True)
129
+
130
+ if collapse_card is not None:
131
+ card, collapse = collapse_card
132
+ if collapse:
133
+ collapse_uncollapse([card], collapse)
134
+
135
+ return button_click
136
+
137
+
138
+ def set_stepper_step(stepper: Stepper, button: Button, next_pos: int):
139
+ bid = button.widget_id
140
+ if button_clicked[bid] is True:
141
+ stepper.set_active_step(next_pos)
142
+ else:
143
+ stepper.set_active_step(next_pos - 1)
144
+
145
+
146
+ def find_parents_in_tree(
147
+ tree: Dict[DatasetInfo, Dict], dataset_id: int, with_self: bool = False
148
+ ) -> Optional[List[DatasetInfo]]:
149
+ """
150
+ Find all parent datasets in the tree for a given dataset ID.
151
+ """
152
+
153
+ def _dfs(subtree: Dict[DatasetInfo, Dict], parents: List[DatasetInfo]):
154
+ for dataset_info, children in subtree.items():
155
+ if dataset_info.id == dataset_id:
156
+ if with_self:
157
+ return parents + [dataset_info]
158
+ return parents
159
+ res = _dfs(children, parents + [dataset_info])
160
+ if res is not None:
161
+ return res
162
+ return None
163
+
164
+ return _dfs(tree, [])
165
+
166
+
167
+ def _copy_items_to_dataset(
168
+ api: Api,
169
+ src_dataset_id: int,
170
+ dst_dataset: DatasetInfo,
171
+ project_type: str,
172
+ with_annotations: bool = True,
173
+ progress_cb: Callable = None,
174
+ progress: Progress = None,
175
+ items_infos: List[Union[ImageInfo, VideoInfo]] = None,
176
+ ) -> Union[List[ImageInfo], List[VideoInfo]]:
177
+ if progress is None:
178
+ progress = Progress()
179
+
180
+ def combined_progress(n):
181
+ progress_cb(n)
182
+ pbar.update(n)
183
+
184
+ if project_type == ProjectType.IMAGES:
185
+ if items_infos is None:
186
+ items_infos = api.image.get_list(src_dataset_id)
187
+ with progress(
188
+ message=f"Copying items from dataset: {dst_dataset.name}", total=len(items_infos)
189
+ ) as pbar:
190
+
191
+ if progress_cb:
192
+ _progress_cb = combined_progress
193
+ else:
194
+ _progress_cb = pbar.update
195
+
196
+ progress.show()
197
+ copied = api.image.copy_batch_optimized(
198
+ src_dataset_id=src_dataset_id,
199
+ src_image_infos=items_infos,
200
+ dst_dataset_id=dst_dataset.id,
201
+ with_annotations=with_annotations,
202
+ progress_cb=_progress_cb,
203
+ )
204
+ progress.hide()
205
+ elif project_type == ProjectType.VIDEOS:
206
+ if items_infos is None:
207
+ items_infos = api.video.get_list(src_dataset_id)
208
+
209
+ with progress(
210
+ message=f"Copying items from dataset: {dst_dataset.name}", total=len(items_infos)
211
+ ) as pbar:
212
+ if progress_cb:
213
+ _progress_cb = combined_progress
214
+ else:
215
+ _progress_cb = pbar.update
216
+ progress.show()
217
+ copied = api.video.copy_batch(
218
+ dst_dataset_id=dst_dataset.id,
219
+ ids=[info.id for info in items_infos],
220
+ with_annotations=with_annotations,
221
+ progress_cb=_progress_cb,
222
+ )
223
+ progress.hide()
224
+ else:
225
+ raise NotImplementedError(f"Copy not implemented for project type {project_type}")
226
+ return copied
227
+
228
+
229
+ def get_items_infos(
230
+ api: Api, items_ids: List[int], project_type: str
231
+ ) -> List[Union[ImageInfo, VideoInfo]]:
232
+ if project_type == ProjectType.IMAGES:
233
+ items_infos: List[ImageInfo] = api.image.get_info_by_id_batch(items_ids)
234
+ elif project_type == ProjectType.VIDEOS:
235
+ items_infos: List[VideoInfo] = api.video.get_info_by_id_batch(items_ids)
236
+ else:
237
+ raise NotImplementedError(f"Items of type {project_type} are not supported")
238
+ return items_infos
239
+
240
+
241
+ def copy_items_to_project(
242
+ api: Api,
243
+ src_project_id: int,
244
+ items: Union[List[ImageInfo], List[VideoInfo]],
245
+ dst_project_id: int,
246
+ with_annotations: bool = True,
247
+ progress_cb: Progress = None,
248
+ ds_progress: Progress = None,
249
+ project_type: str = None,
250
+ src_datasets_tree: Dict[DatasetInfo, Dict] = None,
251
+ ) -> Union[List[ImageInfo], List[VideoInfo]]:
252
+ if project_type is None:
253
+ dst_project_info = api.project.get_info_by_id(src_project_id)
254
+ project_type = dst_project_info.type
255
+ if len(items) == 0:
256
+ return []
257
+ if len(set(info.project_id for info in items)) != 1:
258
+ raise ValueError("Items must belong to the same project")
259
+
260
+ items_by_dataset: Dict[int, List[Union[ImageInfo, VideoInfo]]] = {}
261
+ for item_info in items:
262
+ items_by_dataset.setdefault(item_info.dataset_id, []).append(item_info)
263
+
264
+ if src_datasets_tree is None:
265
+ src_datasets_tree = api.dataset.get_tree(src_project_id)
266
+
267
+ created_datasets: Dict[int, DatasetInfo] = {}
268
+ processed_copy: Set[int] = set()
269
+
270
+ copied_items = {}
271
+ for dataset_id, items_infos in items_by_dataset.items():
272
+ chain = find_parents_in_tree(src_datasets_tree, dataset_id, with_self=True)
273
+ if not chain:
274
+ logger.warning(f"Dataset id {dataset_id} not found in project. Skipping")
275
+ continue
276
+
277
+ parent_created_id = None
278
+ for ds_info in chain:
279
+ if ds_info.id in created_datasets:
280
+ parent_created_id = created_datasets[ds_info.id].id
281
+ continue
282
+
283
+ created_ds = api.dataset.create(
284
+ dst_project_id,
285
+ ds_info.name,
286
+ description=ds_info.description,
287
+ change_name_if_conflict=False,
288
+ parent_id=parent_created_id,
289
+ )
290
+ if ds_info.custom_data:
291
+ created_ds = api.dataset.update_custom_data(created_ds.id, ds_info.custom_data)
292
+ created_datasets[ds_info.id] = created_ds
293
+ parent_created_id = created_ds.id
294
+
295
+ if dataset_id not in processed_copy:
296
+ copied_ds_items = _copy_items_to_dataset(
297
+ api=api,
298
+ src_dataset_id=dataset_id,
299
+ dst_dataset=created_datasets[dataset_id],
300
+ project_type=project_type,
301
+ with_annotations=with_annotations,
302
+ progress_cb=progress_cb,
303
+ progress=ds_progress,
304
+ items_infos=items_infos,
305
+ )
306
+ for src_info, dst_info in zip(items_infos, copied_ds_items):
307
+ copied_items[src_info.id] = dst_info
308
+ processed_copy.add(dataset_id)
309
+ return [copied_items[item.id] for item in items]
310
+
311
+
312
+ def create_project(
313
+ api: Api,
314
+ project_id: int,
315
+ project_name: str,
316
+ workspace_id: int,
317
+ copy_meta: bool = False,
318
+ project_type: str = None,
319
+ ) -> ProjectInfo:
320
+ if project_type is None:
321
+ project_info = api.project.get_info_by_id(project_id)
322
+ project_type = project_info.type
323
+ created_project = api.project.create(
324
+ workspace_id,
325
+ project_name,
326
+ type=project_type,
327
+ change_name_if_conflict=True,
328
+ )
329
+ if copy_meta:
330
+ api.project.merge_metas(src_project_id=project_id, dst_project_id=created_project.id)
331
+ return created_project
332
+
333
+
334
+ def copy_project(
335
+ api: Api,
336
+ project_id: int,
337
+ workspace_id: int,
338
+ project_name: str,
339
+ items_ids: List[int] = None,
340
+ with_annotations: bool = True,
341
+ progress: Progress = None,
342
+ ) -> ProjectInfo:
343
+ dst_project = create_project(
344
+ api, project_id, project_name, workspace_id=workspace_id, copy_meta=True
345
+ )
346
+ items = []
347
+ if items_ids is None:
348
+ project_type = dst_project.type
349
+ datasets = api.dataset.get_list(project_id, recursive=True)
350
+ if project_type == ProjectType.IMAGES:
351
+ get_items_f = api.image.get_list
352
+ elif project_type == ProjectType.VIDEOS:
353
+ get_items_f = api.video.get_list
354
+ else:
355
+ raise NotImplementedError(f"Project type {project_type} is not supported")
356
+ for ds in datasets:
357
+ ds_items = get_items_f(dataset_id=ds.id)
358
+ if ds_items:
359
+ items.extend(ds_items)
360
+ else:
361
+ items = get_items_infos(api, items_ids, dst_project.type)
362
+ copy_items_to_project(
363
+ api=api,
364
+ src_project_id=project_id,
365
+ items=items,
366
+ dst_project_id=dst_project.id,
367
+ with_annotations=with_annotations,
368
+ ds_progress=progress,
369
+ project_type=dst_project.type,
370
+ )
371
+ return dst_project
372
+
373
+
374
+ def video_annotation_from_predictions(
375
+ predictions: List[Prediction], project_meta: ProjectMeta, frame_size: Tuple[int, int]
376
+ ) -> VideoAnnotation:
377
+ objects = {}
378
+ frames = []
379
+ for i, prediction in enumerate(predictions):
380
+ figures = []
381
+ for label in prediction.annotation.labels:
382
+ obj_name = label.obj_class.name
383
+ if not obj_name in objects:
384
+ obj_class = project_meta.get_obj_class(obj_name)
385
+ if obj_class is None:
386
+ continue
387
+ objects[obj_name] = VideoObject(obj_class)
388
+
389
+ vid_object = objects[obj_name]
390
+ if vid_object:
391
+ figures.append(VideoFigure(vid_object, label.geometry, frame_index=i))
392
+ frame = Frame(i, figures=figures)
393
+ frames.append(frame)
394
+ return VideoAnnotation(
395
+ img_size=frame_size,
396
+ frames_count=len(frames),
397
+ objects=VideoObjectCollection(list(objects.values())),
398
+ frames=FrameCollection(frames),
399
+ )
@@ -0,0 +1,176 @@
1
+ from typing import Dict, List, Optional
2
+
3
+ from fastapi import BackgroundTasks, Request
4
+
5
+ import supervisely.io.fs as sly_fs
6
+ from supervisely._utils import logger
7
+ from supervisely.api.api import Api
8
+ from supervisely.app.fastapi.subapp import Application
9
+ from supervisely.nn.inference.predict_app.gui.gui import PredictAppGui
10
+ from supervisely.nn.inference.predict_app.gui.utils import disable_enable
11
+ from supervisely.nn.model.prediction import Prediction
12
+
13
+
14
+ class PredictApp:
15
+ def __init__(self, api: Api):
16
+ _static_dir = "static"
17
+ sly_fs.mkdir(_static_dir, True)
18
+ self.api = api
19
+ self.gui = PredictAppGui(api, static_dir=_static_dir)
20
+ self.app = Application(self.gui.layout, static_dir=_static_dir)
21
+ self._add_endpoints()
22
+
23
+ @self.gui.output_selector.start_button.click
24
+ def start_prediction():
25
+ if self.gui.output_selector.validate_step():
26
+ widgets_to_disable = self.gui.output_selector.widgets_to_disable + [self.gui.settings_selector.preview.run_button]
27
+ disable_enable(widgets_to_disable, True)
28
+ self.gui.run()
29
+ self.shutdown_serving_app()
30
+ self.shutdown_predict_app()
31
+
32
+ def shutdown_serving_app(self):
33
+ if self.gui.output_selector.should_stop_serving_on_finish():
34
+ logger.info("Stopping serving app...")
35
+ self.gui.model_selector.model.stop()
36
+
37
+ def shutdown_predict_app(self):
38
+ if self.gui.output_selector.should_stop_self_on_finish():
39
+ self.gui.output_selector.start_button.disable()
40
+ logger.info("Stopping Predict App...")
41
+ self.app.stop()
42
+ else:
43
+ disable_enable(self.gui.output_selector.widgets_to_disable, False)
44
+ self.gui.output_selector.start_button.enable()
45
+
46
+ def run(self, run_parameters: Optional[Dict] = None) -> List[Prediction]:
47
+ return self.gui.run(run_parameters)
48
+
49
+ def stop(self):
50
+ self.gui.stop()
51
+
52
+ def shutdown_model(self):
53
+ self.gui.shutdown_model()
54
+
55
+ def load_from_json(self, data):
56
+ self.gui.load_from_json(data)
57
+ if data.get("run", False):
58
+ try:
59
+ self.run()
60
+ except Exception as e:
61
+ raise
62
+ finally:
63
+ if data.get("stop_after_run", False):
64
+ self.shutdown_model()
65
+ self.app.stop()
66
+
67
+ def get_inference_settings(self):
68
+ return self.gui.settings_selector.get_inference_settings()
69
+
70
+ def get_run_parameters(self):
71
+ return self.gui.get_run_parameters()
72
+
73
+ def _add_endpoints(self):
74
+ server = self.app.get_server()
75
+
76
+ @server.post("/load")
77
+ def load(request: Request, background_tasks: BackgroundTasks):
78
+ """
79
+ Load the model state from a JSON object.
80
+ This endpoint initializes the model with the provided state.
81
+ All the fields are optional
82
+
83
+ Example state:
84
+ state = {
85
+ "model": {
86
+ "mode": "connect",
87
+ "session_id": "12345"
88
+ # "mode": "pretrained",
89
+ # "framework: "YOLO",
90
+ # "model_name": "YOLO11m-seg",
91
+ # "mode": "custom",
92
+ # "train_task_id": 123
93
+ },
94
+ "input": {
95
+ "project_id": 123,
96
+ # "dataset_ids": [...],
97
+ # "video_id": 123
98
+ },
99
+ "settings": {
100
+ "inference_settings": {
101
+ "confidence_threshold": 0.5
102
+ },
103
+ }
104
+ "output": {
105
+ "mode": "create",
106
+ "project_name": "Predictions",
107
+ # "mode": "append",
108
+ # "mode": "replace",
109
+ # "mode": "iou_merge",
110
+ # "iou_merge_threshold": 0.5
111
+ }
112
+ }
113
+ """
114
+ state = request.state.state
115
+ stop_after_run = state.get("stop_after_run", False)
116
+ if stop_after_run:
117
+ state["stop_after_run"] = False
118
+ self.load_from_json(state)
119
+ if stop_after_run:
120
+ self.shutdown_model()
121
+ background_tasks.add_task(self.app.stop)
122
+
123
+ @server.post("/deploy")
124
+ def deploy(request: Request):
125
+ """
126
+ Deploy the model for inference.
127
+ This endpoint prepares the model for running predictions.
128
+ """
129
+ self.gui.model_selector.model._deploy()
130
+
131
+ @server.get("/inference_settings")
132
+ def get_inference_settings():
133
+ """
134
+ Get the inference settings for the model.
135
+ This endpoint returns the current inference settings.
136
+ """
137
+ return self.get_inference_settings()
138
+
139
+ @server.get("/run_parameters")
140
+ def get_run_parameters():
141
+ """
142
+ Get the run parameters for the model.
143
+ This endpoint returns the parameters needed to run the model.
144
+ """
145
+ return self.get_run_parameters()
146
+
147
+ @server.post("/predict")
148
+ def predict(request: Request):
149
+ """
150
+ Run the model prediction.
151
+ This endpoint processes the request data and runs the model prediction.
152
+
153
+ Example data:
154
+ data = {
155
+ "inference_settings": {
156
+ "conf": 0.6,
157
+ },
158
+ "input": {
159
+ # "project_id": ...,
160
+ # "dataset_ids": [...],
161
+ "image_ids": [1148679, 1148675],
162
+ },
163
+ "output": {"mode": "iou_merge", "iou_merge_threshold": 0.5},
164
+ }
165
+ """
166
+ state = request.state.state
167
+ predictions = self.run(state)
168
+ return [prediction.to_json() for prediction in predictions]
169
+
170
+ @server.post("/run")
171
+ def run(request: Request):
172
+ """
173
+ Run the model prediction.
174
+ """
175
+ predicitons = self.run()
176
+ return [prediction.to_json() for prediction in predicitons]