supervisely 6.73.376__py3-none-any.whl → 6.73.377__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- supervisely/__init__.py +1 -0
- supervisely/_utils.py +11 -0
- supervisely/app/fastapi/index.html +20 -0
- supervisely/app/widgets/__init__.py +1 -0
- supervisely/app/widgets/flexbox/flexbox.py +4 -2
- supervisely/app/widgets/flexbox/template.html +5 -9
- supervisely/app/widgets/input_number/input_number.py +2 -0
- supervisely/app/widgets/input_number/template.html +3 -0
- supervisely/app/widgets/sampling/__init__.py +0 -0
- supervisely/app/widgets/sampling/sampling.py +550 -0
- supervisely/app/widgets/select_dataset/select_dataset.py +15 -5
- supervisely/app/widgets/sly_tqdm/sly_tqdm.py +9 -0
- supervisely/video/sampling.py +550 -0
- {supervisely-6.73.376.dist-info → supervisely-6.73.377.dist-info}/METADATA +1 -1
- {supervisely-6.73.376.dist-info → supervisely-6.73.377.dist-info}/RECORD +19 -16
- {supervisely-6.73.376.dist-info → supervisely-6.73.377.dist-info}/LICENSE +0 -0
- {supervisely-6.73.376.dist-info → supervisely-6.73.377.dist-info}/WHEEL +0 -0
- {supervisely-6.73.376.dist-info → supervisely-6.73.377.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.376.dist-info → supervisely-6.73.377.dist-info}/top_level.txt +0 -0
supervisely/__init__.py
CHANGED
supervisely/_utils.py
CHANGED
|
@@ -98,6 +98,17 @@ def batched(seq, batch_size=50):
|
|
|
98
98
|
yield seq[i : i + batch_size]
|
|
99
99
|
|
|
100
100
|
|
|
101
|
+
def batched_iter(iterable, batch_size=50):
|
|
102
|
+
batch = []
|
|
103
|
+
for item in iterable:
|
|
104
|
+
batch.append(item)
|
|
105
|
+
if len(batch) == batch_size:
|
|
106
|
+
yield batch
|
|
107
|
+
batch = []
|
|
108
|
+
if batch:
|
|
109
|
+
yield batch
|
|
110
|
+
|
|
111
|
+
|
|
101
112
|
def get_bytes_hash(bytes):
|
|
102
113
|
return base64.b64encode(hashlib.sha256(bytes).digest()).decode("utf-8")
|
|
103
114
|
|
|
@@ -57,6 +57,26 @@
|
|
|
57
57
|
border-bottom: 1px solid #dfe2e8;
|
|
58
58
|
position: relative;
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
.el-input-number--mini .el-input-number__decrease,
|
|
62
|
+
.el-input-number--mini .el-input-number__increase {
|
|
63
|
+
height: 20px !important;
|
|
64
|
+
width: 20px !important;
|
|
65
|
+
line-height: 18px !important;
|
|
66
|
+
font-size: 12px !important;
|
|
67
|
+
min-width: 20px !important;
|
|
68
|
+
padding: 0 !important;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.el-input-number--mini .el-input-number__decrease {
|
|
72
|
+
right: 20px !important;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.el-input-number--mini .el-input-number__decrease i,
|
|
76
|
+
.el-input-number--mini .el-input-number__increase i {
|
|
77
|
+
font-size: 10px !important;
|
|
78
|
+
line-height: 18px !important;
|
|
79
|
+
}
|
|
60
80
|
</style>
|
|
61
81
|
<title>{{{app_name}}}</title>
|
|
62
82
|
</head>
|
|
@@ -151,3 +151,4 @@ from supervisely.app.widgets.experiment_selector.experiment_selector import Expe
|
|
|
151
151
|
from supervisely.app.widgets.bokeh.bokeh import Bokeh
|
|
152
152
|
from supervisely.app.widgets.run_app_button.run_app_button import RunAppButton
|
|
153
153
|
from supervisely.app.widgets.select_collection.select_collection import SelectCollection
|
|
154
|
+
from supervisely.app.widgets.sampling.sampling import Sampling
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from
|
|
2
|
-
from typing import Dict, List
|
|
1
|
+
from typing import Dict, List, Literal
|
|
3
2
|
|
|
3
|
+
from supervisely.app.widgets import Widget
|
|
4
4
|
|
|
5
5
|
"""
|
|
6
6
|
<div
|
|
@@ -21,10 +21,12 @@ class Flexbox(Widget):
|
|
|
21
21
|
gap: int = 10,
|
|
22
22
|
center_content: bool = False,
|
|
23
23
|
widget_id: str = None,
|
|
24
|
+
vertical_alignment: Literal["start", "end", "center", "stretch", "baseline"] = None,
|
|
24
25
|
):
|
|
25
26
|
self._widgets = widgets
|
|
26
27
|
self._gap = gap
|
|
27
28
|
self._center_content = center_content
|
|
29
|
+
self._vertical_alignment = vertical_alignment
|
|
28
30
|
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
29
31
|
|
|
30
32
|
def get_json_data(self) -> Dict:
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
<div
|
|
2
|
-
{
|
|
3
|
-
style="display: flex; gap: {{{widget._gap}}}px;
|
|
4
|
-
{% else %}
|
|
5
|
-
style="display: flex; gap: {{{widget._gap}}}px;"
|
|
6
|
-
{% endif %}
|
|
7
|
-
>
|
|
1
|
+
<div {% if widget._center_content==true %}
|
|
2
|
+
style="display: flex; gap: {{{widget._gap}}}px; align-items: {{{widget._vertical_alignment}}}; justify-content: center;"
|
|
3
|
+
{% else %} style="display: flex; gap: {{{widget._gap}}}px; align-items: {{{widget._vertical_alignment}}};" {% endif%}>
|
|
8
4
|
{% for w in widget._widgets %}
|
|
9
|
-
|
|
5
|
+
{{{w}}}
|
|
10
6
|
{% endfor %}
|
|
11
|
-
</div>
|
|
7
|
+
</div>
|
|
@@ -20,6 +20,7 @@ class InputNumber(Widget):
|
|
|
20
20
|
debounce: int = 300,
|
|
21
21
|
precision: int = 0,
|
|
22
22
|
widget_id: str = None,
|
|
23
|
+
width: int = None,
|
|
23
24
|
):
|
|
24
25
|
self._value = value
|
|
25
26
|
self._min = min
|
|
@@ -29,6 +30,7 @@ class InputNumber(Widget):
|
|
|
29
30
|
self._controls = controls
|
|
30
31
|
self._debounce = debounce
|
|
31
32
|
self._precision = precision
|
|
33
|
+
self._width = width
|
|
32
34
|
self._changes_handled = False
|
|
33
35
|
|
|
34
36
|
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
@@ -16,5 +16,8 @@
|
|
|
16
16
|
:controls="data.{{{widget.widget_id}}}.controls"
|
|
17
17
|
:debounce="data.{{{widget.widget_id}}}.debounce"
|
|
18
18
|
:precision="data.{{{widget.widget_id}}}.precision"
|
|
19
|
+
{% if widget._width %}
|
|
20
|
+
:style="{ width: '{{{widget._width}}}px' }"
|
|
21
|
+
{% endif %}
|
|
19
22
|
>
|
|
20
23
|
</el-input-number>
|
|
File without changes
|
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
from typing import List, Tuple, Union
|
|
2
|
+
|
|
3
|
+
from supervisely.api.api import Api
|
|
4
|
+
from supervisely.api.dataset_api import DatasetInfo
|
|
5
|
+
from supervisely.app.widgets import Progress
|
|
6
|
+
from supervisely.app.widgets.button.button import Button
|
|
7
|
+
from supervisely.app.widgets.checkbox.checkbox import Checkbox
|
|
8
|
+
from supervisely.app.widgets.container.container import Container
|
|
9
|
+
from supervisely.app.widgets.empty.empty import Empty
|
|
10
|
+
from supervisely.app.widgets.flexbox.flexbox import Flexbox
|
|
11
|
+
from supervisely.app.widgets.input_number.input_number import InputNumber
|
|
12
|
+
from supervisely.app.widgets.notification_box.notification_box import NotificationBox
|
|
13
|
+
from supervisely.app.widgets.one_of.one_of import OneOf
|
|
14
|
+
from supervisely.app.widgets.project_thumbnail.project_thumbnail import ProjectThumbnail
|
|
15
|
+
from supervisely.app.widgets.radio_group.radio_group import RadioGroup
|
|
16
|
+
from supervisely.app.widgets.select_dataset.select_dataset import SelectDataset
|
|
17
|
+
from supervisely.app.widgets.select_project.select_project import SelectProject
|
|
18
|
+
from supervisely.app.widgets.text.text import Text
|
|
19
|
+
from supervisely.app.widgets.widget import Widget
|
|
20
|
+
from supervisely.project.project import ProjectType
|
|
21
|
+
from supervisely.video.sampling import SamplingSettings, sample_video_project
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Sampling(Widget):
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
project_id: int = None,
|
|
28
|
+
input_selectable: bool = True,
|
|
29
|
+
datasets_ids: List[int] = None,
|
|
30
|
+
output_project_id: int = None,
|
|
31
|
+
output_project_selectable: bool = True,
|
|
32
|
+
widgth: int = 370,
|
|
33
|
+
widget_id: str = None,
|
|
34
|
+
file_path: str = __file__,
|
|
35
|
+
):
|
|
36
|
+
super().__init__(widget_id, file_path)
|
|
37
|
+
if not input_selectable and project_id is None:
|
|
38
|
+
raise ValueError(
|
|
39
|
+
"Either 'project_id' must be provided or 'project_selectable' must be True."
|
|
40
|
+
)
|
|
41
|
+
if project_id is None and not input_selectable:
|
|
42
|
+
input_selectable = True
|
|
43
|
+
if not output_project_selectable and output_project_id is None:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"Either 'output_project_id' must be provided or 'output_project_selectable' must be True."
|
|
46
|
+
)
|
|
47
|
+
if output_project_id is None and not output_project_selectable:
|
|
48
|
+
output_project_selectable = True
|
|
49
|
+
self._api = Api()
|
|
50
|
+
self.project_id = project_id
|
|
51
|
+
self.project_selectable = input_selectable
|
|
52
|
+
self.datasets_ids = datasets_ids
|
|
53
|
+
self.output_project_id = output_project_id
|
|
54
|
+
self.output_project_selectable = output_project_selectable
|
|
55
|
+
self.widgth = widgth
|
|
56
|
+
self.project_info = (
|
|
57
|
+
self._api.project.get_info_by_id(self.project_id) if self.project_id else None
|
|
58
|
+
)
|
|
59
|
+
self.all_datasets = (
|
|
60
|
+
self._api.dataset.get_list(self.project_id, recursive=True) if self.project_id else []
|
|
61
|
+
)
|
|
62
|
+
self.items_count = self._count_items(self.all_datasets, datasets_ids, with_children=True)
|
|
63
|
+
self._init_gui()
|
|
64
|
+
|
|
65
|
+
def _init_input_gui(self):
|
|
66
|
+
self.input_datasets_select = SelectDataset(
|
|
67
|
+
default_id=self.datasets_ids,
|
|
68
|
+
project_id=self.project_id,
|
|
69
|
+
allowed_project_types=[ProjectType.VIDEOS],
|
|
70
|
+
size="mini",
|
|
71
|
+
multiselect=True,
|
|
72
|
+
select_all_datasets=self.datasets_ids is None,
|
|
73
|
+
)
|
|
74
|
+
self.project_preview = ProjectThumbnail()
|
|
75
|
+
self.project_preview.hide()
|
|
76
|
+
if not self.project_selectable:
|
|
77
|
+
self.input_datasets_select.hide()
|
|
78
|
+
project_info = self._api.project.get_info_by_id(self.project_id)
|
|
79
|
+
self.project_preview.set(project_info)
|
|
80
|
+
self.project_preview.show()
|
|
81
|
+
self.nested_datasets_checkbox = Checkbox(
|
|
82
|
+
"Include nested datasets",
|
|
83
|
+
checked=True,
|
|
84
|
+
)
|
|
85
|
+
self.input_project_container = Container(
|
|
86
|
+
widgets=[
|
|
87
|
+
self.input_datasets_select,
|
|
88
|
+
self.nested_datasets_checkbox,
|
|
89
|
+
self.project_preview,
|
|
90
|
+
],
|
|
91
|
+
style="padding-left: 21px; padding-top: 10px;",
|
|
92
|
+
)
|
|
93
|
+
self.input_field = Container(
|
|
94
|
+
widgets=[
|
|
95
|
+
Text(
|
|
96
|
+
'<i class="zmdi zmdi-collection-video" style="padding-right: 10px; color: rgb(0, 154, 255);"></i><b>Input project</b>'
|
|
97
|
+
),
|
|
98
|
+
self.input_project_container,
|
|
99
|
+
],
|
|
100
|
+
gap=0,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def _init_settings_gui(self):
|
|
104
|
+
self.only_annotated_checkbox = Checkbox(Text("Only annotated frames", font_size=13))
|
|
105
|
+
self.only_annotated_row = Flexbox(widgets=[self.only_annotated_checkbox])
|
|
106
|
+
self.step_label = Text("Step:", font_size=13)
|
|
107
|
+
self.step_input = InputNumber(value=1, min=1, step=1, size="mini", width=160)
|
|
108
|
+
self.step_row = Container(
|
|
109
|
+
widgets=[self.step_label, self.step_input],
|
|
110
|
+
direction="horizontal",
|
|
111
|
+
style="width: 202px; align-items: center;",
|
|
112
|
+
widgets_style="flex: 1; display: flex;",
|
|
113
|
+
)
|
|
114
|
+
self.resize_checkbox = Checkbox(Text("Resize frames", font_size=13))
|
|
115
|
+
self.resize_input_h_label = Text("Height:", font_size=13)
|
|
116
|
+
self.resize_input_h = InputNumber(value=224, min=1, step=1, size="mini", controls=False)
|
|
117
|
+
self.resize_input_w_label = Text("Width:", font_size=13)
|
|
118
|
+
self.resize_input_w = InputNumber(value=224, min=1, step=1, size="mini", controls=False)
|
|
119
|
+
self.resize_h_row = Flexbox(widgets=[self.resize_input_h_label, self.resize_input_h])
|
|
120
|
+
self.resize_w_row = Flexbox(
|
|
121
|
+
widgets=[self.resize_input_w_label, self.resize_input_w], gap=15
|
|
122
|
+
)
|
|
123
|
+
self.resize_row = Flexbox(widgets=[self.resize_checkbox])
|
|
124
|
+
self.resize_hw_container = Container(
|
|
125
|
+
widgets=[Empty(), self.resize_h_row, self.resize_w_row]
|
|
126
|
+
)
|
|
127
|
+
self.resize_hw_container.hide()
|
|
128
|
+
self.resize_container = Container(
|
|
129
|
+
widgets=[self.resize_row, self.resize_hw_container], gap=0
|
|
130
|
+
)
|
|
131
|
+
self.copy_annotations_checkbox = Checkbox(
|
|
132
|
+
Text("Copy annotations from source project", font_size=13)
|
|
133
|
+
)
|
|
134
|
+
self.copy_annotations_row = Flexbox(widgets=[self.copy_annotations_checkbox])
|
|
135
|
+
|
|
136
|
+
@self.resize_checkbox.value_changed
|
|
137
|
+
def on_resize_checkbox_change(checked: bool):
|
|
138
|
+
if checked:
|
|
139
|
+
self.resize_hw_container.show()
|
|
140
|
+
else:
|
|
141
|
+
self.resize_hw_container.hide()
|
|
142
|
+
|
|
143
|
+
self.settings_container = Container(
|
|
144
|
+
widgets=[
|
|
145
|
+
Empty(),
|
|
146
|
+
self.step_row,
|
|
147
|
+
self.only_annotated_row,
|
|
148
|
+
self.resize_container,
|
|
149
|
+
self.copy_annotations_row,
|
|
150
|
+
Empty(),
|
|
151
|
+
],
|
|
152
|
+
style="padding-left: 21px; padding-top: 10px;",
|
|
153
|
+
)
|
|
154
|
+
self.settings_field = Container(
|
|
155
|
+
widgets=[
|
|
156
|
+
Text(
|
|
157
|
+
'<i class="zmdi zmdi-settings" style="padding-right: 10px; color: rgb(0, 154, 255);"></i><b>Sampling settings</b>'
|
|
158
|
+
),
|
|
159
|
+
self.settings_container,
|
|
160
|
+
],
|
|
161
|
+
gap=0,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _datasets_to_process(
|
|
165
|
+
self, all_datasets: List[DatasetInfo], datasets_ids: List[int], with_children: bool
|
|
166
|
+
) -> List[DatasetInfo]:
|
|
167
|
+
if datasets_ids is None:
|
|
168
|
+
return all_datasets.copy()
|
|
169
|
+
datasets = []
|
|
170
|
+
for ds in [ds for ds in all_datasets if ds.id in datasets_ids]:
|
|
171
|
+
datasets.append(ds)
|
|
172
|
+
if with_children:
|
|
173
|
+
children = [child for child in all_datasets if child.parent_id == ds.id]
|
|
174
|
+
if children:
|
|
175
|
+
datasets.extend(
|
|
176
|
+
self._datasets_to_process(
|
|
177
|
+
all_datasets, [child.id for child in children], True
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
return datasets
|
|
181
|
+
|
|
182
|
+
def _count_items(
|
|
183
|
+
self, all_datasets: List[DatasetInfo], datasets_ids: List[int], with_children: bool
|
|
184
|
+
) -> int:
|
|
185
|
+
return sum(
|
|
186
|
+
ds.items_count
|
|
187
|
+
for ds in self._datasets_to_process(
|
|
188
|
+
all_datasets, datasets_ids, with_children=with_children
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def _selected_text(self, datasets_ids: List[int] = None, with_children: bool = True) -> str:
|
|
193
|
+
red = "#FF6458;"
|
|
194
|
+
blue = "rgb(0, 154, 255);"
|
|
195
|
+
datasets = self._datasets_to_process(
|
|
196
|
+
self.all_datasets, datasets_ids, with_children=with_children
|
|
197
|
+
)
|
|
198
|
+
color = blue if self.items_count > 0 else red
|
|
199
|
+
ds_num = len(datasets)
|
|
200
|
+
return f'Selected <b style="color: {color};">{ds_num}</b> dataset{"s" if ds_num % 10 != 1 else ""} with <b style="color: {color};">{self.items_count}</b> videos'
|
|
201
|
+
|
|
202
|
+
def _update_preview(self):
|
|
203
|
+
with_children = self.nested_datasets_checkbox.is_checked()
|
|
204
|
+
self.selected_items_text.text = self._selected_text(
|
|
205
|
+
self.datasets_ids, with_children=with_children
|
|
206
|
+
)
|
|
207
|
+
if self.items_count > 0:
|
|
208
|
+
self.run_button.enable()
|
|
209
|
+
else:
|
|
210
|
+
self.run_button.disable()
|
|
211
|
+
|
|
212
|
+
def _datasets_changed(self, datasets_ids: List[int]):
|
|
213
|
+
self.preview_container.loading = True
|
|
214
|
+
self.datasets_ids = datasets_ids
|
|
215
|
+
project_id = self.input_datasets_select.get_selected_project_id()
|
|
216
|
+
with_children = self.nested_datasets_checkbox.is_checked()
|
|
217
|
+
if self.project_id != project_id:
|
|
218
|
+
if project_id is None:
|
|
219
|
+
self.project_id = None
|
|
220
|
+
self.project_info = None
|
|
221
|
+
self.all_datasets = []
|
|
222
|
+
else:
|
|
223
|
+
self.project_id = project_id
|
|
224
|
+
self._api.project.get_info_by_id(project_id)
|
|
225
|
+
self.all_datasets = self._api.dataset.get_list(project_id, recursive=True)
|
|
226
|
+
self.items_count = self._count_items(
|
|
227
|
+
self.all_datasets, datasets_ids, with_children=with_children
|
|
228
|
+
)
|
|
229
|
+
self._update_preview()
|
|
230
|
+
self.preview_container.loading = False
|
|
231
|
+
|
|
232
|
+
def _init_peview_gui(self):
|
|
233
|
+
self.selected_items_text = Text("", font_size=13)
|
|
234
|
+
self.run_button = Button("Run", icon="zmdi zmdi-play", button_size="mini")
|
|
235
|
+
self.run_button.disable()
|
|
236
|
+
|
|
237
|
+
self.preview_text = Text(
|
|
238
|
+
'<i class="zmdi zmdi-eye" style="padding-right: 10px; color: rgb(0, 154, 255);"></i><b style="font-size: 14px">Preview</b>',
|
|
239
|
+
font_size=13,
|
|
240
|
+
)
|
|
241
|
+
self.preview_field = Container(
|
|
242
|
+
widgets=[
|
|
243
|
+
self.preview_text,
|
|
244
|
+
Container(widgets=[self.selected_items_text], style="padding-left: 21px;"),
|
|
245
|
+
],
|
|
246
|
+
style="padding-top: 10px;",
|
|
247
|
+
)
|
|
248
|
+
self.run_button_container = Container(
|
|
249
|
+
widgets=[self.run_button],
|
|
250
|
+
direction="horizontal",
|
|
251
|
+
overflow="wrap",
|
|
252
|
+
style="display: flex; justify-content: flex-end;",
|
|
253
|
+
widgets_style="display: flex; flex: none;",
|
|
254
|
+
)
|
|
255
|
+
self.preview_container = Container(
|
|
256
|
+
widgets=[self.preview_field, self.run_button_container],
|
|
257
|
+
)
|
|
258
|
+
self._update_preview()
|
|
259
|
+
|
|
260
|
+
@self.input_datasets_select.value_changed
|
|
261
|
+
def on_input_datasets_select_change(datasets_ids: List[int]):
|
|
262
|
+
self._datasets_changed(datasets_ids)
|
|
263
|
+
|
|
264
|
+
@self.nested_datasets_checkbox.value_changed
|
|
265
|
+
def on_nested_datasets_checkbox_change(is_checked: bool):
|
|
266
|
+
self._datasets_changed(self.input_datasets_select.get_selected_ids())
|
|
267
|
+
|
|
268
|
+
@self.run_button.click
|
|
269
|
+
def on_run_button_click():
|
|
270
|
+
self.run()
|
|
271
|
+
|
|
272
|
+
def _init_output_gui(self):
|
|
273
|
+
self.output_project_select = SelectProject(
|
|
274
|
+
default_id=self.output_project_id, allowed_types=[ProjectType.IMAGES], size="mini"
|
|
275
|
+
)
|
|
276
|
+
self.output_project_preview = ProjectThumbnail()
|
|
277
|
+
self.output_project_preview.hide()
|
|
278
|
+
self.output_mode_radio = RadioGroup(
|
|
279
|
+
items=[
|
|
280
|
+
RadioGroup.Item("create", "Create new project", content=Empty()),
|
|
281
|
+
RadioGroup.Item(
|
|
282
|
+
"merge",
|
|
283
|
+
"Merge with existing project",
|
|
284
|
+
content=self.output_project_select,
|
|
285
|
+
),
|
|
286
|
+
]
|
|
287
|
+
)
|
|
288
|
+
self.output_merge_project_oneof = OneOf(self.output_mode_radio)
|
|
289
|
+
self.output_project_container = Container(
|
|
290
|
+
widgets=[self.output_mode_radio, self.output_merge_project_oneof],
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
self.output_container = Container(
|
|
294
|
+
widgets=[self.output_project_preview, self.output_project_container],
|
|
295
|
+
gap=0,
|
|
296
|
+
style="padding-left: 21px; padding-top: 10px;",
|
|
297
|
+
)
|
|
298
|
+
if not self.output_project_selectable:
|
|
299
|
+
self.output_project_container.hide()
|
|
300
|
+
project_info = self._api.project.get_info_by_id(self.output_project_id)
|
|
301
|
+
self.output_project_preview.set(project_info)
|
|
302
|
+
self.output_project_preview.show()
|
|
303
|
+
if self.output_project_id is not None:
|
|
304
|
+
self.output_mode_radio.set_value("merge")
|
|
305
|
+
|
|
306
|
+
self.output_field = Container(
|
|
307
|
+
widgets=[
|
|
308
|
+
Text(
|
|
309
|
+
'<i class="zmdi zmdi-collection-folder-image" style="padding-right: 10px; color: rgb(0, 154, 255);"></i><b>Output project</b>'
|
|
310
|
+
),
|
|
311
|
+
self.output_container,
|
|
312
|
+
],
|
|
313
|
+
gap=0,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def _init_progress_gui(self):
|
|
317
|
+
self.items_progress = Progress(hide_on_finish=False)
|
|
318
|
+
self.frames_progress = Progress(hide_on_finish=False)
|
|
319
|
+
self.error_notification = NotificationBox(title="Error", description="", box_type="error")
|
|
320
|
+
self.error_notification.hide()
|
|
321
|
+
self.progress_container = Container(widgets=[self.items_progress, self.frames_progress])
|
|
322
|
+
self.progress_container.hide()
|
|
323
|
+
self.result_project_preview = ProjectThumbnail()
|
|
324
|
+
self.result_project_preview_field = Container(
|
|
325
|
+
widgets=[
|
|
326
|
+
Text(
|
|
327
|
+
'<i class="zmdi zmdi-check-square" style="padding-right: 10px; color: rgb(0, 154, 255);"></i><b>Result project</b>'
|
|
328
|
+
),
|
|
329
|
+
Container(
|
|
330
|
+
widgets=[self.result_project_preview],
|
|
331
|
+
style="padding-left: 21px;",
|
|
332
|
+
),
|
|
333
|
+
]
|
|
334
|
+
)
|
|
335
|
+
self.result_project_preview_field.hide()
|
|
336
|
+
self.result_container = Container(
|
|
337
|
+
widgets=[
|
|
338
|
+
self.progress_container,
|
|
339
|
+
self.error_notification,
|
|
340
|
+
self.result_project_preview_field,
|
|
341
|
+
],
|
|
342
|
+
gap=0,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
def _init_gui(self):
|
|
346
|
+
self._init_input_gui()
|
|
347
|
+
self._init_settings_gui()
|
|
348
|
+
self._init_output_gui()
|
|
349
|
+
self._init_peview_gui()
|
|
350
|
+
self._init_progress_gui()
|
|
351
|
+
|
|
352
|
+
self.content = Container(
|
|
353
|
+
widgets=[
|
|
354
|
+
self.input_field,
|
|
355
|
+
self.settings_field,
|
|
356
|
+
self.output_field,
|
|
357
|
+
self.preview_container,
|
|
358
|
+
self.result_container,
|
|
359
|
+
],
|
|
360
|
+
style=f"width: {self.widgth}px;",
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
def _get_settings(self) -> dict:
|
|
364
|
+
settings = {
|
|
365
|
+
SamplingSettings.ONLY_ANNOTATED: self.only_annotated_checkbox.is_checked(),
|
|
366
|
+
SamplingSettings.STEP: self.step_input.get_value(),
|
|
367
|
+
SamplingSettings.RESIZE: None,
|
|
368
|
+
SamplingSettings.COPY_ANNOTATIONS: self.copy_annotations_checkbox.is_checked(),
|
|
369
|
+
}
|
|
370
|
+
if self.resize_checkbox.is_checked():
|
|
371
|
+
settings[SamplingSettings.RESIZE] = [
|
|
372
|
+
self.resize_input_h.get_value(),
|
|
373
|
+
self.resize_input_w.get_value(),
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
return settings
|
|
377
|
+
|
|
378
|
+
def _get_dst_project_id(self) -> int:
|
|
379
|
+
if self.output_mode_radio.get_value() == "create":
|
|
380
|
+
return None
|
|
381
|
+
else:
|
|
382
|
+
return self.output_project_select.get_selected_id()
|
|
383
|
+
|
|
384
|
+
def run(self):
|
|
385
|
+
self.progress_container.show()
|
|
386
|
+
self.error_notification.hide()
|
|
387
|
+
self.result_project_preview_field.hide()
|
|
388
|
+
try:
|
|
389
|
+
project_id = self.input_datasets_select.get_selected_project_id()
|
|
390
|
+
datasets_ids = self.input_datasets_select.get_selected_ids()
|
|
391
|
+
with_children = self.nested_datasets_checkbox.is_checked()
|
|
392
|
+
selected_datasets_with_children = self._datasets_to_process(
|
|
393
|
+
all_datasets=self.all_datasets,
|
|
394
|
+
datasets_ids=datasets_ids,
|
|
395
|
+
with_children=with_children,
|
|
396
|
+
)
|
|
397
|
+
total_items = sum(ds.items_count for ds in selected_datasets_with_children)
|
|
398
|
+
datasets_ids = [ds.id for ds in selected_datasets_with_children]
|
|
399
|
+
project_info = self.project_info
|
|
400
|
+
|
|
401
|
+
if project_info.type != str(ProjectType.VIDEOS):
|
|
402
|
+
raise ValueError(
|
|
403
|
+
f"Project with ID {self.input_datasets_select.get_selected_id()} is not a video project."
|
|
404
|
+
)
|
|
405
|
+
frames_pbar = self.frames_progress()
|
|
406
|
+
with self.items_progress(
|
|
407
|
+
message=f"Videos progress...",
|
|
408
|
+
total=total_items,
|
|
409
|
+
) as pbar:
|
|
410
|
+
self.progress_container.show()
|
|
411
|
+
dst_project_info = sample_video_project(
|
|
412
|
+
api=self._api,
|
|
413
|
+
project_id=project_id,
|
|
414
|
+
settings=self._get_settings(),
|
|
415
|
+
dst_project_id=self._get_dst_project_id(),
|
|
416
|
+
datasets_ids=datasets_ids,
|
|
417
|
+
items_progress_cb=pbar.update,
|
|
418
|
+
video_progress=frames_pbar,
|
|
419
|
+
)
|
|
420
|
+
except Exception as e:
|
|
421
|
+
self.error_notification.set(title="Error", description=str(e))
|
|
422
|
+
self.error_notification.show()
|
|
423
|
+
raise e
|
|
424
|
+
else:
|
|
425
|
+
if self.output_project_selectable:
|
|
426
|
+
dst_project_info = self._api.project.get_info_by_id(dst_project_info.id)
|
|
427
|
+
self.result_project_preview.set(dst_project_info)
|
|
428
|
+
self.result_project_preview_field.show()
|
|
429
|
+
else:
|
|
430
|
+
dst_project_info = self._api.project.get_info_by_id(self.output_project_id)
|
|
431
|
+
self.output_project_preview.set(dst_project_info)
|
|
432
|
+
finally:
|
|
433
|
+
self.progress_container.hide()
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def selected_project_id(self) -> int:
|
|
437
|
+
if not self.project_selectable:
|
|
438
|
+
return self.project_id
|
|
439
|
+
return self.input_datasets_select.get_selected_project_id()
|
|
440
|
+
|
|
441
|
+
@selected_project_id.setter
|
|
442
|
+
def selected_project_id(self, value: int):
|
|
443
|
+
if not self.project_selectable:
|
|
444
|
+
raise ValueError("Project is not selectable.")
|
|
445
|
+
self.input_datasets_select.set_project_id(value)
|
|
446
|
+
self._datasets_changed(self.input_datasets_select.get_selected_ids())
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def selected_all_datasets(self) -> bool:
|
|
450
|
+
return self.input_datasets_select._all_datasets_checkbox.is_checked()
|
|
451
|
+
|
|
452
|
+
@selected_all_datasets.setter
|
|
453
|
+
def selected_all_datasets(self, value: bool):
|
|
454
|
+
if value:
|
|
455
|
+
self.input_datasets_select._all_datasets_checkbox.check()
|
|
456
|
+
else:
|
|
457
|
+
self.input_datasets_select._all_datasets_checkbox.uncheck()
|
|
458
|
+
self._datasets_changed(self.input_datasets_select.get_selected_ids())
|
|
459
|
+
|
|
460
|
+
@property
|
|
461
|
+
def selected_datasets_ids(self) -> List[int]:
|
|
462
|
+
return self.input_datasets_select.get_selected_ids()
|
|
463
|
+
|
|
464
|
+
@selected_datasets_ids.setter
|
|
465
|
+
def selected_datasets_ids(self, value: List[int]):
|
|
466
|
+
self.input_datasets_select.set_dataset_ids(value)
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def include_nested_datasets(self) -> bool:
|
|
470
|
+
return self.nested_datasets_checkbox.is_checked()
|
|
471
|
+
|
|
472
|
+
@include_nested_datasets.setter
|
|
473
|
+
def include_nested_datasets(self, value: bool):
|
|
474
|
+
if value:
|
|
475
|
+
self.nested_datasets_checkbox.check()
|
|
476
|
+
else:
|
|
477
|
+
self.nested_datasets_checkbox.uncheck()
|
|
478
|
+
self._datasets_changed(self.input_datasets_select.get_selected_ids())
|
|
479
|
+
|
|
480
|
+
@property
|
|
481
|
+
def step(self) -> int:
|
|
482
|
+
return self.step_input.get_value()
|
|
483
|
+
|
|
484
|
+
@step.setter
|
|
485
|
+
def step(self, value: int):
|
|
486
|
+
self.step_input.value = value
|
|
487
|
+
|
|
488
|
+
@property
|
|
489
|
+
def only_annotated(self) -> bool:
|
|
490
|
+
return self.only_annotated_checkbox.is_checked()
|
|
491
|
+
|
|
492
|
+
@only_annotated.setter
|
|
493
|
+
def only_annotated(self, value: bool):
|
|
494
|
+
if value:
|
|
495
|
+
self.only_annotated_checkbox.check()
|
|
496
|
+
else:
|
|
497
|
+
self.only_annotated_checkbox.uncheck()
|
|
498
|
+
|
|
499
|
+
@property
|
|
500
|
+
def resize(self) -> Union[Tuple[int, int], None]:
|
|
501
|
+
if self.resize_checkbox.is_checked():
|
|
502
|
+
return (self.resize_input_h.get_value(), self.resize_input_w.get_value())
|
|
503
|
+
return None
|
|
504
|
+
|
|
505
|
+
@resize.setter
|
|
506
|
+
def resize(self, value: Union[Tuple[int, int], None]):
|
|
507
|
+
if value is None:
|
|
508
|
+
self.resize_checkbox.uncheck()
|
|
509
|
+
else:
|
|
510
|
+
self.resize_checkbox.check()
|
|
511
|
+
self.resize_input_h.value = value[0]
|
|
512
|
+
self.resize_input_w.value = value[1]
|
|
513
|
+
|
|
514
|
+
@property
|
|
515
|
+
def copy_annotations(self) -> bool:
|
|
516
|
+
return self.copy_annotations_checkbox.is_checked()
|
|
517
|
+
|
|
518
|
+
@copy_annotations.setter
|
|
519
|
+
def copy_annotations(self, value: bool):
|
|
520
|
+
if value:
|
|
521
|
+
self.copy_annotations_checkbox.check()
|
|
522
|
+
else:
|
|
523
|
+
self.copy_annotations_checkbox.uncheck()
|
|
524
|
+
|
|
525
|
+
@property
|
|
526
|
+
def selected_output_project_id(self) -> int:
|
|
527
|
+
if not self.output_project_selectable:
|
|
528
|
+
return self.output_project_id
|
|
529
|
+
if self.output_mode_radio.get_value() == "create":
|
|
530
|
+
return None
|
|
531
|
+
return self.output_project_select.get_selected_id()
|
|
532
|
+
|
|
533
|
+
@selected_output_project_id.setter
|
|
534
|
+
def selected_output_project_id(self, value: int):
|
|
535
|
+
if not self.output_project_selectable:
|
|
536
|
+
raise ValueError("Output project is not selectable.")
|
|
537
|
+
self.output_project_select.set_project_id(value)
|
|
538
|
+
if value is not None:
|
|
539
|
+
self.output_mode_radio.set_value("merge")
|
|
540
|
+
else:
|
|
541
|
+
self.output_mode_radio.set_value("create")
|
|
542
|
+
|
|
543
|
+
def get_json_data(self) -> dict:
|
|
544
|
+
return {}
|
|
545
|
+
|
|
546
|
+
def get_json_state(self) -> dict:
|
|
547
|
+
return {}
|
|
548
|
+
|
|
549
|
+
def to_html(self):
|
|
550
|
+
return self.content.to_html()
|