supervisely 6.73.226__py3-none-any.whl → 6.73.228__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 +2 -2
- supervisely/_utils.py +78 -1
- supervisely/api/annotation_api.py +184 -14
- supervisely/api/api.py +2 -2
- supervisely/api/dataset_api.py +90 -1
- supervisely/api/entity_annotation/figure_api.py +11 -2
- supervisely/api/file_api.py +144 -8
- supervisely/api/image_api.py +94 -13
- supervisely/api/pointcloud/pointcloud_api.py +4 -8
- supervisely/api/project_api.py +285 -1
- supervisely/api/video/video_annotation_api.py +45 -0
- supervisely/api/video/video_api.py +2 -4
- supervisely/api/volume/volume_api.py +2 -4
- supervisely/convert/base_converter.py +14 -10
- supervisely/io/fs.py +55 -8
- supervisely/io/json.py +32 -0
- supervisely/project/download.py +176 -64
- supervisely/project/project.py +676 -35
- supervisely/project/project_type.py +4 -1
- supervisely/project/video_project.py +293 -3
- {supervisely-6.73.226.dist-info → supervisely-6.73.228.dist-info}/METADATA +1 -1
- {supervisely-6.73.226.dist-info → supervisely-6.73.228.dist-info}/RECORD +26 -26
- {supervisely-6.73.226.dist-info → supervisely-6.73.228.dist-info}/LICENSE +0 -0
- {supervisely-6.73.226.dist-info → supervisely-6.73.228.dist-info}/WHEEL +0 -0
- {supervisely-6.73.226.dist-info → supervisely-6.73.228.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.226.dist-info → supervisely-6.73.228.dist-info}/top_level.txt +0 -0
supervisely/api/project_api.py
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
# docs
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import os
|
|
7
8
|
from collections import defaultdict
|
|
9
|
+
from copy import deepcopy
|
|
8
10
|
from typing import (
|
|
9
11
|
TYPE_CHECKING,
|
|
10
12
|
Any,
|
|
@@ -25,7 +27,13 @@ if TYPE_CHECKING:
|
|
|
25
27
|
from datetime import datetime, timedelta
|
|
26
28
|
|
|
27
29
|
from supervisely import logger
|
|
28
|
-
from supervisely._utils import
|
|
30
|
+
from supervisely._utils import (
|
|
31
|
+
abs_url,
|
|
32
|
+
compare_dicts,
|
|
33
|
+
compress_image_url,
|
|
34
|
+
get_unix_timestamp,
|
|
35
|
+
is_development,
|
|
36
|
+
)
|
|
29
37
|
from supervisely.annotation.annotation import TagCollection
|
|
30
38
|
from supervisely.annotation.obj_class import ObjClass
|
|
31
39
|
from supervisely.annotation.obj_class_collection import ObjClassCollection
|
|
@@ -36,6 +44,7 @@ from supervisely.api.module_api import (
|
|
|
36
44
|
RemoveableModuleApi,
|
|
37
45
|
UpdateableModule,
|
|
38
46
|
)
|
|
47
|
+
from supervisely.io.json import dump_json_file, load_json_file
|
|
39
48
|
from supervisely.project.project_meta import ProjectMeta
|
|
40
49
|
from supervisely.project.project_meta import ProjectMetaJsonFields as MetaJsonF
|
|
41
50
|
from supervisely.project.project_settings import (
|
|
@@ -43,6 +52,9 @@ from supervisely.project.project_settings import (
|
|
|
43
52
|
ProjectSettingsJsonFields,
|
|
44
53
|
)
|
|
45
54
|
from supervisely.project.project_type import (
|
|
55
|
+
_METADATA_SYSTEM_KEY,
|
|
56
|
+
_METADATA_TIMESTAMP_KEY,
|
|
57
|
+
_METADATA_VALIDATION_SCHEMA_KEY,
|
|
46
58
|
_MULTISPECTRAL_TAG_NAME,
|
|
47
59
|
_MULTIVIEW_TAG_NAME,
|
|
48
60
|
ProjectType,
|
|
@@ -969,6 +981,278 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
969
981
|
)
|
|
970
982
|
return response.json()
|
|
971
983
|
|
|
984
|
+
def get_custom_data(self, id: int) -> Dict[Any, Any]:
|
|
985
|
+
"""Returns custom data of the Project by ID.
|
|
986
|
+
Custom data is a dictionary that can be used to store any additional information.
|
|
987
|
+
|
|
988
|
+
:param id: Project ID in Supervisely.
|
|
989
|
+
:type id: int
|
|
990
|
+
:return: Custom data of the Project
|
|
991
|
+
:rtype: :class:`dict`
|
|
992
|
+
|
|
993
|
+
:Usage example:
|
|
994
|
+
|
|
995
|
+
.. code-block:: python
|
|
996
|
+
|
|
997
|
+
import supervisely as sly
|
|
998
|
+
|
|
999
|
+
api = sly.Api.from_env()
|
|
1000
|
+
|
|
1001
|
+
project_id = 123456
|
|
1002
|
+
|
|
1003
|
+
custom_data = api.project.get_custom_data(project_id)
|
|
1004
|
+
|
|
1005
|
+
print(custom_data) # Output: {'key': 'value'}
|
|
1006
|
+
"""
|
|
1007
|
+
return self.get_info_by_id(id).custom_data
|
|
1008
|
+
|
|
1009
|
+
def _get_system_custom_data(self, id: int) -> Dict[Any, Any]:
|
|
1010
|
+
"""Returns system custom data of the Project by ID.
|
|
1011
|
+
System custom data is just a part of custom data that is used to store system information
|
|
1012
|
+
and obtained by the key `_METADATA_SYSTEM_KEY`.
|
|
1013
|
+
|
|
1014
|
+
:param id: Project ID in Supervisely.
|
|
1015
|
+
:type id: int
|
|
1016
|
+
:return: System custom data of the Project
|
|
1017
|
+
:rtype: :class:`dict`
|
|
1018
|
+
|
|
1019
|
+
:Usage example:
|
|
1020
|
+
|
|
1021
|
+
.. code-block:: python
|
|
1022
|
+
|
|
1023
|
+
import supervisely as sly
|
|
1024
|
+
|
|
1025
|
+
api = sly.Api.from_env()
|
|
1026
|
+
|
|
1027
|
+
project_id = 123456
|
|
1028
|
+
|
|
1029
|
+
system_custom_data = api.project._get_system_custom_data(project_id)
|
|
1030
|
+
|
|
1031
|
+
print(system_custom_data)
|
|
1032
|
+
"""
|
|
1033
|
+
return self.get_info_by_id(id).custom_data.get(_METADATA_SYSTEM_KEY, {})
|
|
1034
|
+
|
|
1035
|
+
def get_validation_schema(self, id: int, use_caching: bool = False) -> Optional[Dict[Any, Any]]:
|
|
1036
|
+
"""Returns validation schema of the Project by ID.
|
|
1037
|
+
Validation schema is a dictionary that can be used to validate metadata of each entity in the project
|
|
1038
|
+
if corresnpoding schema is provided.
|
|
1039
|
+
If using caching, the schema will be loaded from the cache if available.
|
|
1040
|
+
Use cached version only in scenarios when the schema is not expected to change,
|
|
1041
|
+
otherwise it may lead to checks with outdated schema.
|
|
1042
|
+
|
|
1043
|
+
:param id: Project ID in Supervisely.
|
|
1044
|
+
:type id: int
|
|
1045
|
+
:param use_caching: If True, uses cached version of the schema if available.
|
|
1046
|
+
NOTE: This may lead to checks with outdated schema. Use with caution.
|
|
1047
|
+
And only in scenarios when the schema is not expected to change.
|
|
1048
|
+
:return: Validation schema of the Project
|
|
1049
|
+
:rtype: :class:`dict`
|
|
1050
|
+
|
|
1051
|
+
:Usage example:
|
|
1052
|
+
|
|
1053
|
+
.. code-block:: python
|
|
1054
|
+
|
|
1055
|
+
import supervisely as sly
|
|
1056
|
+
|
|
1057
|
+
api = sly.Api.from_env()
|
|
1058
|
+
|
|
1059
|
+
project_id = 123456
|
|
1060
|
+
|
|
1061
|
+
validation_schema = api.project.get_validation_schema(project_id)
|
|
1062
|
+
|
|
1063
|
+
print(validation_schema) # Output: {'key': 'Description of the field'}
|
|
1064
|
+
"""
|
|
1065
|
+
SCHEMA_DIFF_THRESHOLD = 60 * 60 # 1 hour
|
|
1066
|
+
json_cache_filename = os.path.join(os.getcwd(), f"{id}_validation_schema.json")
|
|
1067
|
+
|
|
1068
|
+
if use_caching:
|
|
1069
|
+
if os.path.isfile(json_cache_filename):
|
|
1070
|
+
try:
|
|
1071
|
+
schema = load_json_file(json_cache_filename)
|
|
1072
|
+
timestamp = schema.pop(_METADATA_TIMESTAMP_KEY, 0)
|
|
1073
|
+
|
|
1074
|
+
if get_unix_timestamp() - timestamp < SCHEMA_DIFF_THRESHOLD:
|
|
1075
|
+
return schema
|
|
1076
|
+
except RuntimeError:
|
|
1077
|
+
pass
|
|
1078
|
+
|
|
1079
|
+
schema = self._get_system_custom_data(id).get(_METADATA_VALIDATION_SCHEMA_KEY)
|
|
1080
|
+
if schema and use_caching:
|
|
1081
|
+
schema_with_timestamp = deepcopy(schema)
|
|
1082
|
+
schema_with_timestamp[_METADATA_TIMESTAMP_KEY] = get_unix_timestamp()
|
|
1083
|
+
dump_json_file(schema_with_timestamp, json_cache_filename)
|
|
1084
|
+
|
|
1085
|
+
return schema
|
|
1086
|
+
|
|
1087
|
+
def _edit_validation_schema(
|
|
1088
|
+
self, id: int, schema: Optional[Dict[Any, Any]] = None
|
|
1089
|
+
) -> Dict[Any, Any]:
|
|
1090
|
+
"""Edits validation schema of the Project by ID.
|
|
1091
|
+
Do not use this method directly, use `set_validation_schema` or `remove_validation_schema` instead.
|
|
1092
|
+
|
|
1093
|
+
:param id: Project ID in Supervisely.
|
|
1094
|
+
:type id: int
|
|
1095
|
+
:param schema: Validation schema to set. If None, removes validation schema.
|
|
1096
|
+
:type schema: dict, optional
|
|
1097
|
+
:return: Project information in dict format
|
|
1098
|
+
:rtype: :class:`dict`
|
|
1099
|
+
|
|
1100
|
+
:Usage example:
|
|
1101
|
+
|
|
1102
|
+
.. code-block:: python
|
|
1103
|
+
|
|
1104
|
+
import supervisely as sly
|
|
1105
|
+
|
|
1106
|
+
api = sly.Api.from_env()
|
|
1107
|
+
|
|
1108
|
+
project_id = 123456
|
|
1109
|
+
|
|
1110
|
+
schema = {'key': 'Description of the field'}
|
|
1111
|
+
|
|
1112
|
+
api.project._edit_validation_schema(project_id, schema) #Set new validation schema.
|
|
1113
|
+
api.project._edit_validation_schema(project_id) #Remove validation schema.
|
|
1114
|
+
"""
|
|
1115
|
+
custom_data = self.get_custom_data(id)
|
|
1116
|
+
system_data = custom_data.setdefault(_METADATA_SYSTEM_KEY, {})
|
|
1117
|
+
|
|
1118
|
+
if not schema:
|
|
1119
|
+
system_data.pop(_METADATA_VALIDATION_SCHEMA_KEY, None)
|
|
1120
|
+
else:
|
|
1121
|
+
system_data[_METADATA_VALIDATION_SCHEMA_KEY] = schema
|
|
1122
|
+
return self.update_custom_data(id, custom_data)
|
|
1123
|
+
|
|
1124
|
+
def set_validation_schema(self, id: int, schema: Dict[Any, Any]) -> Dict[Any, Any]:
|
|
1125
|
+
"""Sets validation schema of the Project by ID.
|
|
1126
|
+
NOTE: This method will overwrite existing validation schema. To extend existing schema,
|
|
1127
|
+
use `get_validation_schema` first to get current schema, then update it and use this method to set new schema.
|
|
1128
|
+
|
|
1129
|
+
:param id: Project ID in Supervisely.
|
|
1130
|
+
:type id: int
|
|
1131
|
+
:param schema: Validation schema to set.
|
|
1132
|
+
:type schema: dict
|
|
1133
|
+
:return: Project information in dict format
|
|
1134
|
+
:rtype: :class:`dict`
|
|
1135
|
+
|
|
1136
|
+
:Usage example:
|
|
1137
|
+
|
|
1138
|
+
.. code-block:: python
|
|
1139
|
+
|
|
1140
|
+
import supervisely as sly
|
|
1141
|
+
|
|
1142
|
+
api = sly.Api.from_env()
|
|
1143
|
+
|
|
1144
|
+
project_id = 123456
|
|
1145
|
+
|
|
1146
|
+
schema = {'key': 'Description of the field'}
|
|
1147
|
+
|
|
1148
|
+
api.project.set_validation_schema(project_id, schema)
|
|
1149
|
+
"""
|
|
1150
|
+
return self._edit_validation_schema(id, schema)
|
|
1151
|
+
|
|
1152
|
+
def remove_validation_schema(self, id: int) -> Dict[Any, Any]:
|
|
1153
|
+
"""Removes validation schema of the Project by ID.
|
|
1154
|
+
|
|
1155
|
+
:param id: Project ID in Supervisely.
|
|
1156
|
+
:type id: int
|
|
1157
|
+
:return: Project information in dict format
|
|
1158
|
+
:rtype: :class:`dict`
|
|
1159
|
+
|
|
1160
|
+
:Usage example:
|
|
1161
|
+
|
|
1162
|
+
.. code-block:: python
|
|
1163
|
+
|
|
1164
|
+
import supervisely as sly
|
|
1165
|
+
|
|
1166
|
+
api = sly.Api.from_env()
|
|
1167
|
+
|
|
1168
|
+
project_id = 123456
|
|
1169
|
+
|
|
1170
|
+
api.project.remove_validation_schema(project_id)
|
|
1171
|
+
"""
|
|
1172
|
+
return self._edit_validation_schema(id)
|
|
1173
|
+
|
|
1174
|
+
def validate_entities_schema(
|
|
1175
|
+
self, id: int, strict: bool = False
|
|
1176
|
+
) -> List[Dict[str, Union[id, str, List[str], List[Any]]]]:
|
|
1177
|
+
"""Validates entities of the Project by ID using validation schema.
|
|
1178
|
+
Returns list of entities that do not match the schema.
|
|
1179
|
+
|
|
1180
|
+
Example of the returned list:
|
|
1181
|
+
|
|
1182
|
+
[
|
|
1183
|
+
{
|
|
1184
|
+
"entity_id": 123456,
|
|
1185
|
+
"entity_name": "image.jpg",
|
|
1186
|
+
"missing_fields": ["location"],
|
|
1187
|
+
"extra_fields": ["city.name"] <- Nested field (field "name" of the field "city")
|
|
1188
|
+
}
|
|
1189
|
+
]
|
|
1190
|
+
|
|
1191
|
+
:param id: Project ID in Supervisely.
|
|
1192
|
+
:type id: int
|
|
1193
|
+
:param strict: If strict is disabled, only checks if the entity has all the fields from the schema.
|
|
1194
|
+
Any extra fields in the entity will be ignored and will not be considered as an error.
|
|
1195
|
+
If strict is enabled, checks that the entity custom data is an exact match to the schema.
|
|
1196
|
+
:type strict: bool, optional
|
|
1197
|
+
:return: List of dictionaries with information about entities that do not match the schema.
|
|
1198
|
+
:rtype: :class:`List[Dict[str, Union[id, str, List[str], List[Any]]]`
|
|
1199
|
+
|
|
1200
|
+
:Usage example:
|
|
1201
|
+
|
|
1202
|
+
.. code-block:: python
|
|
1203
|
+
|
|
1204
|
+
import supervisely as sly
|
|
1205
|
+
|
|
1206
|
+
api = sly.Api.from_env()
|
|
1207
|
+
|
|
1208
|
+
project_id = 123456
|
|
1209
|
+
|
|
1210
|
+
incorrect_entities = api.project.validate_entities_schema(project_id)
|
|
1211
|
+
|
|
1212
|
+
for entity in incorrect_entities:
|
|
1213
|
+
print(entity.id, entity.name) # Output: 123456, 'image.jpg'
|
|
1214
|
+
"""
|
|
1215
|
+
validation_schema = self.get_validation_schema(id)
|
|
1216
|
+
if not validation_schema:
|
|
1217
|
+
raise ValueError("Validation schema is not set for this project.")
|
|
1218
|
+
|
|
1219
|
+
info = self.get_info_by_id(id)
|
|
1220
|
+
listing_method = {
|
|
1221
|
+
# Mapping of project type to listing method.
|
|
1222
|
+
ProjectType.IMAGES.value: self._api.image.get_list,
|
|
1223
|
+
}
|
|
1224
|
+
custom_data_properties = {
|
|
1225
|
+
# Mapping of project type to custom data property name.
|
|
1226
|
+
# Can be different for different project types.
|
|
1227
|
+
ProjectType.IMAGES.value: "meta"
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
listing_method = listing_method.get(info.type)
|
|
1231
|
+
custom_data_property = custom_data_properties.get(info.type)
|
|
1232
|
+
|
|
1233
|
+
if not listing_method or not custom_data_property:
|
|
1234
|
+
# TODO: Add support for other project types.
|
|
1235
|
+
raise NotImplementedError("Validation schema is not supported for this project type.")
|
|
1236
|
+
|
|
1237
|
+
entities = listing_method(project_id=id)
|
|
1238
|
+
incorrect_entities = []
|
|
1239
|
+
|
|
1240
|
+
for entity in entities:
|
|
1241
|
+
custom_data = getattr(entity, custom_data_property)
|
|
1242
|
+
missing_fields, extra_fields = compare_dicts(
|
|
1243
|
+
validation_schema, custom_data, strict=strict
|
|
1244
|
+
)
|
|
1245
|
+
if missing_fields or extra_fields:
|
|
1246
|
+
entry = {
|
|
1247
|
+
"entity_id": entity.id,
|
|
1248
|
+
"entity_name": entity.name,
|
|
1249
|
+
"missing_fields": missing_fields,
|
|
1250
|
+
"extra_fields": extra_fields,
|
|
1251
|
+
}
|
|
1252
|
+
incorrect_entities.append(entry)
|
|
1253
|
+
|
|
1254
|
+
return incorrect_entities
|
|
1255
|
+
|
|
972
1256
|
def get_settings(self, id: int) -> Dict[str, str]:
|
|
973
1257
|
info = self._get_info_by_id(id, "projects.info")
|
|
974
1258
|
return info.settings
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import asyncio
|
|
4
5
|
import json
|
|
5
6
|
from typing import Callable, Dict, List, Optional, Union
|
|
6
7
|
|
|
@@ -247,3 +248,47 @@ class VideoAnnotationAPI(EntityAnnotationAPI):
|
|
|
247
248
|
self.append(dst_id, ann)
|
|
248
249
|
if progress_cb is not None:
|
|
249
250
|
progress_cb(1)
|
|
251
|
+
|
|
252
|
+
async def download_async(
|
|
253
|
+
self,
|
|
254
|
+
video_id: int,
|
|
255
|
+
video_info=None,
|
|
256
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
257
|
+
) -> Dict:
|
|
258
|
+
"""
|
|
259
|
+
Download information about VideoAnnotation by video ID from API asynchronously.
|
|
260
|
+
|
|
261
|
+
:param video_id: Video ID in Supervisely.
|
|
262
|
+
:type video_id: int
|
|
263
|
+
:param video_info: VideoInfo object. Use it to avoid additional request to the server.
|
|
264
|
+
:type video_info: VideoInfo, optional
|
|
265
|
+
:param semaphore: Semaphore to limit the number of parallel downloads.
|
|
266
|
+
:type semaphore: asyncio.Semaphore, optional
|
|
267
|
+
:return: Information about VideoAnnotation in json format
|
|
268
|
+
:rtype: :class:`dict`
|
|
269
|
+
:Usage example:
|
|
270
|
+
|
|
271
|
+
.. code-block:: python
|
|
272
|
+
|
|
273
|
+
import supervisely as sly
|
|
274
|
+
|
|
275
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
276
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
277
|
+
api = sly.Api.from_env()
|
|
278
|
+
|
|
279
|
+
video_id = 198702499
|
|
280
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
281
|
+
ann_info = loop.run_until_complete(api.video.annotation.download_async(video_id))
|
|
282
|
+
"""
|
|
283
|
+
if video_info is None:
|
|
284
|
+
video_info = self._api.video.get_info_by_id(video_id)
|
|
285
|
+
|
|
286
|
+
if semaphore is None:
|
|
287
|
+
semaphore = self._api._get_default_semaphore()
|
|
288
|
+
|
|
289
|
+
async with semaphore:
|
|
290
|
+
response = await self._api.post_async(
|
|
291
|
+
self._method_download_bulk,
|
|
292
|
+
{ApiField.DATASET_ID: video_info.dataset_id, self._entity_ids_str: [video_info.id]},
|
|
293
|
+
)
|
|
294
|
+
return response.json()
|
|
@@ -2466,8 +2466,7 @@ class VideoApi(RemoveableBulkModuleApi):
|
|
|
2466
2466
|
save_path = os.path.join("/path/to/save/", video_info.name)
|
|
2467
2467
|
|
|
2468
2468
|
semaphore = asyncio.Semaphore(100)
|
|
2469
|
-
loop =
|
|
2470
|
-
asyncio.set_event_loop(loop)
|
|
2469
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
2471
2470
|
loop.run_until_complete(
|
|
2472
2471
|
api.video.download_path_async(video_info.id, save_path, semaphore)
|
|
2473
2472
|
)
|
|
@@ -2555,8 +2554,7 @@ class VideoApi(RemoveableBulkModuleApi):
|
|
|
2555
2554
|
|
|
2556
2555
|
ids = [770914, 770915]
|
|
2557
2556
|
paths = ["/path/to/save/video1.mp4", "/path/to/save/video2.mp4"]
|
|
2558
|
-
loop =
|
|
2559
|
-
asyncio.set_event_loop(loop)
|
|
2557
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
2560
2558
|
loop.run_until_complete(api.video.download_paths_async(ids, paths))
|
|
2561
2559
|
"""
|
|
2562
2560
|
if len(ids) == 0:
|
|
@@ -1343,8 +1343,7 @@ class VolumeApi(RemoveableBulkModuleApi):
|
|
|
1343
1343
|
save_path = os.path.join("/path/to/save/", volume_info.name)
|
|
1344
1344
|
|
|
1345
1345
|
semaphore = asyncio.Semaphore(100)
|
|
1346
|
-
loop =
|
|
1347
|
-
asyncio.set_event_loop(loop)
|
|
1346
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1348
1347
|
loop.run_until_complete(
|
|
1349
1348
|
api.volume.download_path_async(volume_info.id, save_path, semaphore)
|
|
1350
1349
|
)
|
|
@@ -1433,8 +1432,7 @@ class VolumeApi(RemoveableBulkModuleApi):
|
|
|
1433
1432
|
|
|
1434
1433
|
ids = [770914, 770915]
|
|
1435
1434
|
paths = ["/path/to/save/volume1.nrrd", "/path/to/save/volume2.nrrd"]
|
|
1436
|
-
loop =
|
|
1437
|
-
asyncio.set_event_loop(loop)
|
|
1435
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1438
1436
|
loop.run_until_complete(api.volume.download_paths_async(ids, paths))
|
|
1439
1437
|
"""
|
|
1440
1438
|
if len(ids) == 0:
|
|
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional, Tuple, Union
|
|
|
6
6
|
|
|
7
7
|
from tqdm import tqdm
|
|
8
8
|
|
|
9
|
-
from supervisely._utils import batched, is_production
|
|
9
|
+
from supervisely._utils import batched, get_or_create_event_loop, is_production
|
|
10
10
|
from supervisely.annotation.annotation import Annotation
|
|
11
11
|
from supervisely.annotation.tag_meta import TagValueType
|
|
12
12
|
from supervisely.api.api import Api
|
|
@@ -468,7 +468,7 @@ class BaseConverter:
|
|
|
468
468
|
for remote_path in files.values()
|
|
469
469
|
)
|
|
470
470
|
|
|
471
|
-
loop =
|
|
471
|
+
loop = get_or_create_event_loop()
|
|
472
472
|
_, progress_cb = self.get_progress(
|
|
473
473
|
len(files) if not is_archive_type else file_size,
|
|
474
474
|
f"Downloading {files_type} from remote storage",
|
|
@@ -479,15 +479,19 @@ class BaseConverter:
|
|
|
479
479
|
silent_remove(local_path)
|
|
480
480
|
|
|
481
481
|
logger.info(f"Downloading {files_type} from remote storage...")
|
|
482
|
-
|
|
483
|
-
self.
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
progress_cb_type="number" if not is_archive_type else "size",
|
|
489
|
-
)
|
|
482
|
+
download_coro = self._api.storage.download_bulk_async(
|
|
483
|
+
team_id=self._team_id,
|
|
484
|
+
remote_paths=list(files.values()),
|
|
485
|
+
local_save_paths=list(files.keys()),
|
|
486
|
+
progress_cb=progress_cb,
|
|
487
|
+
progress_cb_type="number" if not is_archive_type else "size",
|
|
490
488
|
)
|
|
489
|
+
|
|
490
|
+
if loop.is_running():
|
|
491
|
+
future = asyncio.run_coroutine_threadsafe(download_coro, loop=loop)
|
|
492
|
+
future.result()
|
|
493
|
+
else:
|
|
494
|
+
loop.run_until_complete(download_coro)
|
|
491
495
|
logger.info("Possible annotations downloaded successfully.")
|
|
492
496
|
|
|
493
497
|
if is_archive_type:
|
supervisely/io/fs.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
3
|
# docs
|
|
4
|
-
import asyncio
|
|
5
4
|
import errno
|
|
6
5
|
import mimetypes
|
|
7
6
|
import os
|
|
@@ -16,7 +15,7 @@ import requests
|
|
|
16
15
|
from requests.structures import CaseInsensitiveDict
|
|
17
16
|
from tqdm import tqdm
|
|
18
17
|
|
|
19
|
-
from supervisely._utils import get_bytes_hash, get_string_hash
|
|
18
|
+
from supervisely._utils import get_bytes_hash, get_or_create_event_loop, get_string_hash
|
|
20
19
|
from supervisely.io.fs_cache import FileCache
|
|
21
20
|
from supervisely.sly_logger import logger
|
|
22
21
|
from supervisely.task.progress import Progress
|
|
@@ -1375,8 +1374,15 @@ async def copy_file_async(
|
|
|
1375
1374
|
|
|
1376
1375
|
.. code-block:: python
|
|
1377
1376
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1377
|
+
import supervisely as sly
|
|
1378
|
+
|
|
1379
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1380
|
+
coro = sly.fs.copy_file_async('/home/admin/work/projects/example/1.png', '/home/admin/work/tests/2.png')
|
|
1381
|
+
if loop.is_running():
|
|
1382
|
+
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
1383
|
+
future.result()
|
|
1384
|
+
else:
|
|
1385
|
+
loop.run_until_complete(coro)
|
|
1380
1386
|
"""
|
|
1381
1387
|
ensure_base_path(dst)
|
|
1382
1388
|
async with aiofiles.open(dst, "wb") as out_f:
|
|
@@ -1404,8 +1410,15 @@ async def get_file_hash_async(path: str) -> str:
|
|
|
1404
1410
|
|
|
1405
1411
|
.. code-block:: python
|
|
1406
1412
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1413
|
+
import supervisely as sly
|
|
1414
|
+
|
|
1415
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1416
|
+
coro = sly.fs.get_file_hash_async('/home/admin/work/projects/examples/1.jpeg')
|
|
1417
|
+
if loop.is_running():
|
|
1418
|
+
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
1419
|
+
hash = future.result()
|
|
1420
|
+
else:
|
|
1421
|
+
hash = loop.run_until_complete(coro)
|
|
1409
1422
|
"""
|
|
1410
1423
|
async with aiofiles.open(path, "rb") as file:
|
|
1411
1424
|
file_bytes = await file.read()
|
|
@@ -1442,7 +1455,13 @@ async def unpack_archive_async(
|
|
|
1442
1455
|
archive_path = '/home/admin/work/examples.tar'
|
|
1443
1456
|
target_dir = '/home/admin/work/projects'
|
|
1444
1457
|
|
|
1445
|
-
|
|
1458
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1459
|
+
coro = sly.fs.unpack_archive_async(archive_path, target_dir)
|
|
1460
|
+
if loop.is_running():
|
|
1461
|
+
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
1462
|
+
future.result()
|
|
1463
|
+
else:
|
|
1464
|
+
loop.run_until_complete(coro)
|
|
1446
1465
|
"""
|
|
1447
1466
|
if is_split:
|
|
1448
1467
|
chunk = chunk_size_mb * 1024 * 1024
|
|
@@ -1467,9 +1486,37 @@ async def unpack_archive_async(
|
|
|
1467
1486
|
await output_file.write(data)
|
|
1468
1487
|
archive_path = combined
|
|
1469
1488
|
|
|
1470
|
-
loop =
|
|
1489
|
+
loop = get_or_create_event_loop()
|
|
1471
1490
|
await loop.run_in_executor(None, shutil.unpack_archive, archive_path, target_dir)
|
|
1472
1491
|
if is_split:
|
|
1473
1492
|
silent_remove(archive_path)
|
|
1474
1493
|
if remove_junk:
|
|
1475
1494
|
remove_junk_from_dir(target_dir)
|
|
1495
|
+
|
|
1496
|
+
|
|
1497
|
+
async def touch_async(path: str) -> None:
|
|
1498
|
+
"""
|
|
1499
|
+
Sets access and modification times for a file asynchronously.
|
|
1500
|
+
|
|
1501
|
+
:param path: Target file path.
|
|
1502
|
+
:type path: str
|
|
1503
|
+
:returns: None
|
|
1504
|
+
:rtype: :class:`NoneType`
|
|
1505
|
+
:Usage example:
|
|
1506
|
+
|
|
1507
|
+
.. code-block:: python
|
|
1508
|
+
|
|
1509
|
+
import supervisely as sly
|
|
1510
|
+
|
|
1511
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1512
|
+
coro = sly.fs.touch_async('/home/admin/work/projects/examples/1.jpeg')
|
|
1513
|
+
if loop.is_running():
|
|
1514
|
+
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
1515
|
+
future.result()
|
|
1516
|
+
else:
|
|
1517
|
+
loop.run_until_complete(coro)
|
|
1518
|
+
"""
|
|
1519
|
+
ensure_base_path(path)
|
|
1520
|
+
async with aiofiles.open(path, "a"):
|
|
1521
|
+
loop = get_or_create_event_loop()
|
|
1522
|
+
await loop.run_in_executor(None, os.utime, path, None)
|
supervisely/io/json.py
CHANGED
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import os
|
|
4
4
|
from typing import Dict, Optional
|
|
5
5
|
|
|
6
|
+
import aiofiles
|
|
6
7
|
import jsonschema
|
|
7
8
|
|
|
8
9
|
|
|
@@ -230,3 +231,34 @@ def validate_json(data: Dict, schema: Dict, raise_error: bool = False) -> bool:
|
|
|
230
231
|
if raise_error:
|
|
231
232
|
raise ValueError("JSON data is invalid. See error message for more details.") from err
|
|
232
233
|
return False
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
async def dump_json_file_async(data: Dict, filename: str, indent: Optional[int] = 4) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Write given data in json format in file with given name asynchronously.
|
|
239
|
+
|
|
240
|
+
:param data: Data in json format as a dict.
|
|
241
|
+
:type data: dict
|
|
242
|
+
:param filename: Target file path to write data.
|
|
243
|
+
:type filename: str
|
|
244
|
+
:param indent: Json array elements and object members will be pretty-printed with that indent level.
|
|
245
|
+
:type indent: int, optional
|
|
246
|
+
:returns: None
|
|
247
|
+
:rtype: :class:`NoneType`
|
|
248
|
+
:Usage example:
|
|
249
|
+
|
|
250
|
+
.. code-block:: python
|
|
251
|
+
|
|
252
|
+
import supervisely as sly
|
|
253
|
+
|
|
254
|
+
data = {1: 'example'}
|
|
255
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
256
|
+
coro = sly.json.dump_json_file_async(data, '/home/admin/work/projects/examples/1.json')
|
|
257
|
+
if loop.is_running():
|
|
258
|
+
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
259
|
+
future.result()
|
|
260
|
+
else:
|
|
261
|
+
loop.run_until_complete(coro)
|
|
262
|
+
"""
|
|
263
|
+
async with aiofiles.open(filename, "w") as fout:
|
|
264
|
+
await fout.write(json.dumps(data, indent=indent))
|