supervisely 6.73.226__py3-none-any.whl → 6.73.227__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 +1 -1
- supervisely/_utils.py +23 -0
- supervisely/api/annotation_api.py +184 -14
- supervisely/api/api.py +2 -2
- supervisely/api/entity_annotation/figure_api.py +11 -2
- supervisely/api/file_api.py +144 -8
- supervisely/api/image_api.py +5 -10
- supervisely/api/pointcloud/pointcloud_api.py +4 -8
- 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/video_project.py +293 -3
- {supervisely-6.73.226.dist-info → supervisely-6.73.227.dist-info}/METADATA +1 -1
- {supervisely-6.73.226.dist-info → supervisely-6.73.227.dist-info}/RECORD +23 -23
- {supervisely-6.73.226.dist-info → supervisely-6.73.227.dist-info}/LICENSE +0 -0
- {supervisely-6.73.226.dist-info → supervisely-6.73.227.dist-info}/WHEEL +0 -0
- {supervisely-6.73.226.dist-info → supervisely-6.73.227.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.226.dist-info → supervisely-6.73.227.dist-info}/top_level.txt +0 -0
supervisely/__init__.py
CHANGED
|
@@ -55,7 +55,7 @@ from supervisely.task.progress import (
|
|
|
55
55
|
|
|
56
56
|
import supervisely.project as project
|
|
57
57
|
from supervisely.project import read_project, get_project_class
|
|
58
|
-
from supervisely.project.download import download
|
|
58
|
+
from supervisely.project.download import download, download_async
|
|
59
59
|
from supervisely.project.upload import upload
|
|
60
60
|
from supervisely.project.project import (
|
|
61
61
|
Project,
|
supervisely/_utils.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import base64
|
|
4
5
|
import copy
|
|
5
6
|
import hashlib
|
|
@@ -382,3 +383,25 @@ def add_callback(func, callback):
|
|
|
382
383
|
return res
|
|
383
384
|
|
|
384
385
|
return wrapper
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
|
|
389
|
+
"""
|
|
390
|
+
Get the current event loop or create a new one if it doesn't exist.
|
|
391
|
+
Works for different Python versions and contexts.
|
|
392
|
+
|
|
393
|
+
:return: Event loop
|
|
394
|
+
:rtype: asyncio.AbstractEventLoop
|
|
395
|
+
"""
|
|
396
|
+
try:
|
|
397
|
+
# Preferred method for asynchronous context (Python 3.7+)
|
|
398
|
+
return asyncio.get_running_loop()
|
|
399
|
+
except RuntimeError:
|
|
400
|
+
# If the loop is not running, get the current one or create a new one (Python 3.8 and 3.9)
|
|
401
|
+
try:
|
|
402
|
+
return asyncio.get_event_loop()
|
|
403
|
+
except RuntimeError:
|
|
404
|
+
# For Python 3.10+ or if the call occurs outside of an active loop context
|
|
405
|
+
loop = asyncio.new_event_loop()
|
|
406
|
+
asyncio.set_event_loop(loop)
|
|
407
|
+
return loop
|
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
# docs
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import asyncio
|
|
7
8
|
import json
|
|
8
9
|
from collections import defaultdict
|
|
9
10
|
from copy import deepcopy
|
|
10
|
-
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Union
|
|
11
|
+
from typing import Any, Callable, Dict, List, Literal, NamedTuple, Optional, Union
|
|
11
12
|
|
|
12
13
|
from tqdm import tqdm
|
|
13
14
|
|
|
@@ -1307,36 +1308,36 @@ class AnnotationApi(ModuleApi):
|
|
|
1307
1308
|
Priority increases with the number: a higher number indicates a higher priority.
|
|
1308
1309
|
The higher priority means that the label will be displayed on top of the others.
|
|
1309
1310
|
The lower priority means that the label will be displayed below the others.
|
|
1310
|
-
|
|
1311
|
+
|
|
1311
1312
|
:param label_id: ID of the label to update
|
|
1312
1313
|
:type label_id: int
|
|
1313
1314
|
:param priority: New priority of the label
|
|
1314
1315
|
:type priority: int
|
|
1315
|
-
|
|
1316
|
-
:Usage example:
|
|
1317
|
-
|
|
1316
|
+
|
|
1317
|
+
:Usage example:
|
|
1318
|
+
|
|
1318
1319
|
.. code-block:: python
|
|
1319
|
-
|
|
1320
|
+
|
|
1320
1321
|
import os
|
|
1321
1322
|
from dotenv import load_dotenv
|
|
1322
|
-
|
|
1323
|
+
|
|
1323
1324
|
import supervisely as sly
|
|
1324
|
-
|
|
1325
|
+
|
|
1325
1326
|
# Load secrets and create API object from .env file (recommended)
|
|
1326
1327
|
# Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
|
|
1327
1328
|
load_dotenv(os.path.expanduser("~/supervisely.env"))
|
|
1328
|
-
|
|
1329
|
+
|
|
1329
1330
|
api = sly.Api.from_env()
|
|
1330
|
-
|
|
1331
|
+
|
|
1331
1332
|
label_ids = [123, 456, 789]
|
|
1332
1333
|
priorities = [1, 2, 3]
|
|
1333
|
-
|
|
1334
|
+
|
|
1334
1335
|
for label_id, priority in zip(label_ids, priorities):
|
|
1335
1336
|
api.annotation.update_label_priority(label_id, priority)
|
|
1336
|
-
|
|
1337
|
+
|
|
1337
1338
|
# The label with ID 789 will be displayed on top of the others.
|
|
1338
1339
|
# The label with ID 123 will be displayed below the others.
|
|
1339
|
-
|
|
1340
|
+
|
|
1340
1341
|
"""
|
|
1341
1342
|
self._api.post(
|
|
1342
1343
|
"figures.priority.update",
|
|
@@ -1344,4 +1345,173 @@ class AnnotationApi(ModuleApi):
|
|
|
1344
1345
|
ApiField.ID: label_id,
|
|
1345
1346
|
ApiField.PRIORITY: priority,
|
|
1346
1347
|
},
|
|
1347
|
-
)
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
async def download_async(
|
|
1351
|
+
self,
|
|
1352
|
+
image_id: int,
|
|
1353
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1354
|
+
with_custom_data: Optional[bool] = False,
|
|
1355
|
+
force_metadata_for_links: Optional[bool] = True,
|
|
1356
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1357
|
+
progress_cb_type: Literal["number", "size"] = "number",
|
|
1358
|
+
) -> AnnotationInfo:
|
|
1359
|
+
"""
|
|
1360
|
+
Download AnnotationInfo by image ID from API.
|
|
1361
|
+
|
|
1362
|
+
:param image_id: Image ID in Supervisely.
|
|
1363
|
+
:type image_id: int
|
|
1364
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1365
|
+
:type semaphore: asyncio.Semaphore, optional
|
|
1366
|
+
:param with_custom_data: Include custom data in the response.
|
|
1367
|
+
:type with_custom_data: bool, optional
|
|
1368
|
+
:param force_metadata_for_links: Force metadata for links.
|
|
1369
|
+
:type force_metadata_for_links: bool, optional
|
|
1370
|
+
:param progress_cb: Function for tracking download progress.
|
|
1371
|
+
:type progress_cb: tqdm or callable, optional
|
|
1372
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
|
|
1373
|
+
:type progress_cb_type: str, optional
|
|
1374
|
+
:return: Information about Annotation. See :class:`info_sequence<info_sequence>`
|
|
1375
|
+
:rtype: :class:`AnnotationInfo`
|
|
1376
|
+
:Usage example:
|
|
1377
|
+
|
|
1378
|
+
.. code-block:: python
|
|
1379
|
+
|
|
1380
|
+
import supervisely as sly
|
|
1381
|
+
|
|
1382
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1383
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1384
|
+
api = sly.Api.from_env()
|
|
1385
|
+
|
|
1386
|
+
image_id = 121236918
|
|
1387
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1388
|
+
ann_info = loop.run_until_complete(api.annotation.download_async(image_id))
|
|
1389
|
+
"""
|
|
1390
|
+
if semaphore is None:
|
|
1391
|
+
semaphore = self._api._get_default_semaphore()
|
|
1392
|
+
async with semaphore:
|
|
1393
|
+
response = await self._api.post_async(
|
|
1394
|
+
"annotations.info",
|
|
1395
|
+
{
|
|
1396
|
+
ApiField.IMAGE_ID: image_id,
|
|
1397
|
+
ApiField.WITH_CUSTOM_DATA: with_custom_data,
|
|
1398
|
+
ApiField.FORCE_METADATA_FOR_LINKS: force_metadata_for_links,
|
|
1399
|
+
ApiField.INTEGER_COORDS: False,
|
|
1400
|
+
},
|
|
1401
|
+
)
|
|
1402
|
+
if progress_cb is not None and progress_cb_type == "size":
|
|
1403
|
+
progress_cb(len(response.content))
|
|
1404
|
+
|
|
1405
|
+
result = response.json()
|
|
1406
|
+
# Convert annotation to pixel coordinate system
|
|
1407
|
+
result[ApiField.ANNOTATION] = Annotation._to_pixel_coordinate_system_json(
|
|
1408
|
+
result[ApiField.ANNOTATION]
|
|
1409
|
+
)
|
|
1410
|
+
# check if there are any AlphaMask geometries in the batch
|
|
1411
|
+
additonal_geometries = defaultdict(int)
|
|
1412
|
+
labels = result[ApiField.ANNOTATION][AnnotationJsonFields.LABELS]
|
|
1413
|
+
for idx, label in enumerate(labels):
|
|
1414
|
+
if label[LabelJsonFields.GEOMETRY_TYPE] == AlphaMask.geometry_name():
|
|
1415
|
+
figure_id = label[LabelJsonFields.ID]
|
|
1416
|
+
additonal_geometries[figure_id] = idx
|
|
1417
|
+
|
|
1418
|
+
# if so, download them separately and update the annotation
|
|
1419
|
+
if len(additonal_geometries) > 0:
|
|
1420
|
+
figure_ids = list(additonal_geometries.keys())
|
|
1421
|
+
figures = self._api.image.figure.download_geometries_batch(
|
|
1422
|
+
figure_ids,
|
|
1423
|
+
(
|
|
1424
|
+
progress_cb
|
|
1425
|
+
if progress_cb is not None and progress_cb_type == "size"
|
|
1426
|
+
else None
|
|
1427
|
+
),
|
|
1428
|
+
)
|
|
1429
|
+
for figure_id, geometry in zip(figure_ids, figures):
|
|
1430
|
+
label_idx = additonal_geometries[figure_id]
|
|
1431
|
+
labels[label_idx].update({BITMAP: geometry})
|
|
1432
|
+
ann_info = self._convert_json_info(result)
|
|
1433
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
1434
|
+
progress_cb(1)
|
|
1435
|
+
return ann_info
|
|
1436
|
+
|
|
1437
|
+
async def download_batch_async(
|
|
1438
|
+
self,
|
|
1439
|
+
dataset_id: int,
|
|
1440
|
+
image_ids: List[int],
|
|
1441
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1442
|
+
with_custom_data: Optional[bool] = False,
|
|
1443
|
+
force_metadata_for_links: Optional[bool] = True,
|
|
1444
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1445
|
+
progress_cb_type: Literal["number", "size"] = "number",
|
|
1446
|
+
) -> List[AnnotationInfo]:
|
|
1447
|
+
"""
|
|
1448
|
+
Get list of AnnotationInfos for given dataset ID from API.
|
|
1449
|
+
|
|
1450
|
+
:param dataset_id: Dataset ID in Supervisely.
|
|
1451
|
+
:type dataset_id: int
|
|
1452
|
+
:param image_ids: List of integers.
|
|
1453
|
+
:type image_ids: List[int]
|
|
1454
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1455
|
+
:type semaphore: asyncio.Semaphore, optional
|
|
1456
|
+
:param with_custom_data: Include custom data in the response.
|
|
1457
|
+
:type with_custom_data: bool, optional
|
|
1458
|
+
:param force_metadata_for_links: Force metadata for links.
|
|
1459
|
+
:type force_metadata_for_links: bool, optional
|
|
1460
|
+
:param progress_cb: Function for tracking download progress. Total should be equal to len(image_ids) or None.
|
|
1461
|
+
:type progress_cb: tqdm or callable, optional
|
|
1462
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
|
|
1463
|
+
:type progress_cb_type: str, optional
|
|
1464
|
+
:return: Information about Annotations. See :class:`info_sequence<info_sequence>`
|
|
1465
|
+
:rtype: :class:`List[AnnotationInfo]`
|
|
1466
|
+
|
|
1467
|
+
:Usage example:
|
|
1468
|
+
|
|
1469
|
+
.. code-block:: python
|
|
1470
|
+
|
|
1471
|
+
import supervisely as sly
|
|
1472
|
+
|
|
1473
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1474
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1475
|
+
api = sly.Api.from_env()
|
|
1476
|
+
|
|
1477
|
+
dataset_id = 254737
|
|
1478
|
+
image_ids = [121236918, 121236919]
|
|
1479
|
+
pbar = tqdm(desc="Download annotations", total=len(image_ids))
|
|
1480
|
+
|
|
1481
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1482
|
+
ann_infos = loop.run_until_complete(
|
|
1483
|
+
api.annotation.download_batch_async(dataset_id, image_ids, progress_cb=pbar)
|
|
1484
|
+
)
|
|
1485
|
+
"""
|
|
1486
|
+
|
|
1487
|
+
# use context to avoid redundant API calls
|
|
1488
|
+
context = self._api.optimization_context
|
|
1489
|
+
context_dataset_id = context.get("dataset_id")
|
|
1490
|
+
project_meta = context.get("project_meta")
|
|
1491
|
+
project_id = context.get("project_id")
|
|
1492
|
+
if dataset_id != context_dataset_id:
|
|
1493
|
+
context["dataset_id"] = dataset_id
|
|
1494
|
+
project_id, project_meta = None, None
|
|
1495
|
+
|
|
1496
|
+
if not isinstance(project_meta, ProjectMeta):
|
|
1497
|
+
if project_id is None:
|
|
1498
|
+
project_id = self._api.dataset.get_info_by_id(dataset_id).project_id
|
|
1499
|
+
context["project_id"] = project_id
|
|
1500
|
+
project_meta = ProjectMeta.from_json(self._api.project.get_meta(project_id))
|
|
1501
|
+
context["project_meta"] = project_meta
|
|
1502
|
+
|
|
1503
|
+
if semaphore is None:
|
|
1504
|
+
semaphore = self._api._get_default_semaphore()
|
|
1505
|
+
tasks = []
|
|
1506
|
+
for image in image_ids:
|
|
1507
|
+
task = self.download_async(
|
|
1508
|
+
image_id=image,
|
|
1509
|
+
semaphore=semaphore,
|
|
1510
|
+
with_custom_data=with_custom_data,
|
|
1511
|
+
force_metadata_for_links=force_metadata_for_links,
|
|
1512
|
+
progress_cb=progress_cb,
|
|
1513
|
+
progress_cb_type=progress_cb_type,
|
|
1514
|
+
)
|
|
1515
|
+
tasks.append(task)
|
|
1516
|
+
ann_infos = await asyncio.gather(*tasks)
|
|
1517
|
+
return ann_infos
|
supervisely/api/api.py
CHANGED
|
@@ -1248,7 +1248,7 @@ class Api:
|
|
|
1248
1248
|
raise ValueError(
|
|
1249
1249
|
f"Streamed size does not match the expected: {total_streamed} != {expected_size}"
|
|
1250
1250
|
)
|
|
1251
|
-
logger.
|
|
1251
|
+
logger.trace(f"Streamed size: {total_streamed}, expected size: {expected_size}")
|
|
1252
1252
|
return
|
|
1253
1253
|
except httpx.RequestError as e:
|
|
1254
1254
|
retry_range_start = total_streamed + (range_start or 0)
|
|
@@ -1478,7 +1478,7 @@ class Api:
|
|
|
1478
1478
|
raise ValueError(
|
|
1479
1479
|
f"Streamed size does not match the expected: {total_streamed} != {expected_size}"
|
|
1480
1480
|
)
|
|
1481
|
-
logger.
|
|
1481
|
+
logger.trace(f"Streamed size: {total_streamed}, expected size: {expected_size}")
|
|
1482
1482
|
return
|
|
1483
1483
|
except httpx.RequestError as e:
|
|
1484
1484
|
retry_range_start = total_streamed + (range_start or 0)
|
|
@@ -6,10 +6,11 @@ from __future__ import annotations
|
|
|
6
6
|
import json
|
|
7
7
|
import re
|
|
8
8
|
from collections import defaultdict
|
|
9
|
-
from typing import Dict, Generator, List, NamedTuple, Optional, Tuple
|
|
9
|
+
from typing import Callable, Dict, Generator, List, NamedTuple, Optional, Tuple, Union
|
|
10
10
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
from requests_toolbelt import MultipartDecoder, MultipartEncoder
|
|
13
|
+
from tqdm import tqdm
|
|
13
14
|
|
|
14
15
|
from supervisely._utils import batched
|
|
15
16
|
from supervisely.api.module_api import ApiField, ModuleApi, RemoveableBulkModuleApi
|
|
@@ -540,17 +541,25 @@ class FigureApi(RemoveableBulkModuleApi):
|
|
|
540
541
|
"""
|
|
541
542
|
return self.download_geometries_batch([figure_id])
|
|
542
543
|
|
|
543
|
-
def download_geometries_batch(
|
|
544
|
+
def download_geometries_batch(
|
|
545
|
+
self,
|
|
546
|
+
ids: List[int],
|
|
547
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
548
|
+
) -> List[dict]:
|
|
544
549
|
"""
|
|
545
550
|
Download figure geometries with given IDs from storage.
|
|
546
551
|
|
|
547
552
|
:param ids: List of figure IDs in Supervisely.
|
|
548
553
|
:type ids: List[int]
|
|
554
|
+
:param progress_cb: Progress bar to show the download progress. Shows the number of bytes downloaded.
|
|
555
|
+
:type progress_cb: Union[tqdm, Callable], optional
|
|
549
556
|
:return: List of figure geometries in Supervisely JSON format.
|
|
550
557
|
:rtype: List[dict]
|
|
551
558
|
"""
|
|
552
559
|
geometries = {}
|
|
553
560
|
for idx, part in self._download_geometries_generator(ids):
|
|
561
|
+
if progress_cb is not None:
|
|
562
|
+
progress_cb(len(part.content))
|
|
554
563
|
geometry_json = json.loads(part.content)
|
|
555
564
|
geometries[idx] = geometry_json
|
|
556
565
|
|
supervisely/api/file_api.py
CHANGED
|
@@ -14,6 +14,7 @@ from time import time
|
|
|
14
14
|
from typing import Callable, Dict, List, NamedTuple, Optional, Union
|
|
15
15
|
|
|
16
16
|
import aiofiles
|
|
17
|
+
import httpx
|
|
17
18
|
import requests
|
|
18
19
|
from dotenv import load_dotenv
|
|
19
20
|
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
|
|
@@ -1641,8 +1642,7 @@ class FileApi(ModuleApiBase):
|
|
|
1641
1642
|
|
|
1642
1643
|
path_to_file = "/999_App_Test/ds1/01587.json"
|
|
1643
1644
|
local_save_path = "/path/to/save/999_App_Test/ds1/01587.json"
|
|
1644
|
-
loop =
|
|
1645
|
-
asyncio.set_event_loop(loop)
|
|
1645
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1646
1646
|
loop.run_until_complete(api.file.download_async(8, path_to_file, local_save_path))
|
|
1647
1647
|
"""
|
|
1648
1648
|
if semaphore is None:
|
|
@@ -1756,8 +1756,7 @@ class FileApi(ModuleApiBase):
|
|
|
1756
1756
|
"/path/to/save/999_App_Test/ds1/01588.json",
|
|
1757
1757
|
"/path/to/save/999_App_Test/ds1/01587.json"
|
|
1758
1758
|
]
|
|
1759
|
-
loop =
|
|
1760
|
-
asyncio.set_event_loop(loop)
|
|
1759
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1761
1760
|
loop.run_until_complete(
|
|
1762
1761
|
api.file.download_bulk_async(8, paths_to_files, local_paths)
|
|
1763
1762
|
)
|
|
@@ -1830,8 +1829,7 @@ class FileApi(ModuleApiBase):
|
|
|
1830
1829
|
path_to_dir = "/files/folder"
|
|
1831
1830
|
local_path = "path/to/local/folder"
|
|
1832
1831
|
|
|
1833
|
-
loop =
|
|
1834
|
-
asyncio.set_event_loop(loop)
|
|
1832
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1835
1833
|
loop.run_until_complete(
|
|
1836
1834
|
api.file.download_directory_async(9, path_to_dir, local_path)
|
|
1837
1835
|
)
|
|
@@ -1918,8 +1916,7 @@ class FileApi(ModuleApiBase):
|
|
|
1918
1916
|
|
|
1919
1917
|
# Application is started...
|
|
1920
1918
|
save_path = "/my_app_data"
|
|
1921
|
-
loop =
|
|
1922
|
-
asyncio.set_event_loop(loop)
|
|
1919
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1923
1920
|
loop.run_until_complete(
|
|
1924
1921
|
api.file.download_input_async(save_path)
|
|
1925
1922
|
)
|
|
@@ -2008,3 +2005,142 @@ class FileApi(ModuleApiBase):
|
|
|
2008
2005
|
semaphore=semaphore,
|
|
2009
2006
|
show_progress=show_progress,
|
|
2010
2007
|
)
|
|
2008
|
+
|
|
2009
|
+
async def upload_async(
|
|
2010
|
+
self,
|
|
2011
|
+
team_id: int,
|
|
2012
|
+
src: str,
|
|
2013
|
+
dst: str,
|
|
2014
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
2015
|
+
# chunk_size: int = 1024 * 1024, #TODO add with resumaple api
|
|
2016
|
+
# check_hash: bool = True, #TODO add with resumaple api
|
|
2017
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
2018
|
+
progress_cb_type: Literal["number", "size"] = "size",
|
|
2019
|
+
) -> httpx.Response:
|
|
2020
|
+
"""
|
|
2021
|
+
Upload file from local path to Team Files asynchronously.
|
|
2022
|
+
|
|
2023
|
+
:param team_id: Team ID in Supervisely.
|
|
2024
|
+
:type team_id: int
|
|
2025
|
+
:param src: Local path to file.
|
|
2026
|
+
:type src: str
|
|
2027
|
+
:param dst: Path to save file in Team Files.
|
|
2028
|
+
:type dst: str
|
|
2029
|
+
:param semaphore: Semaphore for limiting the number of simultaneous uploads.
|
|
2030
|
+
:type semaphore: asyncio.Semaphore, optional
|
|
2031
|
+
:param progress_cb: Function for tracking download progress.
|
|
2032
|
+
:type progress_cb: tqdm or callable, optional
|
|
2033
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "size".
|
|
2034
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
2035
|
+
:return: Response from API.
|
|
2036
|
+
:rtype: :class:`httpx.Response`
|
|
2037
|
+
:Usage example:
|
|
2038
|
+
|
|
2039
|
+
.. code-block:: python
|
|
2040
|
+
|
|
2041
|
+
import supervisely as sly
|
|
2042
|
+
import asyncio
|
|
2043
|
+
|
|
2044
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
2045
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
2046
|
+
api = sly.Api.from_env()
|
|
2047
|
+
|
|
2048
|
+
path_to_file = "/path/to/local/file/01587.json"
|
|
2049
|
+
path_to_save = "/files/01587.json"
|
|
2050
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
2051
|
+
loop.run_until_complete(
|
|
2052
|
+
api.file.upload_async(8, path_to_file, path_to_save)
|
|
2053
|
+
)
|
|
2054
|
+
"""
|
|
2055
|
+
api_method = "file-storage.upload"
|
|
2056
|
+
headers = {"Content-Type": "application/octet-stream"}
|
|
2057
|
+
# sha256 = await get_file_hash_async(src) #TODO add with resumaple api
|
|
2058
|
+
json_body = {
|
|
2059
|
+
ApiField.TEAM_ID: team_id,
|
|
2060
|
+
ApiField.PATH: dst,
|
|
2061
|
+
# "sha256": sha256, #TODO add with resumaple api
|
|
2062
|
+
}
|
|
2063
|
+
if semaphore is None:
|
|
2064
|
+
semaphore = self._api._get_default_semaphore()
|
|
2065
|
+
async with semaphore:
|
|
2066
|
+
async with aiofiles.open(src, "rb") as fd:
|
|
2067
|
+
item = await fd.read()
|
|
2068
|
+
response = await self._api.post_async(
|
|
2069
|
+
api_method, content=item, params=json_body, headers=headers
|
|
2070
|
+
)
|
|
2071
|
+
if progress_cb is not None and progress_cb_type == "size":
|
|
2072
|
+
progress_cb(len(item))
|
|
2073
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
2074
|
+
progress_cb(1)
|
|
2075
|
+
return response
|
|
2076
|
+
|
|
2077
|
+
async def upload_bulk_async(
|
|
2078
|
+
self,
|
|
2079
|
+
team_id: int,
|
|
2080
|
+
src_paths: List[str],
|
|
2081
|
+
dst_paths: List[str],
|
|
2082
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
2083
|
+
# chunk_size: int = 1024 * 1024, #TODO add with resumaple api
|
|
2084
|
+
# check_hash: bool = True, #TODO add with resumaple api
|
|
2085
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
2086
|
+
progress_cb_type: Literal["number", "size"] = "size",
|
|
2087
|
+
) -> None:
|
|
2088
|
+
"""
|
|
2089
|
+
Upload multiple files from local paths to Team Files asynchronously.
|
|
2090
|
+
|
|
2091
|
+
:param team_id: Team ID in Supervisely.
|
|
2092
|
+
:type team_id: int
|
|
2093
|
+
:param src_paths: List of local paths to files.
|
|
2094
|
+
:type src_paths: List[str]
|
|
2095
|
+
:param dst_paths: List of paths to save files in Team Files.
|
|
2096
|
+
:type dst_paths: List[str]
|
|
2097
|
+
:param semaphore: Semaphore for limiting the number of simultaneous uploads.
|
|
2098
|
+
:type semaphore: asyncio.Semaphore, optional
|
|
2099
|
+
:param progress_cb: Function for tracking download progress.
|
|
2100
|
+
:type progress_cb: tqdm or callable, optional
|
|
2101
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "size".
|
|
2102
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
2103
|
+
:return: None
|
|
2104
|
+
:rtype: :class:`NoneType`
|
|
2105
|
+
:Usage example:
|
|
2106
|
+
|
|
2107
|
+
.. code-block:: python
|
|
2108
|
+
|
|
2109
|
+
import supervisely as sly
|
|
2110
|
+
import asyncio
|
|
2111
|
+
|
|
2112
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
2113
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
2114
|
+
api = sly.Api.from_env()
|
|
2115
|
+
|
|
2116
|
+
paths_to_files = [
|
|
2117
|
+
"/path/to/local/file/01587.json",
|
|
2118
|
+
"/path/to/local/file/01588.json",
|
|
2119
|
+
"/path/to/local/file/01589.json"
|
|
2120
|
+
]
|
|
2121
|
+
paths_to_save = [
|
|
2122
|
+
"/files/01587.json",
|
|
2123
|
+
"/files/01588.json",
|
|
2124
|
+
"/files/01589.json"
|
|
2125
|
+
]
|
|
2126
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
2127
|
+
loop.run_until_complete(
|
|
2128
|
+
api.file.upload_bulk_async(8, paths_to_files, paths_to_save)
|
|
2129
|
+
)
|
|
2130
|
+
"""
|
|
2131
|
+
if semaphore is None:
|
|
2132
|
+
semaphore = self._api._get_default_semaphore()
|
|
2133
|
+
tasks = []
|
|
2134
|
+
for s, d in zip(src_paths, dst_paths):
|
|
2135
|
+
task = self.upload_async(
|
|
2136
|
+
team_id,
|
|
2137
|
+
s,
|
|
2138
|
+
d,
|
|
2139
|
+
semaphore=semaphore,
|
|
2140
|
+
# chunk_size=chunk_size, #TODO add with resumaple api
|
|
2141
|
+
# check_hash=check_hash, #TODO add with resumaple api
|
|
2142
|
+
progress_cb=progress_cb,
|
|
2143
|
+
progress_cb_type=progress_cb_type,
|
|
2144
|
+
)
|
|
2145
|
+
tasks.append(task)
|
|
2146
|
+
await asyncio.gather(*tasks)
|
supervisely/api/image_api.py
CHANGED
|
@@ -3604,8 +3604,7 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
3604
3604
|
semaphore = asyncio.Semaphore(100)
|
|
3605
3605
|
images = api.image.get_list(DATASET_ID)
|
|
3606
3606
|
img_ids = [image.id for image in images]
|
|
3607
|
-
loop =
|
|
3608
|
-
asyncio.set_event_loop(loop)
|
|
3607
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
3609
3608
|
results = loop.run_until_complete(
|
|
3610
3609
|
api.image.download_nps_async(img_ids, semaphore)
|
|
3611
3610
|
)
|
|
@@ -3671,8 +3670,7 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
3671
3670
|
save_path = os.path.join("/path/to/save/", img_info.name)
|
|
3672
3671
|
|
|
3673
3672
|
semaphore = asyncio.Semaphore(100)
|
|
3674
|
-
loop =
|
|
3675
|
-
asyncio.set_event_loop(loop)
|
|
3673
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
3676
3674
|
loop.run_until_complete(
|
|
3677
3675
|
api.image.download_path_async(img_info.id, save_path, semaphore)
|
|
3678
3676
|
)
|
|
@@ -3756,8 +3754,7 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
3756
3754
|
|
|
3757
3755
|
ids = [770918, 770919]
|
|
3758
3756
|
paths = ["/path/to/save/image1.png", "/path/to/save/image2.png"]
|
|
3759
|
-
loop =
|
|
3760
|
-
asyncio.set_event_loop(loop)
|
|
3757
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
3761
3758
|
loop.run_until_complete(api.image.download_paths_async(ids, paths))
|
|
3762
3759
|
"""
|
|
3763
3760
|
if len(ids) == 0:
|
|
@@ -3828,8 +3825,7 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
3828
3825
|
api = sly.Api.from_env()
|
|
3829
3826
|
|
|
3830
3827
|
img_id = 770918
|
|
3831
|
-
loop =
|
|
3832
|
-
asyncio.set_event_loop(loop)
|
|
3828
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
3833
3829
|
img_bytes = loop.run_until_complete(api.image.download_bytes_async(img_id))
|
|
3834
3830
|
|
|
3835
3831
|
"""
|
|
@@ -3906,8 +3902,7 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
3906
3902
|
os.environ['API_TOKEN
|
|
3907
3903
|
api = sly.Api.from_env()
|
|
3908
3904
|
|
|
3909
|
-
loop =
|
|
3910
|
-
asyncio.set_event_loop(loop)
|
|
3905
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
3911
3906
|
semaphore = asyncio.Semaphore(100)
|
|
3912
3907
|
img_bytes_list = loop.run_until_complete(api.image.download_bytes_imgs_async(ids, semaphore))
|
|
3913
3908
|
"""
|
|
@@ -1149,8 +1149,7 @@ class PointcloudApi(RemoveableBulkModuleApi):
|
|
|
1149
1149
|
save_path = os.path.join("/path/to/save/", pcd_info.name)
|
|
1150
1150
|
|
|
1151
1151
|
semaphore = asyncio.Semaphore(100)
|
|
1152
|
-
loop =
|
|
1153
|
-
asyncio.set_event_loop(loop)
|
|
1152
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1154
1153
|
loop.run_until_complete(
|
|
1155
1154
|
api.pointcloud.download_path_async(pcd_info.id, save_path, semaphore)
|
|
1156
1155
|
)
|
|
@@ -1241,8 +1240,7 @@ class PointcloudApi(RemoveableBulkModuleApi):
|
|
|
1241
1240
|
|
|
1242
1241
|
ids = [19373403, 19373404]
|
|
1243
1242
|
paths = ["/path/to/save/000063.pcd", "/path/to/save/000064.pcd"]
|
|
1244
|
-
loop =
|
|
1245
|
-
asyncio.set_event_loop(loop)
|
|
1243
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1246
1244
|
loop.run_until_complete(api.pointcloud.download_paths_async(ids, paths))
|
|
1247
1245
|
"""
|
|
1248
1246
|
if len(ids) == 0:
|
|
@@ -1313,8 +1311,7 @@ class PointcloudApi(RemoveableBulkModuleApi):
|
|
|
1313
1311
|
save_path = os.path.join("/path/to/save/", img_info.name)
|
|
1314
1312
|
|
|
1315
1313
|
semaphore = asyncio.Semaphore(100)
|
|
1316
|
-
loop =
|
|
1317
|
-
asyncio.set_event_loop(loop)
|
|
1314
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1318
1315
|
loop.run_until_complete(
|
|
1319
1316
|
api.pointcloud.download_related_image_async(19373403, save_path, semaphore)
|
|
1320
1317
|
)
|
|
@@ -1395,8 +1392,7 @@ class PointcloudApi(RemoveableBulkModuleApi):
|
|
|
1395
1392
|
save_paths = [os.path.join("/path/to/save/", img_info.name) for img_info in img_infos]
|
|
1396
1393
|
|
|
1397
1394
|
semaphore = asyncio.Semaphore(100)
|
|
1398
|
-
loop =
|
|
1399
|
-
asyncio.set_event_loop(loop)
|
|
1395
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
1400
1396
|
loop.run_until_complete(
|
|
1401
1397
|
api.pointcloud.download_related_images_async(ids, save_paths, semaphore)
|
|
1402
1398
|
)
|
|
@@ -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()
|