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 CHANGED
@@ -133,6 +133,7 @@ from supervisely.cli import _handle_creds_error_to_console
133
133
  from supervisely._utils import (
134
134
  rand_str,
135
135
  batched,
136
+ batched_iter,
136
137
  get_bytes_hash,
137
138
  generate_names,
138
139
  ENTERPRISE,
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 supervisely.app.widgets import Widget
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
- {% if widget._center_content == true %}
3
- style="display: flex; gap: {{{widget._gap}}}px; justify-content: center;"
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
- {{{w}}}
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()