supervisely 6.73.221__py3-none-any.whl → 6.73.222__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/api/api.py +609 -3
- supervisely/api/file_api.py +522 -9
- supervisely/api/image_api.py +469 -0
- supervisely/api/pointcloud/pointcloud_api.py +390 -1
- supervisely/api/video/video_api.py +231 -1
- supervisely/api/volume/volume_api.py +223 -2
- supervisely/convert/base_converter.py +44 -3
- supervisely/convert/converter.py +6 -5
- supervisely/convert/image/image_converter.py +26 -13
- supervisely/convert/image/sly/fast_sly_image_converter.py +4 -0
- supervisely/convert/image/sly/sly_image_converter.py +9 -4
- supervisely/convert/video/sly/sly_video_converter.py +9 -1
- supervisely/convert/video/video_converter.py +44 -23
- supervisely/io/fs.py +125 -0
- supervisely/io/fs_cache.py +19 -1
- supervisely/io/network_exceptions.py +20 -3
- supervisely/task/progress.py +1 -1
- {supervisely-6.73.221.dist-info → supervisely-6.73.222.dist-info}/METADATA +3 -1
- {supervisely-6.73.221.dist-info → supervisely-6.73.222.dist-info}/RECORD +23 -23
- {supervisely-6.73.221.dist-info → supervisely-6.73.222.dist-info}/LICENSE +0 -0
- {supervisely-6.73.221.dist-info → supervisely-6.73.222.dist-info}/WHEEL +0 -0
- {supervisely-6.73.221.dist-info → supervisely-6.73.222.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.221.dist-info → supervisely-6.73.222.dist-info}/top_level.txt +0 -0
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
3
|
# docs
|
|
4
|
+
import asyncio
|
|
4
5
|
import os
|
|
5
6
|
from collections import defaultdict
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import (
|
|
8
|
+
AsyncGenerator,
|
|
9
|
+
Callable,
|
|
10
|
+
Dict,
|
|
11
|
+
List,
|
|
12
|
+
Literal,
|
|
13
|
+
NamedTuple,
|
|
14
|
+
Optional,
|
|
15
|
+
Union,
|
|
16
|
+
)
|
|
7
17
|
|
|
18
|
+
import aiofiles
|
|
8
19
|
from requests import Response
|
|
9
20
|
from requests_toolbelt import MultipartEncoder
|
|
10
21
|
from tqdm import tqdm
|
|
@@ -18,11 +29,13 @@ from supervisely.api.pointcloud.pointcloud_tag_api import PointcloudTagApi
|
|
|
18
29
|
from supervisely.io.fs import (
|
|
19
30
|
ensure_base_path,
|
|
20
31
|
get_file_hash,
|
|
32
|
+
get_file_hash_async,
|
|
21
33
|
get_file_name_with_ext,
|
|
22
34
|
list_files,
|
|
23
35
|
list_files_recursively,
|
|
24
36
|
)
|
|
25
37
|
from supervisely.pointcloud.pointcloud import is_valid_format
|
|
38
|
+
from supervisely.sly_logger import logger
|
|
26
39
|
|
|
27
40
|
|
|
28
41
|
class PointcloudInfo(NamedTuple):
|
|
@@ -1032,3 +1045,379 @@ class PointcloudApi(RemoveableBulkModuleApi):
|
|
|
1032
1045
|
)
|
|
1033
1046
|
)
|
|
1034
1047
|
return pcds_infos
|
|
1048
|
+
|
|
1049
|
+
async def _download_async(
|
|
1050
|
+
self,
|
|
1051
|
+
id: int,
|
|
1052
|
+
is_stream: bool = False,
|
|
1053
|
+
range_start: Optional[int] = None,
|
|
1054
|
+
range_end: Optional[int] = None,
|
|
1055
|
+
headers: dict = None,
|
|
1056
|
+
chunk_size: int = 1024 * 1024,
|
|
1057
|
+
) -> AsyncGenerator:
|
|
1058
|
+
"""
|
|
1059
|
+
Download Point cloud with given ID asynchronously.
|
|
1060
|
+
If is_stream is True, returns stream of bytes, otherwise returns response object.
|
|
1061
|
+
For streaming, returns tuple of chunk and hash.
|
|
1062
|
+
|
|
1063
|
+
:param id: Point cloud ID in Supervisely.
|
|
1064
|
+
:type id: int
|
|
1065
|
+
:param is_stream: If True, returns stream of bytes, otherwise returns response object.
|
|
1066
|
+
:type is_stream: bool, optional
|
|
1067
|
+
:param range_start: Start byte of range for partial download.
|
|
1068
|
+
:type range_start: int, optional
|
|
1069
|
+
:param range_end: End byte of range for partial download.
|
|
1070
|
+
:type range_end: int, optional
|
|
1071
|
+
:param headers: Headers for request.
|
|
1072
|
+
:type headers: dict, optional
|
|
1073
|
+
:param chunk_size: Size of chunk for partial download. Default is 1MB.
|
|
1074
|
+
:type chunk_size: int, optional
|
|
1075
|
+
:return: Stream of bytes or response object.
|
|
1076
|
+
:rtype: AsyncGenerator
|
|
1077
|
+
"""
|
|
1078
|
+
api_method_name = "point-clouds.download"
|
|
1079
|
+
|
|
1080
|
+
json_body = {ApiField.ID: id}
|
|
1081
|
+
|
|
1082
|
+
if is_stream:
|
|
1083
|
+
async for chunk, hhash in self._api.stream_async(
|
|
1084
|
+
api_method_name,
|
|
1085
|
+
"POST",
|
|
1086
|
+
json_body,
|
|
1087
|
+
headers=headers,
|
|
1088
|
+
range_start=range_start,
|
|
1089
|
+
range_end=range_end,
|
|
1090
|
+
chunk_size=chunk_size,
|
|
1091
|
+
):
|
|
1092
|
+
yield chunk, hhash
|
|
1093
|
+
else:
|
|
1094
|
+
response = await self._api.post_async(api_method_name, json_body, headers=headers)
|
|
1095
|
+
yield response
|
|
1096
|
+
|
|
1097
|
+
async def download_path_async(
|
|
1098
|
+
self,
|
|
1099
|
+
id: int,
|
|
1100
|
+
path: str,
|
|
1101
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1102
|
+
range_start: Optional[int] = None,
|
|
1103
|
+
range_end: Optional[int] = None,
|
|
1104
|
+
headers: Optional[dict] = None,
|
|
1105
|
+
chunk_size: int = 1024 * 1024,
|
|
1106
|
+
check_hash: bool = True,
|
|
1107
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1108
|
+
progress_cb_type: Literal["number", "size"] = "number",
|
|
1109
|
+
) -> None:
|
|
1110
|
+
"""
|
|
1111
|
+
Downloads Point cloud with given ID to local path.
|
|
1112
|
+
|
|
1113
|
+
:param id: Point cloud ID in Supervisely.
|
|
1114
|
+
:type id: int
|
|
1115
|
+
:param path: Local save path for Point cloud.
|
|
1116
|
+
:type path: str
|
|
1117
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1118
|
+
:type semaphore: :class:`asyncio.Semaphore`, optional
|
|
1119
|
+
:param range_start: Start byte of range for partial download.
|
|
1120
|
+
:type range_start: int, optional
|
|
1121
|
+
:param range_end: End byte of range for partial download.
|
|
1122
|
+
:type range_end: int, optional
|
|
1123
|
+
:param headers: Headers for request.
|
|
1124
|
+
:type headers: dict, optional
|
|
1125
|
+
:param chunk_size: Size of chunk for partial download. Default is 1MB.
|
|
1126
|
+
:type chunk_size: int, optional
|
|
1127
|
+
:param check_hash: If True, checks hash of downloaded file.
|
|
1128
|
+
Check is not supported for partial downloads.
|
|
1129
|
+
When range is set, hash check is disabled.
|
|
1130
|
+
:type check_hash: bool, optional
|
|
1131
|
+
:param progress_cb: Function for tracking download progress.
|
|
1132
|
+
:type progress_cb: Optional[Union[tqdm, Callable]]
|
|
1133
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
|
|
1134
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
1135
|
+
:return: None
|
|
1136
|
+
:rtype: :class:`NoneType`
|
|
1137
|
+
:Usage example:
|
|
1138
|
+
|
|
1139
|
+
.. code-block:: python
|
|
1140
|
+
|
|
1141
|
+
import supervisely as sly
|
|
1142
|
+
import asyncio
|
|
1143
|
+
|
|
1144
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1145
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1146
|
+
api = sly.Api.from_env()
|
|
1147
|
+
|
|
1148
|
+
pcd_info = api.pointcloud.get_info_by_id(19373403)
|
|
1149
|
+
save_path = os.path.join("/path/to/save/", pcd_info.name)
|
|
1150
|
+
|
|
1151
|
+
semaphore = asyncio.Semaphore(100)
|
|
1152
|
+
loop = asyncio.new_event_loop()
|
|
1153
|
+
asyncio.set_event_loop(loop)
|
|
1154
|
+
loop.run_until_complete(
|
|
1155
|
+
api.pointcloud.download_path_async(pcd_info.id, save_path, semaphore)
|
|
1156
|
+
)
|
|
1157
|
+
"""
|
|
1158
|
+
|
|
1159
|
+
if range_start is not None or range_end is not None:
|
|
1160
|
+
check_hash = False # Hash check is not supported for partial downloads
|
|
1161
|
+
headers = headers or {}
|
|
1162
|
+
headers["Range"] = f"bytes={range_start or ''}-{range_end or ''}"
|
|
1163
|
+
logger.debug(f"Image ID: {id}. Setting Range header: {headers['Range']}")
|
|
1164
|
+
|
|
1165
|
+
writing_method = "ab" if range_start not in [0, None] else "wb"
|
|
1166
|
+
|
|
1167
|
+
ensure_base_path(path)
|
|
1168
|
+
hash_to_check = None
|
|
1169
|
+
if semaphore is None:
|
|
1170
|
+
semaphore = self._api._get_default_semaphore()
|
|
1171
|
+
async with semaphore:
|
|
1172
|
+
async with aiofiles.open(path, writing_method) as fd:
|
|
1173
|
+
async for chunk, hhash in self._download_async(
|
|
1174
|
+
id,
|
|
1175
|
+
is_stream=True,
|
|
1176
|
+
headers=headers,
|
|
1177
|
+
range_start=range_start,
|
|
1178
|
+
range_end=range_end,
|
|
1179
|
+
chunk_size=chunk_size,
|
|
1180
|
+
):
|
|
1181
|
+
await fd.write(chunk)
|
|
1182
|
+
hash_to_check = hhash
|
|
1183
|
+
if progress_cb is not None and progress_cb_type == "size":
|
|
1184
|
+
progress_cb(len(chunk))
|
|
1185
|
+
if check_hash:
|
|
1186
|
+
if hash_to_check is not None:
|
|
1187
|
+
downloaded_bytes_hash = await get_file_hash_async(path)
|
|
1188
|
+
if hash_to_check != downloaded_bytes_hash:
|
|
1189
|
+
raise RuntimeError(
|
|
1190
|
+
f"Downloaded hash of point cloud with ID:{id} does not match the expected hash: {downloaded_bytes_hash} != {hash_to_check}"
|
|
1191
|
+
)
|
|
1192
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
1193
|
+
progress_cb(1)
|
|
1194
|
+
|
|
1195
|
+
async def download_paths_async(
|
|
1196
|
+
self,
|
|
1197
|
+
ids: List[int],
|
|
1198
|
+
paths: List[str],
|
|
1199
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1200
|
+
headers: dict = None,
|
|
1201
|
+
chunk_size: int = 1024 * 1024,
|
|
1202
|
+
check_hash: bool = True,
|
|
1203
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1204
|
+
progress_cb_type: Literal["number", "size"] = "number",
|
|
1205
|
+
) -> None:
|
|
1206
|
+
"""
|
|
1207
|
+
Download Point clouds with given IDs and saves them to given local paths asynchronously.
|
|
1208
|
+
|
|
1209
|
+
:param ids: List of Point cloud IDs in Supervisely.
|
|
1210
|
+
:type ids: :class:`List[int]`
|
|
1211
|
+
:param paths: Local save paths for Point clouds.
|
|
1212
|
+
:type paths: :class:`List[str]`
|
|
1213
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1214
|
+
:type semaphore: :class:`asyncio.Semaphore`, optional
|
|
1215
|
+
:param headers: Headers for request.
|
|
1216
|
+
:type headers: dict, optional
|
|
1217
|
+
:param show_progress: If True, shows progress bar.
|
|
1218
|
+
:type show_progress: bool, optional
|
|
1219
|
+
:param chunk_size: Size of chunk for partial download. Default is 1MB.
|
|
1220
|
+
:type chunk_size: int, optional
|
|
1221
|
+
:param check_hash: If True, checks hash of downloaded file.
|
|
1222
|
+
:type check_hash: bool, optional
|
|
1223
|
+
:param progress_cb: Function for tracking download progress.
|
|
1224
|
+
:type progress_cb: Optional[Union[tqdm, Callable]]
|
|
1225
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
|
|
1226
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
1227
|
+
:raises: :class:`ValueError` if len(ids) != len(paths)
|
|
1228
|
+
:return: None
|
|
1229
|
+
:rtype: :class:`NoneType`
|
|
1230
|
+
|
|
1231
|
+
:Usage example:
|
|
1232
|
+
|
|
1233
|
+
.. code-block:: python
|
|
1234
|
+
|
|
1235
|
+
import supervisely as sly
|
|
1236
|
+
import asyncio
|
|
1237
|
+
|
|
1238
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1239
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1240
|
+
api = sly.Api.from_env()
|
|
1241
|
+
|
|
1242
|
+
ids = [19373403, 19373404]
|
|
1243
|
+
paths = ["/path/to/save/000063.pcd", "/path/to/save/000064.pcd"]
|
|
1244
|
+
loop = asyncio.new_event_loop()
|
|
1245
|
+
asyncio.set_event_loop(loop)
|
|
1246
|
+
loop.run_until_complete(api.pointcloud.download_paths_async(ids, paths))
|
|
1247
|
+
"""
|
|
1248
|
+
if len(ids) == 0:
|
|
1249
|
+
return
|
|
1250
|
+
if len(ids) != len(paths):
|
|
1251
|
+
raise ValueError('Can not match "ids" and "paths" lists, len(ids) != len(paths)')
|
|
1252
|
+
if semaphore is None:
|
|
1253
|
+
semaphore = self._api._get_default_semaphore()
|
|
1254
|
+
tasks = []
|
|
1255
|
+
for img_id, img_path in zip(ids, paths):
|
|
1256
|
+
task = self.download_path_async(
|
|
1257
|
+
img_id,
|
|
1258
|
+
img_path,
|
|
1259
|
+
semaphore,
|
|
1260
|
+
headers=headers,
|
|
1261
|
+
chunk_size=chunk_size,
|
|
1262
|
+
check_hash=check_hash,
|
|
1263
|
+
progress_cb=progress_cb,
|
|
1264
|
+
progress_cb_type=progress_cb_type,
|
|
1265
|
+
)
|
|
1266
|
+
tasks.append(task)
|
|
1267
|
+
await asyncio.gather(*tasks)
|
|
1268
|
+
|
|
1269
|
+
async def download_related_image_async(
|
|
1270
|
+
self,
|
|
1271
|
+
id: int,
|
|
1272
|
+
path: str,
|
|
1273
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1274
|
+
headers: dict = None,
|
|
1275
|
+
chunk_size: int = 1024 * 1024,
|
|
1276
|
+
check_hash: bool = True,
|
|
1277
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1278
|
+
progress_cb_type: Literal["number", "size"] = "number",
|
|
1279
|
+
) -> None:
|
|
1280
|
+
"""
|
|
1281
|
+
Downloads a related context image from Supervisely to local directory by image id.
|
|
1282
|
+
|
|
1283
|
+
:param id: Point cloud ID in Supervisely.
|
|
1284
|
+
:type id: int
|
|
1285
|
+
:param path: Local save path for Point cloud.
|
|
1286
|
+
:type path: str
|
|
1287
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1288
|
+
:type semaphore: :class:`asyncio.Semaphore`, optional
|
|
1289
|
+
:param headers: Headers for request.
|
|
1290
|
+
:type headers: dict, optional
|
|
1291
|
+
:param chunk_size: Size of chunk for partial download. Default is 1MB.
|
|
1292
|
+
:type chunk_size: int, optional
|
|
1293
|
+
:param check_hash: If True, checks hash of downloaded file.
|
|
1294
|
+
:type check_hash: bool, optional
|
|
1295
|
+
:param progress_cb: Function for tracking download progress.
|
|
1296
|
+
:type progress_cb: Optional[Union[tqdm, Callable]]
|
|
1297
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
|
|
1298
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
1299
|
+
:return: None
|
|
1300
|
+
:rtype: :class:`NoneType`
|
|
1301
|
+
:Usage example:
|
|
1302
|
+
|
|
1303
|
+
.. code-block:: python
|
|
1304
|
+
|
|
1305
|
+
import supervisely as sly
|
|
1306
|
+
import asyncio
|
|
1307
|
+
|
|
1308
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1309
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1310
|
+
api = sly.Api.from_env()
|
|
1311
|
+
|
|
1312
|
+
img_info = api.pointcloud.get_list_related_images(19373403)[0]
|
|
1313
|
+
save_path = os.path.join("/path/to/save/", img_info.name)
|
|
1314
|
+
|
|
1315
|
+
semaphore = asyncio.Semaphore(100)
|
|
1316
|
+
loop = asyncio.new_event_loop()
|
|
1317
|
+
asyncio.set_event_loop(loop)
|
|
1318
|
+
loop.run_until_complete(
|
|
1319
|
+
api.pointcloud.download_related_image_async(19373403, save_path, semaphore)
|
|
1320
|
+
)
|
|
1321
|
+
"""
|
|
1322
|
+
|
|
1323
|
+
api_method_name = "point-clouds.images.download"
|
|
1324
|
+
|
|
1325
|
+
ensure_base_path(path)
|
|
1326
|
+
hash_to_check = None
|
|
1327
|
+
if semaphore is None:
|
|
1328
|
+
semaphore = self._api._get_default_semaphore()
|
|
1329
|
+
async with semaphore:
|
|
1330
|
+
async with aiofiles.open(path, "wb") as fd:
|
|
1331
|
+
async for chunk, hhash in self._api.stream_async(
|
|
1332
|
+
api_method_name,
|
|
1333
|
+
"POST",
|
|
1334
|
+
{ApiField.ID: id},
|
|
1335
|
+
headers=headers,
|
|
1336
|
+
chunk_size=chunk_size,
|
|
1337
|
+
):
|
|
1338
|
+
await fd.write(chunk)
|
|
1339
|
+
hash_to_check = hhash
|
|
1340
|
+
if progress_cb is not None and progress_cb_type == "size":
|
|
1341
|
+
progress_cb(len(chunk))
|
|
1342
|
+
if check_hash:
|
|
1343
|
+
if hash_to_check is not None:
|
|
1344
|
+
downloaded_bytes_hash = await get_file_hash_async(path)
|
|
1345
|
+
if hash_to_check != downloaded_bytes_hash:
|
|
1346
|
+
raise RuntimeError(
|
|
1347
|
+
f"Downloaded hash of point cloud related image with ID:{id} does not match the expected hash: {downloaded_bytes_hash} != {hash_to_check}"
|
|
1348
|
+
)
|
|
1349
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
1350
|
+
progress_cb(1)
|
|
1351
|
+
|
|
1352
|
+
async def download_related_images_async(
|
|
1353
|
+
self,
|
|
1354
|
+
ids: List[int],
|
|
1355
|
+
paths: List[str],
|
|
1356
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1357
|
+
headers: dict = None,
|
|
1358
|
+
check_hash: bool = True,
|
|
1359
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1360
|
+
progress_cb_type: Literal["number", "size"] = "number",
|
|
1361
|
+
) -> None:
|
|
1362
|
+
"""
|
|
1363
|
+
Downloads a related context image from Supervisely to local directory by image id.
|
|
1364
|
+
|
|
1365
|
+
:param ids: Related context imgage IDs in Supervisely.
|
|
1366
|
+
:type ids: int
|
|
1367
|
+
:param paths: Local save paths for Point clouds.
|
|
1368
|
+
:type paths: str
|
|
1369
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1370
|
+
:type semaphore: :class:`asyncio.Semaphore`, optional
|
|
1371
|
+
:param headers: Headers for request.
|
|
1372
|
+
:type headers: dict, optional
|
|
1373
|
+
:param check_hash: If True, checks hash of downloaded file.
|
|
1374
|
+
:type check_hash: bool, optional
|
|
1375
|
+
:param progress_cb: Function for tracking download progress.
|
|
1376
|
+
:type progress_cb: Optional[Union[tqdm, Callable]]
|
|
1377
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
|
|
1378
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
1379
|
+
:return: None
|
|
1380
|
+
:rtype: :class:`NoneType`
|
|
1381
|
+
|
|
1382
|
+
:Usage example:
|
|
1383
|
+
|
|
1384
|
+
.. code-block:: python
|
|
1385
|
+
|
|
1386
|
+
import supervisely as sly
|
|
1387
|
+
import asyncio
|
|
1388
|
+
|
|
1389
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1390
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1391
|
+
api = sly.Api.from_env()
|
|
1392
|
+
|
|
1393
|
+
img_infos = api.pointcloud.get_list_related_images(19373403)
|
|
1394
|
+
ids = [img_info.id for img_info in img_infos]
|
|
1395
|
+
save_paths = [os.path.join("/path/to/save/", img_info.name) for img_info in img_infos]
|
|
1396
|
+
|
|
1397
|
+
semaphore = asyncio.Semaphore(100)
|
|
1398
|
+
loop = asyncio.new_event_loop()
|
|
1399
|
+
asyncio.set_event_loop(loop)
|
|
1400
|
+
loop.run_until_complete(
|
|
1401
|
+
api.pointcloud.download_related_images_async(ids, save_paths, semaphore)
|
|
1402
|
+
)
|
|
1403
|
+
"""
|
|
1404
|
+
|
|
1405
|
+
if len(ids) == 0:
|
|
1406
|
+
return
|
|
1407
|
+
if len(ids) != len(paths):
|
|
1408
|
+
raise ValueError('Can not match "ids" and "paths" lists, len(ids) != len(paths)')
|
|
1409
|
+
if semaphore is None:
|
|
1410
|
+
semaphore = self._api._get_default_semaphore()
|
|
1411
|
+
tasks = []
|
|
1412
|
+
for img_id, img_path in zip(ids, paths):
|
|
1413
|
+
task = self.download_related_image_async(
|
|
1414
|
+
img_id,
|
|
1415
|
+
img_path,
|
|
1416
|
+
semaphore,
|
|
1417
|
+
headers=headers,
|
|
1418
|
+
check_hash=check_hash,
|
|
1419
|
+
progress_cb=progress_cb,
|
|
1420
|
+
progress_cb_type=progress_cb_type,
|
|
1421
|
+
)
|
|
1422
|
+
tasks.append(task)
|
|
1423
|
+
await asyncio.gather(*tasks)
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import asyncio
|
|
4
5
|
import datetime
|
|
5
6
|
import json
|
|
6
7
|
import os
|
|
7
8
|
import urllib.parse
|
|
8
9
|
from functools import partial
|
|
9
|
-
from typing import
|
|
10
|
+
from typing import (
|
|
11
|
+
AsyncGenerator,
|
|
12
|
+
Callable,
|
|
13
|
+
Dict,
|
|
14
|
+
Iterator,
|
|
15
|
+
List,
|
|
16
|
+
Literal,
|
|
17
|
+
NamedTuple,
|
|
18
|
+
Optional,
|
|
19
|
+
Tuple,
|
|
20
|
+
Union,
|
|
21
|
+
)
|
|
10
22
|
|
|
23
|
+
import aiofiles
|
|
11
24
|
from numerize.numerize import numerize
|
|
12
25
|
from requests import Response
|
|
13
26
|
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
|
|
@@ -31,6 +44,7 @@ from supervisely.io.fs import (
|
|
|
31
44
|
ensure_base_path,
|
|
32
45
|
get_file_ext,
|
|
33
46
|
get_file_hash,
|
|
47
|
+
get_file_hash_async,
|
|
34
48
|
get_file_name_with_ext,
|
|
35
49
|
get_file_size,
|
|
36
50
|
list_files,
|
|
@@ -2305,3 +2319,219 @@ class VideoApi(RemoveableBulkModuleApi):
|
|
|
2305
2319
|
data = {ApiField.VIDEOS: videos_list, ApiField.CLEAR_LOCAL_DATA_SOURCE: True}
|
|
2306
2320
|
r = self._api.post("videos.update.links", data)
|
|
2307
2321
|
return r.json()
|
|
2322
|
+
|
|
2323
|
+
async def _download_async(
|
|
2324
|
+
self,
|
|
2325
|
+
id: int,
|
|
2326
|
+
is_stream: bool = False,
|
|
2327
|
+
range_start: Optional[int] = None,
|
|
2328
|
+
range_end: Optional[int] = None,
|
|
2329
|
+
headers: Optional[dict] = None,
|
|
2330
|
+
chunk_size: int = 1024 * 1024,
|
|
2331
|
+
) -> AsyncGenerator:
|
|
2332
|
+
"""
|
|
2333
|
+
Download Video with given ID asynchronously.
|
|
2334
|
+
|
|
2335
|
+
:param id: Video ID in Supervisely.
|
|
2336
|
+
:type id: int
|
|
2337
|
+
:param is_stream: If True, returns stream of bytes, otherwise returns response object.
|
|
2338
|
+
:type is_stream: bool, optional
|
|
2339
|
+
:param range_start: Start byte of range for partial download.
|
|
2340
|
+
:type range_start: int, optional
|
|
2341
|
+
:param range_end: End byte of range for partial download.
|
|
2342
|
+
:type range_end: int, optional
|
|
2343
|
+
:param headers: Headers for request.
|
|
2344
|
+
:type headers: dict, optional
|
|
2345
|
+
:param chunk_size: Size of chunk for partial download. Default is 1MB.
|
|
2346
|
+
:type chunk_size: int, optional
|
|
2347
|
+
:return: Stream of bytes or response object.
|
|
2348
|
+
:rtype: AsyncGenerator
|
|
2349
|
+
"""
|
|
2350
|
+
api_method_name = "videos.download"
|
|
2351
|
+
|
|
2352
|
+
json_body = {ApiField.ID: id}
|
|
2353
|
+
|
|
2354
|
+
if is_stream:
|
|
2355
|
+
async for chunk, hhash in self._api.stream_async(
|
|
2356
|
+
api_method_name,
|
|
2357
|
+
"POST",
|
|
2358
|
+
json_body,
|
|
2359
|
+
headers=headers,
|
|
2360
|
+
range_start=range_start,
|
|
2361
|
+
range_end=range_end,
|
|
2362
|
+
chunk_size=chunk_size,
|
|
2363
|
+
):
|
|
2364
|
+
yield chunk, hhash
|
|
2365
|
+
else:
|
|
2366
|
+
response = await self._api.post_async(api_method_name, json_body, headers=headers)
|
|
2367
|
+
yield response
|
|
2368
|
+
|
|
2369
|
+
async def download_path_async(
|
|
2370
|
+
self,
|
|
2371
|
+
id: int,
|
|
2372
|
+
path: str,
|
|
2373
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
2374
|
+
range_start: Optional[int] = None,
|
|
2375
|
+
range_end: Optional[int] = None,
|
|
2376
|
+
headers: Optional[dict] = None,
|
|
2377
|
+
chunk_size: int = 1024 * 1024,
|
|
2378
|
+
check_hash: bool = True,
|
|
2379
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
2380
|
+
progress_cb_type: Literal["number", "size"] = "number",
|
|
2381
|
+
) -> None:
|
|
2382
|
+
"""
|
|
2383
|
+
Downloads Video with given ID to local path.
|
|
2384
|
+
|
|
2385
|
+
:param id: Video ID in Supervisely.
|
|
2386
|
+
:type id: int
|
|
2387
|
+
:param path: Local save path for Video.
|
|
2388
|
+
:type path: str
|
|
2389
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
2390
|
+
:type semaphore: :class:`asyncio.Semaphore`, optional
|
|
2391
|
+
:param range_start: Start byte of range for partial download.
|
|
2392
|
+
:type range_start: int, optional
|
|
2393
|
+
:param range_end: End byte of range for partial download.
|
|
2394
|
+
:type range_end: int, optional
|
|
2395
|
+
:param headers: Headers for request.
|
|
2396
|
+
:type headers: dict, optional
|
|
2397
|
+
:param chunk_size: Size of chunk for partial download. Default is 1MB.
|
|
2398
|
+
:type chunk_size: int, optional
|
|
2399
|
+
:param check_hash: If True, checks hash of downloaded file.
|
|
2400
|
+
Check is not supported for partial downloads.
|
|
2401
|
+
When range is set, hash check is disabled.
|
|
2402
|
+
:type check_hash: bool, optional
|
|
2403
|
+
:param progress_cb: Function for tracking download progress.
|
|
2404
|
+
:type progress_cb: Optional[Union[tqdm, Callable]]
|
|
2405
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
|
|
2406
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
2407
|
+
:return: None
|
|
2408
|
+
:rtype: :class:`NoneType`
|
|
2409
|
+
:Usage example:
|
|
2410
|
+
|
|
2411
|
+
.. code-block:: python
|
|
2412
|
+
|
|
2413
|
+
import supervisely as sly
|
|
2414
|
+
import asyncio
|
|
2415
|
+
|
|
2416
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
2417
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
2418
|
+
api = sly.Api.from_env()
|
|
2419
|
+
|
|
2420
|
+
video_info = api.video.get_info_by_id(770918)
|
|
2421
|
+
save_path = os.path.join("/path/to/save/", video_info.name)
|
|
2422
|
+
|
|
2423
|
+
semaphore = asyncio.Semaphore(100)
|
|
2424
|
+
loop = asyncio.new_event_loop()
|
|
2425
|
+
asyncio.set_event_loop(loop)
|
|
2426
|
+
loop.run_until_complete(
|
|
2427
|
+
api.video.download_path_async(video_info.id, save_path, semaphore)
|
|
2428
|
+
)
|
|
2429
|
+
"""
|
|
2430
|
+
if range_start is not None or range_end is not None:
|
|
2431
|
+
check_hash = False # hash check is not supported for partial downloads
|
|
2432
|
+
headers = headers or {}
|
|
2433
|
+
headers["Range"] = f"bytes={range_start or ''}-{range_end or ''}"
|
|
2434
|
+
logger.debug(f"Image ID: {id}. Setting Range header: {headers['Range']}")
|
|
2435
|
+
|
|
2436
|
+
writing_method = "ab" if range_start not in [0, None] else "wb"
|
|
2437
|
+
|
|
2438
|
+
ensure_base_path(path)
|
|
2439
|
+
hash_to_check = None
|
|
2440
|
+
if semaphore is None:
|
|
2441
|
+
semaphore = self._api._get_default_semaphore()
|
|
2442
|
+
async with semaphore:
|
|
2443
|
+
async with aiofiles.open(path, writing_method) as fd:
|
|
2444
|
+
async for chunk, hhash in self._download_async(
|
|
2445
|
+
id,
|
|
2446
|
+
is_stream=True,
|
|
2447
|
+
range_start=range_start,
|
|
2448
|
+
range_end=range_end,
|
|
2449
|
+
headers=headers,
|
|
2450
|
+
chunk_size=chunk_size,
|
|
2451
|
+
):
|
|
2452
|
+
await fd.write(chunk)
|
|
2453
|
+
hash_to_check = hhash
|
|
2454
|
+
if progress_cb is not None and progress_cb_type == "size":
|
|
2455
|
+
progress_cb(len(chunk))
|
|
2456
|
+
if check_hash:
|
|
2457
|
+
if hash_to_check is not None:
|
|
2458
|
+
downloaded_file_hash = await get_file_hash_async(path)
|
|
2459
|
+
if hash_to_check != downloaded_file_hash:
|
|
2460
|
+
raise RuntimeError(
|
|
2461
|
+
f"Downloaded hash of video with ID:{id} does not match the expected hash: {downloaded_file_hash} != {hash_to_check}"
|
|
2462
|
+
)
|
|
2463
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
2464
|
+
progress_cb(1)
|
|
2465
|
+
|
|
2466
|
+
async def download_paths_async(
|
|
2467
|
+
self,
|
|
2468
|
+
ids: List[int],
|
|
2469
|
+
paths: List[str],
|
|
2470
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
2471
|
+
headers: Optional[dict] = None,
|
|
2472
|
+
chunk_size: int = 1024 * 1024,
|
|
2473
|
+
check_hash: bool = True,
|
|
2474
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
2475
|
+
progress_cb_type: Literal["number", "size"] = "number",
|
|
2476
|
+
) -> None:
|
|
2477
|
+
"""
|
|
2478
|
+
Download Videos with given IDs and saves them to given local paths asynchronously.
|
|
2479
|
+
|
|
2480
|
+
:param ids: List of Video IDs in Supervisely.
|
|
2481
|
+
:type ids: :class:`List[int]`
|
|
2482
|
+
:param paths: Local save paths for Videos.
|
|
2483
|
+
:type paths: :class:`List[str]`
|
|
2484
|
+
:param semaphore: Semaphore
|
|
2485
|
+
:type semaphore: :class:`asyncio.Semaphore`, optional
|
|
2486
|
+
:param headers: Headers for request.
|
|
2487
|
+
:type headers: dict, optional
|
|
2488
|
+
:param chunk_size: Size of chunk for partial download. Default is 1MB.
|
|
2489
|
+
:type chunk_size: int, optional
|
|
2490
|
+
:param check_hash: If True, checks hash of downloaded files.
|
|
2491
|
+
:type check_hash: bool, optional
|
|
2492
|
+
:param progress_cb: Function for tracking download progress.
|
|
2493
|
+
:type progress_cb: Optional[Union[tqdm, Callable]]
|
|
2494
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
|
|
2495
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
2496
|
+
:raises: :class:`ValueError` if len(ids) != len(paths)
|
|
2497
|
+
:return: None
|
|
2498
|
+
:rtype: :class:`NoneType`
|
|
2499
|
+
|
|
2500
|
+
:Usage example:
|
|
2501
|
+
|
|
2502
|
+
.. code-block:: python
|
|
2503
|
+
|
|
2504
|
+
import supervisely as sly
|
|
2505
|
+
import asyncio
|
|
2506
|
+
|
|
2507
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
2508
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
2509
|
+
api = sly.Api.from_env()
|
|
2510
|
+
|
|
2511
|
+
ids = [770914, 770915]
|
|
2512
|
+
paths = ["/path/to/save/video1.mp4", "/path/to/save/video2.mp4"]
|
|
2513
|
+
loop = asyncio.new_event_loop()
|
|
2514
|
+
asyncio.set_event_loop(loop)
|
|
2515
|
+
loop.run_until_complete(api.video.download_paths_async(ids, paths))
|
|
2516
|
+
"""
|
|
2517
|
+
if len(ids) == 0:
|
|
2518
|
+
return
|
|
2519
|
+
if len(ids) != len(paths):
|
|
2520
|
+
raise ValueError('Can not match "ids" and "paths" lists, len(ids) != len(paths)')
|
|
2521
|
+
if semaphore is None:
|
|
2522
|
+
semaphore = self._api._get_default_semaphore()
|
|
2523
|
+
tasks = []
|
|
2524
|
+
for img_id, img_path in zip(ids, paths):
|
|
2525
|
+
task = self.download_path_async(
|
|
2526
|
+
img_id,
|
|
2527
|
+
img_path,
|
|
2528
|
+
semaphore=semaphore,
|
|
2529
|
+
headers=headers,
|
|
2530
|
+
chunk_size=chunk_size,
|
|
2531
|
+
check_hash=check_hash,
|
|
2532
|
+
progress_cb=progress_cb,
|
|
2533
|
+
progress_cb_type=progress_cb_type,
|
|
2534
|
+
)
|
|
2535
|
+
|
|
2536
|
+
tasks.append(task)
|
|
2537
|
+
await asyncio.gather(*tasks)
|