supervisely 6.73.410__py3-none-any.whl → 6.73.470__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of supervisely might be problematic. Click here for more details.
- supervisely/__init__.py +136 -1
- supervisely/_utils.py +81 -0
- supervisely/annotation/json_geometries_map.py +2 -0
- supervisely/annotation/label.py +80 -3
- supervisely/api/annotation_api.py +9 -9
- supervisely/api/api.py +67 -43
- supervisely/api/app_api.py +72 -5
- supervisely/api/dataset_api.py +108 -33
- supervisely/api/entity_annotation/figure_api.py +113 -49
- supervisely/api/image_api.py +82 -0
- supervisely/api/module_api.py +10 -0
- supervisely/api/nn/deploy_api.py +15 -9
- supervisely/api/nn/ecosystem_models_api.py +201 -0
- supervisely/api/nn/neural_network_api.py +12 -3
- supervisely/api/pointcloud/pointcloud_api.py +38 -0
- supervisely/api/pointcloud/pointcloud_episode_annotation_api.py +3 -0
- supervisely/api/project_api.py +213 -6
- supervisely/api/task_api.py +11 -1
- supervisely/api/video/video_annotation_api.py +4 -2
- supervisely/api/video/video_api.py +79 -1
- supervisely/api/video/video_figure_api.py +24 -11
- supervisely/api/volume/volume_api.py +38 -0
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +14 -6
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +88 -0
- supervisely/app/fastapi/subapp.py +175 -42
- supervisely/app/fastapi/templating.py +1 -1
- supervisely/app/fastapi/websocket.py +77 -9
- supervisely/app/singleton.py +21 -0
- supervisely/app/v1/app_service.py +18 -2
- supervisely/app/v1/constants.py +7 -1
- supervisely/app/widgets/__init__.py +11 -1
- supervisely/app/widgets/agent_selector/template.html +1 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/dataset_thumbnail/dataset_thumbnail.py +11 -2
- supervisely/app/widgets/dataset_thumbnail/template.html +3 -1
- supervisely/app/widgets/deploy_model/deploy_model.py +750 -0
- supervisely/app/widgets/dialog/dialog.py +12 -0
- supervisely/app/widgets/dialog/template.html +2 -1
- supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
- supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
- supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
- supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +195 -0
- supervisely/app/widgets/experiment_selector/experiment_selector.py +454 -263
- supervisely/app/widgets/fast_table/fast_table.py +713 -126
- supervisely/app/widgets/fast_table/script.js +492 -95
- supervisely/app/widgets/fast_table/style.css +54 -0
- supervisely/app/widgets/fast_table/template.html +45 -5
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +523 -0
- supervisely/app/widgets/heatmap/script.js +378 -0
- supervisely/app/widgets/heatmap/style.css +227 -0
- supervisely/app/widgets/heatmap/template.html +21 -0
- supervisely/app/widgets/input_tag/input_tag.py +102 -15
- supervisely/app/widgets/input_tag_list/__init__.py +0 -0
- supervisely/app/widgets/input_tag_list/input_tag_list.py +274 -0
- supervisely/app/widgets/input_tag_list/template.html +70 -0
- supervisely/app/widgets/radio_table/radio_table.py +10 -2
- supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
- supervisely/app/widgets/radio_tabs/template.html +1 -0
- supervisely/app/widgets/select/select.py +6 -4
- supervisely/app/widgets/select_dataset/select_dataset.py +6 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +83 -7
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tabs/tabs.py +22 -6
- supervisely/app/widgets/tabs/template.html +5 -1
- supervisely/app/widgets/transfer/style.css +3 -0
- supervisely/app/widgets/transfer/template.html +3 -1
- supervisely/app/widgets/transfer/transfer.py +48 -45
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/convert/image/csv/csv_converter.py +24 -15
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +43 -41
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +75 -51
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +137 -124
- supervisely/convert/video/video_converter.py +2 -2
- supervisely/geometry/polyline_3d.py +110 -0
- supervisely/io/env.py +161 -1
- supervisely/nn/artifacts/__init__.py +1 -1
- supervisely/nn/artifacts/artifacts.py +10 -2
- supervisely/nn/artifacts/detectron2.py +1 -0
- supervisely/nn/artifacts/hrda.py +1 -0
- supervisely/nn/artifacts/mmclassification.py +20 -0
- supervisely/nn/artifacts/mmdetection.py +5 -3
- supervisely/nn/artifacts/mmsegmentation.py +1 -0
- supervisely/nn/artifacts/ritm.py +1 -0
- supervisely/nn/artifacts/rtdetr.py +1 -0
- supervisely/nn/artifacts/unet.py +1 -0
- supervisely/nn/artifacts/utils.py +3 -0
- supervisely/nn/artifacts/yolov5.py +2 -0
- supervisely/nn/artifacts/yolov8.py +1 -0
- supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
- supervisely/nn/experiments.py +9 -0
- supervisely/nn/inference/cache.py +37 -17
- supervisely/nn/inference/gui/serving_gui_template.py +39 -13
- supervisely/nn/inference/inference.py +953 -211
- supervisely/nn/inference/inference_request.py +15 -8
- supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
- supervisely/nn/inference/object_detection/object_detection.py +1 -0
- supervisely/nn/inference/predict_app/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/classes_selector.py +160 -0
- supervisely/nn/inference/predict_app/gui/gui.py +915 -0
- supervisely/nn/inference/predict_app/gui/input_selector.py +344 -0
- supervisely/nn/inference/predict_app/gui/model_selector.py +77 -0
- supervisely/nn/inference/predict_app/gui/output_selector.py +179 -0
- supervisely/nn/inference/predict_app/gui/preview.py +93 -0
- supervisely/nn/inference/predict_app/gui/settings_selector.py +881 -0
- supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
- supervisely/nn/inference/predict_app/gui/utils.py +399 -0
- supervisely/nn/inference/predict_app/predict_app.py +176 -0
- supervisely/nn/inference/session.py +47 -39
- supervisely/nn/inference/tracking/bbox_tracking.py +5 -1
- supervisely/nn/inference/tracking/point_tracking.py +5 -1
- supervisely/nn/inference/tracking/tracker_interface.py +4 -0
- supervisely/nn/inference/uploader.py +9 -5
- supervisely/nn/model/model_api.py +44 -22
- supervisely/nn/model/prediction.py +15 -1
- supervisely/nn/model/prediction_session.py +70 -14
- supervisely/nn/prediction_dto.py +7 -0
- supervisely/nn/tracker/__init__.py +6 -8
- supervisely/nn/tracker/base_tracker.py +54 -0
- supervisely/nn/tracker/botsort/__init__.py +1 -0
- supervisely/nn/tracker/botsort/botsort_config.yaml +30 -0
- supervisely/nn/tracker/botsort/osnet_reid/__init__.py +0 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
- supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
- supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
- supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
- supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
- supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
- supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
- supervisely/nn/tracker/botsort_tracker.py +273 -0
- supervisely/nn/tracker/calculate_metrics.py +264 -0
- supervisely/nn/tracker/utils.py +273 -0
- supervisely/nn/tracker/visualize.py +520 -0
- supervisely/nn/training/gui/gui.py +152 -49
- supervisely/nn/training/gui/hyperparameters_selector.py +1 -1
- supervisely/nn/training/gui/model_selector.py +8 -6
- supervisely/nn/training/gui/train_val_splits_selector.py +144 -71
- supervisely/nn/training/gui/training_artifacts.py +3 -1
- supervisely/nn/training/train_app.py +225 -46
- supervisely/project/pointcloud_episode_project.py +12 -8
- supervisely/project/pointcloud_project.py +12 -8
- supervisely/project/project.py +221 -75
- supervisely/template/experiment/experiment.html.jinja +105 -55
- supervisely/template/experiment/experiment_generator.py +258 -112
- supervisely/template/experiment/header.html.jinja +31 -13
- supervisely/template/experiment/sly-style.css +7 -2
- supervisely/versions.json +3 -1
- supervisely/video/sampling.py +42 -20
- supervisely/video/video.py +41 -12
- supervisely/video_annotation/video_figure.py +38 -4
- supervisely/volume/stl_converter.py +2 -0
- supervisely/worker_api/agent_rpc.py +24 -1
- supervisely/worker_api/rpc_servicer.py +31 -7
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/METADATA +22 -14
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/RECORD +167 -148
- supervisely_lib/__init__.py +6 -1
- supervisely/app/widgets/experiment_selector/style.css +0 -27
- supervisely/app/widgets/experiment_selector/template.html +0 -61
- supervisely/nn/tracker/bot_sort/__init__.py +0 -21
- supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
- supervisely/nn/tracker/bot_sort/matching.py +0 -127
- supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
- supervisely/nn/tracker/deep_sort/__init__.py +0 -6
- supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
- supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
- supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
- supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
- supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
- supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
- supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
- supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
- supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
- supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
- supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
- supervisely/nn/tracker/tracker.py +0 -285
- supervisely/nn/tracker/utils/kalman_filter.py +0 -492
- supervisely/nn/tracking/__init__.py +0 -1
- supervisely/nn/tracking/boxmot.py +0 -114
- supervisely/nn/tracking/tracking.py +0 -24
- /supervisely/{nn/tracker/utils → app/widgets/deploy_model}/__init__.py +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/LICENSE +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/WHEEL +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/top_level.txt +0 -0
|
@@ -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]
|