supervisely 6.73.359__py3-none-any.whl → 6.73.361__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/app/widgets/project_thumbnail/project_thumbnail.py +3 -2
- supervisely/app/widgets/random_splits_table/random_splits_table.py +13 -2
- supervisely/app/widgets/random_splits_table/template.html +2 -2
- supervisely/app/widgets/select_app_session/select_app_session.py +3 -0
- supervisely/app/widgets/train_val_splits/train_val_splits.py +36 -24
- supervisely/nn/inference/inference.py +1 -2
- supervisely/nn/training/gui/gui.py +551 -186
- supervisely/nn/training/gui/input_selector.py +1 -1
- supervisely/nn/training/gui/model_selector.py +26 -6
- supervisely/nn/training/gui/tags_selector.py +105 -0
- supervisely/nn/training/gui/train_val_splits_selector.py +80 -18
- supervisely/nn/training/train_app.py +139 -43
- supervisely/project/download.py +24 -6
- supervisely/project/video_project.py +35 -6
- {supervisely-6.73.359.dist-info → supervisely-6.73.361.dist-info}/METADATA +80 -59
- {supervisely-6.73.359.dist-info → supervisely-6.73.361.dist-info}/RECORD +20 -19
- {supervisely-6.73.359.dist-info → supervisely-6.73.361.dist-info}/LICENSE +0 -0
- {supervisely-6.73.359.dist-info → supervisely-6.73.361.dist-info}/WHEEL +0 -0
- {supervisely-6.73.359.dist-info → supervisely-6.73.361.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.359.dist-info → supervisely-6.73.361.dist-info}/top_level.txt +0 -0
|
@@ -31,6 +31,8 @@ from supervisely import (
|
|
|
31
31
|
Project,
|
|
32
32
|
ProjectInfo,
|
|
33
33
|
ProjectMeta,
|
|
34
|
+
ProjectType,
|
|
35
|
+
VideoProject,
|
|
34
36
|
WorkflowMeta,
|
|
35
37
|
WorkflowSettings,
|
|
36
38
|
batched,
|
|
@@ -152,8 +154,8 @@ class TrainApp:
|
|
|
152
154
|
self.sly_project = None
|
|
153
155
|
# -------------------------- #
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
self.
|
|
157
|
+
self._train_split = None
|
|
158
|
+
self._val_split = None
|
|
157
159
|
# -------------------------- #
|
|
158
160
|
|
|
159
161
|
# Input
|
|
@@ -376,6 +378,8 @@ class TrainApp:
|
|
|
376
378
|
:return: List of selected classes names.
|
|
377
379
|
:rtype: List[str]
|
|
378
380
|
"""
|
|
381
|
+
if not self._has_classes_selector:
|
|
382
|
+
return []
|
|
379
383
|
selected_classes = set(self.gui.classes_selector.get_selected_classes())
|
|
380
384
|
# remap classes with project_meta order
|
|
381
385
|
return [x for x in self.project_meta.obj_classes.keys() if x in selected_classes]
|
|
@@ -388,8 +392,29 @@ class TrainApp:
|
|
|
388
392
|
:return: Number of selected classes.
|
|
389
393
|
:rtype: int
|
|
390
394
|
"""
|
|
395
|
+
if not self._has_classes_selector:
|
|
396
|
+
return 0
|
|
391
397
|
return len(self.gui.classes_selector.get_selected_classes())
|
|
392
398
|
|
|
399
|
+
@property
|
|
400
|
+
def tags(self) -> List[str]:
|
|
401
|
+
"""
|
|
402
|
+
Returns the selected tags for training.
|
|
403
|
+
"""
|
|
404
|
+
if not self._has_tags_selector:
|
|
405
|
+
return []
|
|
406
|
+
selected_tags = set(self.gui.tags_selector.get_selected_tags())
|
|
407
|
+
return [x for x in self.project_meta.tag_metas.keys() if x in selected_tags]
|
|
408
|
+
|
|
409
|
+
@property
|
|
410
|
+
def num_tags(self) -> int:
|
|
411
|
+
"""
|
|
412
|
+
Returns the number of selected tags for training.
|
|
413
|
+
"""
|
|
414
|
+
if not self._has_tags_selector:
|
|
415
|
+
return 0
|
|
416
|
+
return len(self.gui.tags_selector.get_selected_tags())
|
|
417
|
+
|
|
393
418
|
# Hyperparameters
|
|
394
419
|
@property
|
|
395
420
|
def hyperparameters(self) -> Dict[str, Any]:
|
|
@@ -448,6 +473,24 @@ class TrainApp:
|
|
|
448
473
|
# Output
|
|
449
474
|
# ----------------------------------------- #
|
|
450
475
|
|
|
476
|
+
# Helper properties
|
|
477
|
+
@property
|
|
478
|
+
def _has_splits_selector(self) -> bool:
|
|
479
|
+
"""Return True if Train/Val splits selector is enabled in GUI."""
|
|
480
|
+
return self.gui.train_val_splits_selector is not None
|
|
481
|
+
|
|
482
|
+
@property
|
|
483
|
+
def _has_classes_selector(self) -> bool:
|
|
484
|
+
"""Return True if Classes selector is enabled in GUI."""
|
|
485
|
+
return self.gui.classes_selector is not None
|
|
486
|
+
|
|
487
|
+
@property
|
|
488
|
+
def _has_tags_selector(self) -> bool:
|
|
489
|
+
"""Return True if Tags selector is enabled in GUI."""
|
|
490
|
+
return self.gui.tags_selector is not None
|
|
491
|
+
|
|
492
|
+
# ----------------------------------------- #
|
|
493
|
+
|
|
451
494
|
# Wrappers
|
|
452
495
|
@property
|
|
453
496
|
def start(self):
|
|
@@ -546,6 +589,7 @@ class TrainApp:
|
|
|
546
589
|
try:
|
|
547
590
|
# Convert GT project
|
|
548
591
|
gt_project_id, bm_splits_data = None, train_splits_data
|
|
592
|
+
# @TODO: check with anyshape classes
|
|
549
593
|
if self._app_options.get("auto_convert_classes", True):
|
|
550
594
|
if self.gui.need_convert_shapes_for_bm:
|
|
551
595
|
self._set_text_status("convert_gt_project")
|
|
@@ -644,9 +688,12 @@ class TrainApp:
|
|
|
644
688
|
:return: Application state.
|
|
645
689
|
:rtype: dict
|
|
646
690
|
"""
|
|
691
|
+
# Prepare optional sections depending on what selectors are enabled in GUI
|
|
647
692
|
train_val_splits = self._get_train_val_splits_for_app_state()
|
|
648
|
-
|
|
693
|
+
classes = self.classes
|
|
694
|
+
tags = self.tags
|
|
649
695
|
|
|
696
|
+
model = self._get_model_config_for_app_state(experiment_info)
|
|
650
697
|
options = {
|
|
651
698
|
"model_benchmark": {
|
|
652
699
|
"enable": self.gui.hyperparameters_selector.get_model_benchmark_checkbox_value(),
|
|
@@ -656,12 +703,14 @@ class TrainApp:
|
|
|
656
703
|
}
|
|
657
704
|
|
|
658
705
|
app_state = {
|
|
659
|
-
"train_val_split": train_val_splits,
|
|
660
|
-
"classes": self.classes,
|
|
661
706
|
"model": model,
|
|
662
707
|
"hyperparameters": self.hyperparameters_yaml,
|
|
663
708
|
"options": options,
|
|
664
709
|
}
|
|
710
|
+
|
|
711
|
+
app_state["train_val_split"] = train_val_splits
|
|
712
|
+
app_state["classes"] = classes
|
|
713
|
+
app_state["tags"] = tags
|
|
665
714
|
return app_state
|
|
666
715
|
|
|
667
716
|
def load_app_state(self, app_state: dict) -> None:
|
|
@@ -675,12 +724,13 @@ class TrainApp:
|
|
|
675
724
|
|
|
676
725
|
app_state = {
|
|
677
726
|
"input": {"project_id": 55555},
|
|
678
|
-
"
|
|
727
|
+
"train_val_split": {
|
|
679
728
|
"method": "random",
|
|
680
729
|
"split": "train",
|
|
681
730
|
"percent": 90
|
|
682
731
|
},
|
|
683
732
|
"classes": ["apple"],
|
|
733
|
+
"tags": ["green", "red"],
|
|
684
734
|
"model": {
|
|
685
735
|
"source": "Pretrained models",
|
|
686
736
|
"model_name": "rtdetr_r50vd_coco_objects365"
|
|
@@ -786,25 +836,44 @@ class TrainApp:
|
|
|
786
836
|
|
|
787
837
|
# Preprocess
|
|
788
838
|
# Download Project
|
|
839
|
+
def _read_project(self, remove_unselected_classes: bool = True) -> None:
|
|
840
|
+
"""
|
|
841
|
+
Reads the project data from Supervisely.
|
|
842
|
+
|
|
843
|
+
:param remove_unselected_classes: Whether to remove unselected classes from the project.
|
|
844
|
+
:type remove_unselected_classes: bool
|
|
845
|
+
"""
|
|
846
|
+
if self.project_info.type == ProjectType.IMAGES.value:
|
|
847
|
+
self.sly_project = Project(self.project_dir, OpenMode.READ)
|
|
848
|
+
if remove_unselected_classes:
|
|
849
|
+
self.sly_project.remove_classes_except(self.project_dir, self.classes, True)
|
|
850
|
+
elif self.project_info.type == ProjectType.VIDEOS.value:
|
|
851
|
+
self.sly_project = VideoProject(self.project_dir, OpenMode.READ)
|
|
852
|
+
else:
|
|
853
|
+
raise ValueError(
|
|
854
|
+
f"Unsupported project type: {self.project_info.type}. Only images and videos are supported."
|
|
855
|
+
)
|
|
856
|
+
|
|
789
857
|
def _download_project(self) -> None:
|
|
790
858
|
"""
|
|
791
859
|
Downloads the project data from Supervisely.
|
|
792
860
|
If the cache is enabled, it will attempt to retrieve the project from the cache.
|
|
793
861
|
"""
|
|
794
862
|
dataset_infos = [dataset for _, dataset in self._api.dataset.tree(self.project_id)]
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
863
|
+
if self.gui.train_val_splits_selector is not None:
|
|
864
|
+
if self.gui.train_val_splits_selector.get_split_method() == "Based on datasets":
|
|
865
|
+
selected_ds_ids = (
|
|
866
|
+
self.gui.train_val_splits_selector.get_train_dataset_ids()
|
|
867
|
+
+ self.gui.train_val_splits_selector.get_val_dataset_ids()
|
|
868
|
+
)
|
|
869
|
+
dataset_infos = [
|
|
870
|
+
ds_info for ds_info in dataset_infos if ds_info.id in selected_ds_ids
|
|
871
|
+
]
|
|
802
872
|
|
|
803
873
|
total_images = sum(ds_info.images_count for ds_info in dataset_infos)
|
|
804
|
-
if not self.gui.input_selector.get_cache_value()
|
|
874
|
+
if not self.gui.input_selector.get_cache_value():
|
|
805
875
|
self._download_no_cache(dataset_infos, total_images)
|
|
806
|
-
self.
|
|
807
|
-
self.sly_project.remove_classes_except(self.project_dir, self.classes, True)
|
|
876
|
+
self._read_project()
|
|
808
877
|
return
|
|
809
878
|
|
|
810
879
|
try:
|
|
@@ -818,8 +887,7 @@ class TrainApp:
|
|
|
818
887
|
sly_fs.clean_dir(self.project_dir)
|
|
819
888
|
self._download_no_cache(dataset_infos, total_images)
|
|
820
889
|
finally:
|
|
821
|
-
self.
|
|
822
|
-
self.sly_project.remove_classes_except(self.project_dir, self.classes, True)
|
|
890
|
+
self._read_project()
|
|
823
891
|
logger.info(f"Project downloaded successfully to: '{self.project_dir}'")
|
|
824
892
|
|
|
825
893
|
def _download_no_cache(self, dataset_infos: List[DatasetInfo], total_images: int) -> None:
|
|
@@ -919,6 +987,15 @@ class TrainApp:
|
|
|
919
987
|
All images and annotations will be renamed and moved to the appropriate directories.
|
|
920
988
|
Assigns self.sly_project to the new project, which contains only 2 datasets: train and val.
|
|
921
989
|
"""
|
|
990
|
+
if not self._has_splits_selector:
|
|
991
|
+
# Splits disabled in options, init empty splits
|
|
992
|
+
self.train_dataset_dir = None
|
|
993
|
+
self.val_dataset_dir = None
|
|
994
|
+
self._train_val_split_file = None
|
|
995
|
+
self._train_split = []
|
|
996
|
+
self._val_split = []
|
|
997
|
+
return
|
|
998
|
+
|
|
922
999
|
# Load splits
|
|
923
1000
|
self.gui.train_val_splits_selector.set_sly_project(self.sly_project)
|
|
924
1001
|
self._train_split, self._val_split = (
|
|
@@ -1005,7 +1082,7 @@ class TrainApp:
|
|
|
1005
1082
|
|
|
1006
1083
|
# Clean up temporary directory
|
|
1007
1084
|
sly_fs.remove_dir(project_split_path)
|
|
1008
|
-
self.
|
|
1085
|
+
self._read_project(False)
|
|
1009
1086
|
|
|
1010
1087
|
# ----------------------------------------- #
|
|
1011
1088
|
|
|
@@ -1272,6 +1349,9 @@ class TrainApp:
|
|
|
1272
1349
|
train_dataset_ids = None
|
|
1273
1350
|
train_images_ids = None
|
|
1274
1351
|
|
|
1352
|
+
if not self._has_splits_selector:
|
|
1353
|
+
return {} # splits disabled in options
|
|
1354
|
+
|
|
1275
1355
|
split_method = self.gui.train_val_splits_selector.get_split_method()
|
|
1276
1356
|
train_set, val_set = self._train_split, self._val_split
|
|
1277
1357
|
if split_method == "Based on datasets":
|
|
@@ -1482,6 +1562,9 @@ class TrainApp:
|
|
|
1482
1562
|
:param remote_dir: Remote directory path.
|
|
1483
1563
|
:type remote_dir: str
|
|
1484
1564
|
"""
|
|
1565
|
+
if not self._has_splits_selector:
|
|
1566
|
+
return # splits disabled in options
|
|
1567
|
+
|
|
1485
1568
|
local_train_val_split_path = join(self.output_dir, self._train_val_split_file)
|
|
1486
1569
|
remote_train_val_split_path = join(remote_dir, self._train_val_split_file)
|
|
1487
1570
|
|
|
@@ -1575,9 +1658,6 @@ class TrainApp:
|
|
|
1575
1658
|
"export": export_weights,
|
|
1576
1659
|
"app_state": self._app_state_file,
|
|
1577
1660
|
"model_meta": self._model_meta_file,
|
|
1578
|
-
"train_val_split": self._train_val_split_file,
|
|
1579
|
-
"train_size": len(self._train_split),
|
|
1580
|
-
"val_size": len(self._val_split),
|
|
1581
1661
|
"hyperparameters": self._hyperparameters_file,
|
|
1582
1662
|
"artifacts_dir": remote_dir,
|
|
1583
1663
|
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
@@ -1587,6 +1667,11 @@ class TrainApp:
|
|
|
1587
1667
|
"logs": {"type": "tensorboard", "link": f"{remote_dir}logs/"},
|
|
1588
1668
|
}
|
|
1589
1669
|
|
|
1670
|
+
if self._has_splits_selector:
|
|
1671
|
+
experiment_info["train_val_split"] = self._train_val_split_file
|
|
1672
|
+
experiment_info["train_size"] = len(self._train_split)
|
|
1673
|
+
experiment_info["val_size"] = len(self._val_split)
|
|
1674
|
+
|
|
1590
1675
|
remote_checkpoints_dir = join(remote_dir, self._remote_checkpoints_dir_name)
|
|
1591
1676
|
checkpoint_files = self._api.file.list(
|
|
1592
1677
|
self.team_id, remote_checkpoints_dir, return_type="fileinfo"
|
|
@@ -1703,6 +1788,9 @@ class TrainApp:
|
|
|
1703
1788
|
:return: Train and val splits information based on selected split method.
|
|
1704
1789
|
:rtype: dict
|
|
1705
1790
|
"""
|
|
1791
|
+
if not self._has_splits_selector:
|
|
1792
|
+
return {} # splits disabled in options
|
|
1793
|
+
|
|
1706
1794
|
split_method = self.gui.train_val_splits_selector.get_split_method()
|
|
1707
1795
|
train_val_splits = {"method": split_method.lower()}
|
|
1708
1796
|
if split_method == "Random":
|
|
@@ -2067,20 +2155,25 @@ class TrainApp:
|
|
|
2067
2155
|
else:
|
|
2068
2156
|
raise ValueError(f"Task type: '{task_type}' is not supported for Model Benchmark")
|
|
2069
2157
|
|
|
2070
|
-
if self.
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2158
|
+
if self._has_splits_selector:
|
|
2159
|
+
if self.gui.train_val_splits_selector.get_split_method() == "Based on datasets":
|
|
2160
|
+
train_info = {
|
|
2161
|
+
"app_session_id": self.task_id,
|
|
2162
|
+
"train_dataset_ids": train_dataset_ids,
|
|
2163
|
+
"train_images_ids": None,
|
|
2164
|
+
"images_count": len(self._train_split),
|
|
2165
|
+
}
|
|
2166
|
+
else:
|
|
2167
|
+
train_info = {
|
|
2168
|
+
"app_session_id": self.task_id,
|
|
2169
|
+
"train_dataset_ids": None,
|
|
2170
|
+
"train_images_ids": train_images_ids,
|
|
2171
|
+
"images_count": len(self._train_split),
|
|
2172
|
+
}
|
|
2077
2173
|
else:
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
"train_images_ids": train_images_ids,
|
|
2082
|
-
"images_count": len(self._train_split),
|
|
2083
|
-
}
|
|
2174
|
+
# @TODO: Add train info for apps without splits
|
|
2175
|
+
train_info = None
|
|
2176
|
+
|
|
2084
2177
|
bm.train_info = train_info
|
|
2085
2178
|
|
|
2086
2179
|
# 2. Run inference
|
|
@@ -2144,14 +2237,17 @@ class TrainApp:
|
|
|
2144
2237
|
"""
|
|
2145
2238
|
Adds the input data to the workflow.
|
|
2146
2239
|
"""
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
self.
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2240
|
+
if self.project_info.type == ProjectType.IMAGES.value:
|
|
2241
|
+
try:
|
|
2242
|
+
project_version_id = self._api.project.version.create(
|
|
2243
|
+
self.project_info,
|
|
2244
|
+
self._app_name,
|
|
2245
|
+
f"This backup was created automatically by Supervisely before the {self._app_name} task with ID: {self._api.task_id}",
|
|
2246
|
+
)
|
|
2247
|
+
except Exception as e:
|
|
2248
|
+
logger.warning(f"Failed to create a project version: {repr(e)}")
|
|
2249
|
+
project_version_id = None
|
|
2250
|
+
else:
|
|
2155
2251
|
project_version_id = None
|
|
2156
2252
|
|
|
2157
2253
|
try:
|
supervisely/project/download.py
CHANGED
|
@@ -409,12 +409,22 @@ def _project_meta_changed(meta1: ProjectMeta, meta2: ProjectMeta) -> bool:
|
|
|
409
409
|
return False
|
|
410
410
|
|
|
411
411
|
|
|
412
|
+
def _get_ds_full_name(
|
|
413
|
+
dataset_info: DatasetInfo, all_ds_infos: List[DatasetInfo], suffix: str = ""
|
|
414
|
+
) -> str:
|
|
415
|
+
if dataset_info.parent_id is None:
|
|
416
|
+
return dataset_info.name + suffix
|
|
417
|
+
parent = next((ds_info for ds_info in all_ds_infos if ds_info.id == dataset_info.parent_id))
|
|
418
|
+
return _get_ds_full_name(parent, all_ds_infos, "/" + dataset_info.name)
|
|
419
|
+
|
|
420
|
+
|
|
412
421
|
def _validate_dataset(
|
|
413
422
|
api: Api,
|
|
414
423
|
project_id: int,
|
|
415
424
|
project_type: str,
|
|
416
425
|
project_meta: ProjectMeta,
|
|
417
426
|
dataset_info: DatasetInfo,
|
|
427
|
+
all_ds_infos: List[DatasetInfo] = None,
|
|
418
428
|
):
|
|
419
429
|
try:
|
|
420
430
|
project_class = get_project_class(project_type)
|
|
@@ -430,10 +440,12 @@ def _validate_dataset(
|
|
|
430
440
|
except:
|
|
431
441
|
logger.debug("Validating dataset failed. Unable to download items infos.", exc_info=True)
|
|
432
442
|
return False
|
|
443
|
+
if all_ds_infos is None:
|
|
444
|
+
all_ds_infos = api.dataset.get_list(project_id, recursive=True)
|
|
433
445
|
project_meta_changed = _project_meta_changed(project_meta, project.meta)
|
|
434
446
|
for dataset in project.datasets:
|
|
435
447
|
dataset: Dataset
|
|
436
|
-
if dataset.name
|
|
448
|
+
if dataset.name == _get_ds_full_name(dataset_info, all_ds_infos):
|
|
437
449
|
diff = set(items_infos_dict.keys()).difference(set(dataset.get_items_names()))
|
|
438
450
|
if diff:
|
|
439
451
|
logger.debug(
|
|
@@ -481,7 +493,11 @@ def _validate_dataset(
|
|
|
481
493
|
|
|
482
494
|
|
|
483
495
|
def _validate(
|
|
484
|
-
api: Api,
|
|
496
|
+
api: Api,
|
|
497
|
+
project_info: ProjectInfo,
|
|
498
|
+
project_meta: ProjectMeta,
|
|
499
|
+
dataset_infos: List[DatasetInfo],
|
|
500
|
+
all_ds_infos: List[DatasetInfo] = None,
|
|
485
501
|
):
|
|
486
502
|
project_id = project_info.id
|
|
487
503
|
to_download, cached = _split_by_cache(
|
|
@@ -498,6 +514,7 @@ def _validate(
|
|
|
498
514
|
project_info.type,
|
|
499
515
|
project_meta,
|
|
500
516
|
dataset_info,
|
|
517
|
+
all_ds_infos,
|
|
501
518
|
):
|
|
502
519
|
to_download.add(ds_path)
|
|
503
520
|
cached.remove(ds_path)
|
|
@@ -520,7 +537,7 @@ def _add_save_items_infos_to_kwargs(kwargs: dict, project_type: str):
|
|
|
520
537
|
|
|
521
538
|
|
|
522
539
|
def _add_resume_download_to_kwargs(kwargs: dict, project_type: str):
|
|
523
|
-
supported_force_projects = (str(ProjectType.IMAGES),)
|
|
540
|
+
supported_force_projects = (str(ProjectType.IMAGES), (str(ProjectType.VIDEOS)))
|
|
524
541
|
if project_type in supported_force_projects:
|
|
525
542
|
kwargs["resume_download"] = True
|
|
526
543
|
return kwargs
|
|
@@ -592,13 +609,14 @@ def download_to_cache(
|
|
|
592
609
|
project_meta = ProjectMeta.from_json(api.project.get_meta(project_id))
|
|
593
610
|
if dataset_infos is not None and dataset_ids is not None:
|
|
594
611
|
raise ValueError("dataset_infos and dataset_ids cannot be specified at the same time")
|
|
612
|
+
all_ds_infos = api.dataset.get_list(project_id, recursive=True)
|
|
595
613
|
if dataset_infos is None:
|
|
596
614
|
if dataset_ids is None:
|
|
597
|
-
dataset_infos =
|
|
615
|
+
dataset_infos = all_ds_infos
|
|
598
616
|
else:
|
|
599
|
-
dataset_infos = [
|
|
617
|
+
dataset_infos = [ds_info for ds_info in all_ds_infos if ds_info.id in dataset_ids]
|
|
600
618
|
path_to_info = {_get_dataset_path(api, dataset_infos, info.id): info for info in dataset_infos}
|
|
601
|
-
to_download, cached = _validate(api, project_info, project_meta, dataset_infos)
|
|
619
|
+
to_download, cached = _validate(api, project_info, project_meta, dataset_infos, all_ds_infos)
|
|
602
620
|
if progress_cb is not None:
|
|
603
621
|
cached_items_n = sum(path_to_info[ds_path].items_count for ds_path in cached)
|
|
604
622
|
progress_cb(cached_items_n)
|
|
@@ -16,7 +16,7 @@ from supervisely.api.dataset_api import DatasetInfo
|
|
|
16
16
|
from supervisely.api.module_api import ApiField
|
|
17
17
|
from supervisely.api.video.video_api import VideoInfo
|
|
18
18
|
from supervisely.collection.key_indexed_collection import KeyIndexedCollection
|
|
19
|
-
from supervisely.io.fs import mkdir, touch, touch_async
|
|
19
|
+
from supervisely.io.fs import clean_dir, mkdir, touch, touch_async
|
|
20
20
|
from supervisely.io.json import dump_json_file, dump_json_file_async, load_json_file
|
|
21
21
|
from supervisely.project.project import Dataset, OpenMode, Project
|
|
22
22
|
from supervisely.project.project import read_single_project as read_project_wrapper
|
|
@@ -1056,6 +1056,7 @@ class VideoProject(Project):
|
|
|
1056
1056
|
save_video_info: bool = False,
|
|
1057
1057
|
log_progress: bool = True,
|
|
1058
1058
|
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1059
|
+
resume_download: Optional[bool] = False,
|
|
1059
1060
|
) -> None:
|
|
1060
1061
|
"""
|
|
1061
1062
|
Download video project from Supervisely to the given directory.
|
|
@@ -1109,6 +1110,7 @@ class VideoProject(Project):
|
|
|
1109
1110
|
save_video_info=save_video_info,
|
|
1110
1111
|
log_progress=log_progress,
|
|
1111
1112
|
progress_cb=progress_cb,
|
|
1113
|
+
resume_download=resume_download,
|
|
1112
1114
|
)
|
|
1113
1115
|
|
|
1114
1116
|
@staticmethod
|
|
@@ -1182,6 +1184,7 @@ class VideoProject(Project):
|
|
|
1182
1184
|
log_progress: bool = True,
|
|
1183
1185
|
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1184
1186
|
include_custom_data: bool = False,
|
|
1187
|
+
resume_download: Optional[bool] = False,
|
|
1185
1188
|
**kwargs,
|
|
1186
1189
|
) -> None:
|
|
1187
1190
|
"""
|
|
@@ -1238,6 +1241,7 @@ class VideoProject(Project):
|
|
|
1238
1241
|
log_progress=log_progress,
|
|
1239
1242
|
progress_cb=progress_cb,
|
|
1240
1243
|
include_custom_data=include_custom_data,
|
|
1244
|
+
resume_download=resume_download,
|
|
1241
1245
|
**kwargs,
|
|
1242
1246
|
)
|
|
1243
1247
|
|
|
@@ -1252,6 +1256,7 @@ def download_video_project(
|
|
|
1252
1256
|
log_progress: bool = True,
|
|
1253
1257
|
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1254
1258
|
include_custom_data: Optional[bool] = False,
|
|
1259
|
+
resume_download: Optional[bool] = False,
|
|
1255
1260
|
) -> None:
|
|
1256
1261
|
"""
|
|
1257
1262
|
Download video project to the local directory.
|
|
@@ -1312,9 +1317,22 @@ def download_video_project(
|
|
|
1312
1317
|
LOG_BATCH_SIZE = 1
|
|
1313
1318
|
|
|
1314
1319
|
key_id_map = KeyIdMap()
|
|
1315
|
-
|
|
1316
|
-
meta = ProjectMeta.from_json(api.project.get_meta(project_id))
|
|
1320
|
+
|
|
1321
|
+
meta = ProjectMeta.from_json(api.project.get_meta(project_id, with_settings=True))
|
|
1322
|
+
if os.path.exists(dest_dir) and resume_download:
|
|
1323
|
+
dump_json_file(meta.to_json(), os.path.join(dest_dir, "meta.json"))
|
|
1324
|
+
try:
|
|
1325
|
+
project_fs = VideoProject(dest_dir, OpenMode.READ)
|
|
1326
|
+
except RuntimeError as e:
|
|
1327
|
+
if "Project is empty" in str(e):
|
|
1328
|
+
clean_dir(dest_dir)
|
|
1329
|
+
project_fs = None
|
|
1330
|
+
else:
|
|
1331
|
+
raise
|
|
1332
|
+
if project_fs is None:
|
|
1333
|
+
project_fs = VideoProject(dest_dir, OpenMode.CREATE)
|
|
1317
1334
|
project_fs.set_meta(meta)
|
|
1335
|
+
|
|
1318
1336
|
if progress_cb is not None:
|
|
1319
1337
|
log_progress = False
|
|
1320
1338
|
|
|
@@ -1549,6 +1567,7 @@ async def download_video_project_async(
|
|
|
1549
1567
|
log_progress: bool = True,
|
|
1550
1568
|
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1551
1569
|
include_custom_data: Optional[bool] = False,
|
|
1570
|
+
resume_download: Optional[bool] = False,
|
|
1552
1571
|
**kwargs,
|
|
1553
1572
|
) -> None:
|
|
1554
1573
|
"""
|
|
@@ -1603,9 +1622,19 @@ async def download_video_project_async(
|
|
|
1603
1622
|
|
|
1604
1623
|
key_id_map = KeyIdMap()
|
|
1605
1624
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1625
|
+
meta = ProjectMeta.from_json(api.project.get_meta(project_id, with_settings=True))
|
|
1626
|
+
if os.path.exists(dest_dir) and resume_download:
|
|
1627
|
+
dump_json_file(meta.to_json(), os.path.join(dest_dir, "meta.json"))
|
|
1628
|
+
try:
|
|
1629
|
+
project_fs = VideoProject(dest_dir, OpenMode.READ)
|
|
1630
|
+
except RuntimeError as e:
|
|
1631
|
+
if "Project is empty" in str(e):
|
|
1632
|
+
clean_dir(dest_dir)
|
|
1633
|
+
project_fs = None
|
|
1634
|
+
else:
|
|
1635
|
+
raise
|
|
1636
|
+
if project_fs is None:
|
|
1637
|
+
project_fs = VideoProject(dest_dir, OpenMode.CREATE)
|
|
1609
1638
|
project_fs.set_meta(meta)
|
|
1610
1639
|
|
|
1611
1640
|
if progress_cb is not None:
|