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.

@@ -4,6 +4,7 @@
4
4
  # docs
5
5
  from __future__ import annotations
6
6
 
7
+ import asyncio
7
8
  import io
8
9
  import json
9
10
  import re
@@ -16,6 +17,7 @@ from pathlib import Path
16
17
  from time import sleep
17
18
  from typing import (
18
19
  Any,
20
+ AsyncGenerator,
19
21
  Callable,
20
22
  Dict,
21
23
  Generator,
@@ -29,6 +31,7 @@ from typing import (
29
31
  )
30
32
  from uuid import uuid4
31
33
 
34
+ import aiofiles
32
35
  import numpy as np
33
36
  import requests
34
37
  from requests.exceptions import HTTPError
@@ -57,6 +60,7 @@ from supervisely.io.fs import (
57
60
  ensure_base_path,
58
61
  get_file_ext,
59
62
  get_file_hash,
63
+ get_file_hash_async,
60
64
  get_file_name,
61
65
  get_file_name_with_ext,
62
66
  list_files,
@@ -3450,3 +3454,468 @@ class ImageApi(RemoveableBulkModuleApi):
3450
3454
  data = {ApiField.IMAGES: images_list, ApiField.CLEAR_LOCAL_DATA_SOURCE: True}
3451
3455
  r = self._api.post("images.update.links", data)
3452
3456
  return r.json()
3457
+
3458
+ async def _download_async(
3459
+ self,
3460
+ id: int,
3461
+ is_stream: bool = False,
3462
+ range_start: Optional[int] = None,
3463
+ range_end: Optional[int] = None,
3464
+ headers: Optional[dict] = None,
3465
+ ) -> AsyncGenerator:
3466
+ """
3467
+ Download Image with given ID asynchronously.
3468
+ If is_stream is True, returns stream of bytes, otherwise returns response object.
3469
+ For streaming, returns tuple of chunk and hash.
3470
+
3471
+ :param id: Image ID in Supervisely.
3472
+ :type id: int
3473
+ :param is_stream: If True, returns stream of bytes, otherwise returns response object.
3474
+ :type is_stream: bool, optional
3475
+ :param range_start: Start byte of range for partial download.
3476
+ :type range_start: int, optional
3477
+ :param range_end: End byte of range for partial download.
3478
+ :type range_end: int, optional
3479
+ :param headers: Headers for request.
3480
+ :type headers: dict, optional
3481
+ :return: Stream of bytes or response object.
3482
+ :rtype: AsyncGenerator
3483
+ """
3484
+ api_method_name = "images.download"
3485
+
3486
+ json_body = {ApiField.ID: id}
3487
+
3488
+ if is_stream:
3489
+ async for chunk, hhash in self._api.stream_async(
3490
+ api_method_name,
3491
+ "POST",
3492
+ json_body,
3493
+ headers=headers,
3494
+ range_start=range_start,
3495
+ range_end=range_end,
3496
+ ):
3497
+ yield chunk, hhash
3498
+ else:
3499
+ response = await self._api.post_async(api_method_name, json_body, headers=headers)
3500
+ yield response
3501
+
3502
+ async def download_np_async(
3503
+ self,
3504
+ id: int,
3505
+ semaphore: Optional[asyncio.Semaphore] = None,
3506
+ keep_alpha: Optional[bool] = False,
3507
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
3508
+ progress_cb_type: Literal["number", "size"] = "number",
3509
+ ) -> np.ndarray:
3510
+ """
3511
+ Downloads Image with given ID in NumPy format asynchronously.
3512
+
3513
+ :param id: Image ID in Supervisely.
3514
+ :type id: int
3515
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
3516
+ :type semaphore: :class:`asyncio.Semaphore`, optional
3517
+ :param keep_alpha: If True keeps alpha mask for image, otherwise don't.
3518
+ :type keep_alpha: bool, optional
3519
+ :param progress_cb: Function for tracking download progress.
3520
+ :type progress_cb: tqdm or callable, optional
3521
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
3522
+ :type progress_cb_type: Literal["number", "size"], optional
3523
+ :return: Image in RGB numpy matrix format
3524
+ :rtype: :class:`np.ndarray`
3525
+
3526
+ :Usage example:
3527
+
3528
+ .. code-block:: python
3529
+
3530
+ import supervisely as sly
3531
+ import asyncio
3532
+ from tqdm.asyncio import tqdm
3533
+
3534
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
3535
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
3536
+ api = sly.Api.from_env()
3537
+
3538
+ DATASET_ID = 98357
3539
+ semaphore = asyncio.Semaphore(100)
3540
+ images = api.image.get_list(DATASET_ID)
3541
+ tasks = []
3542
+ pbar = tqdm(total=len(images), desc="Downloading images", unit="image")
3543
+ for image in images:
3544
+ task = api.image.download_np_async(image.id, semaphore, progress_cb=pbar)
3545
+ tasks.append(task)
3546
+ results = await asyncio.gather(*tasks)
3547
+ """
3548
+ if semaphore is None:
3549
+ semaphore = self._api._get_default_semaphore()
3550
+
3551
+ async with semaphore:
3552
+ async for response in self._download_async(id):
3553
+ img = sly_image.read_bytes(response.content, keep_alpha)
3554
+ if progress_cb is not None:
3555
+ if progress_cb_type == "number":
3556
+ progress_cb(1)
3557
+ elif progress_cb_type == "size":
3558
+ progress_cb(len(response.content))
3559
+ return img
3560
+
3561
+ async def download_nps_async(
3562
+ self,
3563
+ ids: List[int],
3564
+ semaphore: Optional[asyncio.Semaphore] = None,
3565
+ keep_alpha: Optional[bool] = False,
3566
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
3567
+ progress_cb_type: Literal["number", "size"] = "number",
3568
+ ) -> List[np.ndarray]:
3569
+ """
3570
+ Downloads Images with given IDs in NumPy format asynchronously.
3571
+
3572
+ :param ids: List of Image IDs in Supervisely.
3573
+ :type ids: :class:`List[int]`
3574
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
3575
+ :type semaphore: :class:`asyncio.Semaphore`, optional
3576
+ :param keep_alpha: If True keeps alpha mask for images, otherwise don't.
3577
+ :type keep_alpha: bool, optional
3578
+ :param progress_cb: Function for tracking download progress.
3579
+ :type progress_cb: tqdm or callable, optional
3580
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
3581
+ :type progress_cb_type: Literal["number", "size"], optional
3582
+ :return: List of Images in RGB numpy matrix format
3583
+ :rtype: :class:`List[np.ndarray]`
3584
+
3585
+ :Usage example:
3586
+
3587
+ .. code-block:: python
3588
+
3589
+ import supervisely as sly
3590
+ import asyncio
3591
+
3592
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
3593
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
3594
+ api = sly.Api.from_env()
3595
+
3596
+ DATASET_ID = 98357
3597
+ semaphore = asyncio.Semaphore(100)
3598
+ images = api.image.get_list(DATASET_ID)
3599
+ img_ids = [image.id for image in images]
3600
+ loop = asyncio.new_event_loop()
3601
+ asyncio.set_event_loop(loop)
3602
+ results = loop.run_until_complete(
3603
+ api.image.download_nps_async(img_ids, semaphore)
3604
+ )
3605
+
3606
+ """
3607
+ if semaphore is None:
3608
+ semaphore = self._api._get_default_semaphore()
3609
+ tasks = [
3610
+ self.download_np_async(id, semaphore, keep_alpha, progress_cb, progress_cb_type)
3611
+ for id in ids
3612
+ ]
3613
+ return await asyncio.gather(*tasks)
3614
+
3615
+ async def download_path_async(
3616
+ self,
3617
+ id: int,
3618
+ path: str,
3619
+ semaphore: Optional[asyncio.Semaphore] = None,
3620
+ range_start: Optional[int] = None,
3621
+ range_end: Optional[int] = None,
3622
+ headers: Optional[dict] = None,
3623
+ check_hash: bool = True,
3624
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
3625
+ progress_cb_type: Literal["number", "size"] = "number",
3626
+ ) -> None:
3627
+ """
3628
+ Downloads Image with given ID to local path.
3629
+
3630
+ :param id: Image ID in Supervisely.
3631
+ :type id: int
3632
+ :param path: Local save path for Image.
3633
+ :type path: str
3634
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
3635
+ :type semaphore: :class:`asyncio.Semaphore`, optional
3636
+ :param range_start: Start byte of range for partial download.
3637
+ :type range_start: int, optional
3638
+ :param range_end: End byte of range for partial download.
3639
+ :type range_end: int, optional
3640
+ :param headers: Headers for request.
3641
+ :type headers: dict, optional
3642
+ :param check_hash: If True, checks hash of downloaded file.
3643
+ Check is not supported for partial downloads.
3644
+ When range is set, hash check is disabled.
3645
+ :type check_hash: bool, optional
3646
+ :param progress_cb: Function for tracking download progress.
3647
+ :type progress_cb: tqdm or callable, optional
3648
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
3649
+ :type progress_cb_type: Literal["number", "size"], optional
3650
+ :return: None
3651
+ :rtype: :class:`NoneType`
3652
+ :Usage example:
3653
+
3654
+ .. code-block:: python
3655
+
3656
+ import supervisely as sly
3657
+ import asyncio
3658
+
3659
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
3660
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
3661
+ api = sly.Api.from_env()
3662
+
3663
+ img_info = api.image.get_info_by_id(770918)
3664
+ save_path = os.path.join("/path/to/save/", img_info.name)
3665
+
3666
+ semaphore = asyncio.Semaphore(100)
3667
+ loop = asyncio.new_event_loop()
3668
+ asyncio.set_event_loop(loop)
3669
+ loop.run_until_complete(
3670
+ api.image.download_path_async(img_info.id, save_path, semaphore)
3671
+ )
3672
+ """
3673
+ if range_start is not None or range_end is not None:
3674
+ check_hash = False # hash check is not supported for partial downloads
3675
+ headers = headers or {}
3676
+ headers["Range"] = f"bytes={range_start or ''}-{range_end or ''}"
3677
+ logger.debug(f"Image ID: {id}. Setting Range header: {headers['Range']}")
3678
+
3679
+ writing_method = "ab" if range_start not in [0, None] else "wb"
3680
+
3681
+ ensure_base_path(path)
3682
+ hash_to_check = None
3683
+ if semaphore is None:
3684
+ semaphore = self._api._get_default_semaphore()
3685
+ async with semaphore:
3686
+ async with aiofiles.open(path, writing_method) as fd:
3687
+ async for chunk, hhash in self._download_async(
3688
+ id,
3689
+ is_stream=True,
3690
+ range_start=range_start,
3691
+ range_end=range_end,
3692
+ headers=headers,
3693
+ ):
3694
+ await fd.write(chunk)
3695
+ hash_to_check = hhash
3696
+ if progress_cb is not None and progress_cb_type == "size":
3697
+ progress_cb(len(chunk))
3698
+ if progress_cb is not None and progress_cb_type == "number":
3699
+ progress_cb(1)
3700
+ if check_hash:
3701
+ if hash_to_check is not None:
3702
+ downloaded_file_hash = await get_file_hash_async(path)
3703
+ if hash_to_check != downloaded_file_hash:
3704
+ raise RuntimeError(
3705
+ f"Downloaded hash of image with ID:{id} does not match the expected hash: {downloaded_file_hash} != {hash_to_check}"
3706
+ )
3707
+
3708
+ async def download_paths_async(
3709
+ self,
3710
+ ids: List[int],
3711
+ paths: List[str],
3712
+ semaphore: Optional[asyncio.Semaphore] = None,
3713
+ headers: Optional[dict] = None,
3714
+ check_hash: bool = True,
3715
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
3716
+ progress_cb_type: Literal["number", "size"] = "number",
3717
+ ) -> None:
3718
+ """
3719
+ Download Images with given IDs and saves them to given local paths asynchronously.
3720
+
3721
+ :param ids: List of Image IDs in Supervisely.
3722
+ :type ids: :class:`List[int]`
3723
+ :param paths: Local save paths for Images.
3724
+ :type paths: :class:`List[str]`
3725
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
3726
+ :type semaphore: :class:`asyncio.Semaphore`, optional
3727
+ :param headers: Headers for request.
3728
+ :type headers: dict, optional
3729
+ :param check_hash: If True, checks hash of downloaded images.
3730
+ :type check_hash: bool, optional
3731
+ :param progress_cb: Function for tracking download progress.
3732
+ :type progress_cb: tqdm or callable, optional
3733
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
3734
+ :type progress_cb_type: Literal["number", "size"], optional
3735
+ :raises: :class:`ValueError` if len(ids) != len(paths)
3736
+ :return: None
3737
+ :rtype: :class:`NoneType`
3738
+
3739
+ :Usage example:
3740
+
3741
+ .. code-block:: python
3742
+
3743
+ import supervisely as sly
3744
+ import asyncio
3745
+
3746
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
3747
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
3748
+ api = sly.Api.from_env()
3749
+
3750
+ ids = [770918, 770919]
3751
+ paths = ["/path/to/save/image1.png", "/path/to/save/image2.png"]
3752
+ loop = asyncio.new_event_loop()
3753
+ asyncio.set_event_loop(loop)
3754
+ loop.run_until_complete(api.image.download_paths_async(ids, paths))
3755
+ """
3756
+ if len(ids) == 0:
3757
+ return
3758
+ if len(ids) != len(paths):
3759
+ raise ValueError(
3760
+ f'Length of "ids" and "paths" should be equal. {len(ids)} != {len(paths)}'
3761
+ )
3762
+ if semaphore is None:
3763
+ semaphore = self._api._get_default_semaphore()
3764
+ tasks = []
3765
+ for img_id, img_path in zip(ids, paths):
3766
+ task = self.download_path_async(
3767
+ img_id,
3768
+ img_path,
3769
+ semaphore,
3770
+ headers=headers,
3771
+ check_hash=check_hash,
3772
+ progress_cb=progress_cb,
3773
+ progress_cb_type=progress_cb_type,
3774
+ )
3775
+ tasks.append(task)
3776
+ await asyncio.gather(*tasks)
3777
+
3778
+ async def download_bytes_single_async(
3779
+ self,
3780
+ id: int,
3781
+ semaphore: Optional[asyncio.Semaphore] = None,
3782
+ range_start: Optional[int] = None,
3783
+ range_end: Optional[int] = None,
3784
+ headers: Optional[dict] = None,
3785
+ check_hash: bool = True,
3786
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
3787
+ progress_cb_type: Literal["number", "size"] = "number",
3788
+ ) -> bytes:
3789
+ """
3790
+ Downloads Image bytes with given ID.
3791
+
3792
+ :param id: Image ID in Supervisely.
3793
+ :type id: int
3794
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
3795
+ :type semaphore: :class:`asyncio.Semaphore`, optional
3796
+ :param range_start: Start byte of range for partial download.
3797
+ :type range_start: int, optional
3798
+ :param range_end: End byte of range for partial download.
3799
+ :type range_end: int, optional
3800
+ :param headers: Headers for request.
3801
+ :type headers: dict, optional
3802
+ :param check_hash: If True, checks hash of downloaded bytes.
3803
+ Check is not supported for partial downloads.
3804
+ When range is set, hash check is disabled.
3805
+ :type check_hash: bool, optional
3806
+ :param progress_cb: Function for tracking download progress.
3807
+ :type progress_cb: Optional[Union[tqdm, Callable]]
3808
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
3809
+ :type progress_cb_type: Literal["number", "size"], optional
3810
+ :return: Bytes of downloaded image.
3811
+ :rtype: :class:`bytes`
3812
+ :Usage example:
3813
+
3814
+ .. code-block:: python
3815
+
3816
+ import supervisely as sly
3817
+ import asyncio
3818
+
3819
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
3820
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
3821
+ api = sly.Api.from_env()
3822
+
3823
+ img_id = 770918
3824
+ loop = asyncio.new_event_loop()
3825
+ asyncio.set_event_loop(loop)
3826
+ img_bytes = loop.run_until_complete(api.image.download_bytes_async(img_id))
3827
+
3828
+ """
3829
+ if range_start is not None or range_end is not None:
3830
+ check_hash = False # hash check is not supported for partial downloads
3831
+ headers = headers or {}
3832
+ headers["Range"] = f"bytes={range_start or ''}-{range_end or ''}"
3833
+ logger.debug(f"Image ID: {id}. Setting Range header: {headers['Range']}")
3834
+
3835
+ hash_to_check = None
3836
+
3837
+ if semaphore is None:
3838
+ semaphore = self._api._get_default_semaphore()
3839
+ async with semaphore:
3840
+ content = b""
3841
+ async for chunk, hhash in self._download_async(
3842
+ id,
3843
+ is_stream=True,
3844
+ headers=headers,
3845
+ range_start=range_start,
3846
+ range_end=range_end,
3847
+ ):
3848
+ content += chunk
3849
+ hash_to_check = hhash
3850
+ if progress_cb is not None and progress_cb_type == "size":
3851
+ progress_cb(len(chunk))
3852
+ if check_hash:
3853
+ if hash_to_check is not None:
3854
+ downloaded_bytes_hash = get_bytes_hash(content)
3855
+ if hash_to_check != downloaded_bytes_hash:
3856
+ raise RuntimeError(
3857
+ f"Downloaded hash of image with ID:{id} does not match the expected hash: {downloaded_bytes_hash} != {hash_to_check}"
3858
+ )
3859
+ if progress_cb is not None and progress_cb_type == "number":
3860
+ progress_cb(1)
3861
+ return content
3862
+
3863
+ async def download_bytes_many_async(
3864
+ self,
3865
+ ids: List[int],
3866
+ semaphore: Optional[asyncio.Semaphore] = None,
3867
+ headers: Optional[dict] = None,
3868
+ check_hash: bool = True,
3869
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
3870
+ progress_cb_type: Literal["number", "size"] = "number",
3871
+ ) -> List[bytes]:
3872
+ """
3873
+ Downloads Images bytes with given IDs asynchronously
3874
+ and returns reults in the same order as in the input list.
3875
+
3876
+ :param ids: List of Image IDs in Supervisely.
3877
+ :type ids: :class:`List[int]`
3878
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
3879
+ :type semaphore: :class:`asyncio.Semaphore`, optional
3880
+ :param headers: Headers for every request.
3881
+ :type headers: dict, optional
3882
+ :param check_hash: If True, checks hash of downloaded images.
3883
+ :type check_hash: bool, optional
3884
+ :param progress_cb: Function for tracking download progress.
3885
+ :type progress_cb: Optional[Union[tqdm, Callable]]
3886
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
3887
+ :type progress_cb_type: Literal["number", "size"], optional
3888
+ :return: List of bytes of downloaded images.
3889
+ :rtype: :class:`List[bytes]`
3890
+
3891
+ :Usage example:
3892
+
3893
+ .. code-block:: python
3894
+
3895
+ import supervisely as sly
3896
+ import asyncio
3897
+
3898
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
3899
+ os.environ['API_TOKEN
3900
+ api = sly.Api.from_env()
3901
+
3902
+ loop = asyncio.new_event_loop()
3903
+ asyncio.set_event_loop(loop)
3904
+ semaphore = asyncio.Semaphore(100)
3905
+ img_bytes_list = loop.run_until_complete(api.image.download_bytes_imgs_async(ids, semaphore))
3906
+ """
3907
+ if semaphore is None:
3908
+ semaphore = self._api._get_default_semaphore()
3909
+ tasks = []
3910
+ for id in ids:
3911
+ task = self.download_bytes_single_async(
3912
+ id,
3913
+ semaphore,
3914
+ headers=headers,
3915
+ check_hash=check_hash,
3916
+ progress_cb=progress_cb,
3917
+ progress_cb_type=progress_cb_type,
3918
+ )
3919
+ tasks.append(task)
3920
+ results = await asyncio.gather(*tasks)
3921
+ return results