supervisely 6.73.247__py3-none-any.whl → 6.73.248__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/annotation_api.py +130 -1
- supervisely/api/api.py +1 -1
- supervisely/api/entity_annotation/figure_api.py +90 -1
- supervisely/api/image_api.py +186 -1
- supervisely/api/module_api.py +95 -1
- supervisely/nn/benchmark/utils/semantic_segmentation/utils.py +4 -1
- supervisely/nn/tracker/deep_sort/deep_sort/kalman_filter.py +4 -2
- supervisely/nn/tracker/utils/kalman_filter.py +8 -1
- supervisely/project/download.py +39 -19
- supervisely/project/project.py +173 -23
- {supervisely-6.73.247.dist-info → supervisely-6.73.248.dist-info}/METADATA +1 -1
- {supervisely-6.73.247.dist-info → supervisely-6.73.248.dist-info}/RECORD +16 -16
- {supervisely-6.73.247.dist-info → supervisely-6.73.248.dist-info}/LICENSE +0 -0
- {supervisely-6.73.247.dist-info → supervisely-6.73.248.dist-info}/WHEEL +0 -0
- {supervisely-6.73.247.dist-info → supervisely-6.73.248.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.247.dist-info → supervisely-6.73.248.dist-info}/top_level.txt +0 -0
|
@@ -1418,13 +1418,14 @@ class AnnotationApi(ModuleApi):
|
|
|
1418
1418
|
# if so, download them separately and update the annotation
|
|
1419
1419
|
if len(additonal_geometries) > 0:
|
|
1420
1420
|
figure_ids = list(additonal_geometries.keys())
|
|
1421
|
-
figures = self._api.image.figure.
|
|
1421
|
+
figures = await self._api.image.figure.download_geometries_batch_async(
|
|
1422
1422
|
figure_ids,
|
|
1423
1423
|
(
|
|
1424
1424
|
progress_cb
|
|
1425
1425
|
if progress_cb is not None and progress_cb_type == "size"
|
|
1426
1426
|
else None
|
|
1427
1427
|
),
|
|
1428
|
+
semaphore=semaphore,
|
|
1428
1429
|
)
|
|
1429
1430
|
for figure_id, geometry in zip(figure_ids, figures):
|
|
1430
1431
|
label_idx = additonal_geometries[figure_id]
|
|
@@ -1515,3 +1516,131 @@ class AnnotationApi(ModuleApi):
|
|
|
1515
1516
|
tasks.append(task)
|
|
1516
1517
|
ann_infos = await asyncio.gather(*tasks)
|
|
1517
1518
|
return ann_infos
|
|
1519
|
+
|
|
1520
|
+
async def download_bulk_async(
|
|
1521
|
+
self,
|
|
1522
|
+
dataset_id: int,
|
|
1523
|
+
image_ids: List[int],
|
|
1524
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1525
|
+
with_custom_data: Optional[bool] = False,
|
|
1526
|
+
force_metadata_for_links: Optional[bool] = True,
|
|
1527
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1528
|
+
) -> List[AnnotationInfo]:
|
|
1529
|
+
"""
|
|
1530
|
+
Get list of AnnotationInfos for given dataset ID from API.
|
|
1531
|
+
This method is optimized for downloading a large number of small size annotations with a single API call.
|
|
1532
|
+
|
|
1533
|
+
:param dataset_id: Dataset ID in Supervisely.
|
|
1534
|
+
:type dataset_id: int
|
|
1535
|
+
:param image_ids: List of integers.
|
|
1536
|
+
:type image_ids: List[int]
|
|
1537
|
+
:param progress_cb: Function for tracking download progress.
|
|
1538
|
+
:type progress_cb: tqdm
|
|
1539
|
+
:param with_custom_data: Include custom data in the response.
|
|
1540
|
+
:type with_custom_data: bool, optional
|
|
1541
|
+
:param force_metadata_for_links: Force metadata for links.
|
|
1542
|
+
:type force_metadata_for_links: bool, optional
|
|
1543
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1544
|
+
:type semaphore: asyncio.Semaphore, optional
|
|
1545
|
+
:return: Information about Annotations. See :class:`info_sequence<info_sequence>`
|
|
1546
|
+
:rtype: :class:`List[AnnotationInfo]`
|
|
1547
|
+
|
|
1548
|
+
:Usage example:
|
|
1549
|
+
|
|
1550
|
+
.. code-block:: python
|
|
1551
|
+
|
|
1552
|
+
import supervisely as sly
|
|
1553
|
+
|
|
1554
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1555
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1556
|
+
api = sly.Api.from_env()
|
|
1557
|
+
|
|
1558
|
+
dataset_id = 254737
|
|
1559
|
+
image_ids = [121236918, 121236919]
|
|
1560
|
+
p = tqdm(desc="Annotations downloaded: ", total=len(image_ids))
|
|
1561
|
+
|
|
1562
|
+
ann_infos = await api.annotation.download_bulk_async(dataset_id, image_ids, progress_cb=p)
|
|
1563
|
+
|
|
1564
|
+
Optimizing the download process by using the context to avoid redundant API calls.:
|
|
1565
|
+
# 1. Download the project meta
|
|
1566
|
+
project_id = api.dataset.get_info_by_id(dataset_id).project_id
|
|
1567
|
+
project_meta = api.project.get_meta(project_id)
|
|
1568
|
+
|
|
1569
|
+
# 2. Use the context to avoid redundant API calls
|
|
1570
|
+
dataset_id = 254737
|
|
1571
|
+
image_ids = [121236918, 121236919]
|
|
1572
|
+
with sly.ApiContext(api, dataset_id=dataset_id, project_id=project_id, project_meta=project_meta):
|
|
1573
|
+
ann_infos = await api.annotation.download_bulk_async(dataset_id, image_ids)
|
|
1574
|
+
"""
|
|
1575
|
+
if semaphore is None:
|
|
1576
|
+
semaphore = self._api.get_default_semaphore()
|
|
1577
|
+
|
|
1578
|
+
# use context to avoid redundant API calls
|
|
1579
|
+
context = self._api.optimization_context
|
|
1580
|
+
context_dataset_id = context.get("dataset_id")
|
|
1581
|
+
project_meta = context.get("project_meta")
|
|
1582
|
+
project_id = context.get("project_id")
|
|
1583
|
+
if dataset_id != context_dataset_id:
|
|
1584
|
+
context["dataset_id"] = dataset_id
|
|
1585
|
+
project_id, project_meta = None, None
|
|
1586
|
+
|
|
1587
|
+
if not isinstance(project_meta, ProjectMeta):
|
|
1588
|
+
if project_id is None:
|
|
1589
|
+
project_id = self._api.dataset.get_info_by_id(dataset_id).project_id
|
|
1590
|
+
context["project_id"] = project_id
|
|
1591
|
+
project_meta = ProjectMeta.from_json(self._api.project.get_meta(project_id))
|
|
1592
|
+
context["project_meta"] = project_meta
|
|
1593
|
+
|
|
1594
|
+
need_download_alpha_masks = False
|
|
1595
|
+
for obj_cls in project_meta.obj_classes:
|
|
1596
|
+
if obj_cls.geometry_type == AlphaMask:
|
|
1597
|
+
need_download_alpha_masks = True
|
|
1598
|
+
break
|
|
1599
|
+
|
|
1600
|
+
id_to_ann = {}
|
|
1601
|
+
for batch in batched(image_ids):
|
|
1602
|
+
json_data = {
|
|
1603
|
+
ApiField.DATASET_ID: dataset_id,
|
|
1604
|
+
ApiField.IMAGE_IDS: batch,
|
|
1605
|
+
ApiField.WITH_CUSTOM_DATA: with_custom_data,
|
|
1606
|
+
ApiField.FORCE_METADATA_FOR_LINKS: force_metadata_for_links,
|
|
1607
|
+
ApiField.INTEGER_COORDS: False,
|
|
1608
|
+
}
|
|
1609
|
+
async with semaphore:
|
|
1610
|
+
results = await self._api.post_async("annotations.bulk.info", json=json_data)
|
|
1611
|
+
results = results.json()
|
|
1612
|
+
if need_download_alpha_masks is True:
|
|
1613
|
+
additonal_geometries = defaultdict(tuple)
|
|
1614
|
+
for ann_idx, ann_dict in enumerate(results):
|
|
1615
|
+
# check if there are any AlphaMask geometries in the batch
|
|
1616
|
+
for label_idx, label in enumerate(
|
|
1617
|
+
ann_dict[ApiField.ANNOTATION][AnnotationJsonFields.LABELS]
|
|
1618
|
+
):
|
|
1619
|
+
if label[LabelJsonFields.GEOMETRY_TYPE] == AlphaMask.geometry_name():
|
|
1620
|
+
figure_id = label[LabelJsonFields.ID]
|
|
1621
|
+
additonal_geometries[figure_id] = (ann_idx, label_idx)
|
|
1622
|
+
|
|
1623
|
+
# if there are any AlphaMask geometries, download them separately and update the annotation
|
|
1624
|
+
if len(additonal_geometries) > 0:
|
|
1625
|
+
figure_ids = list(additonal_geometries.keys())
|
|
1626
|
+
figures = await self._api.image.figure.download_geometries_batch_async(
|
|
1627
|
+
figure_ids, semaphore=semaphore
|
|
1628
|
+
)
|
|
1629
|
+
for figure_id, geometry in zip(figure_ids, figures):
|
|
1630
|
+
ann_idx, label_idx = additonal_geometries[figure_id]
|
|
1631
|
+
results[ann_idx][ApiField.ANNOTATION][AnnotationJsonFields.LABELS][
|
|
1632
|
+
label_idx
|
|
1633
|
+
].update({BITMAP: geometry})
|
|
1634
|
+
|
|
1635
|
+
for ann_dict in results:
|
|
1636
|
+
# Convert annotation to pixel coordinate system
|
|
1637
|
+
ann_dict[ApiField.ANNOTATION] = Annotation._to_pixel_coordinate_system_json(
|
|
1638
|
+
ann_dict[ApiField.ANNOTATION]
|
|
1639
|
+
)
|
|
1640
|
+
ann_info = self._convert_json_info(ann_dict)
|
|
1641
|
+
id_to_ann[ann_info.image_id] = ann_info
|
|
1642
|
+
|
|
1643
|
+
if progress_cb is not None:
|
|
1644
|
+
progress_cb(len(batch))
|
|
1645
|
+
ordered_results = [id_to_ann[image_id] for image_id in image_ids]
|
|
1646
|
+
return ordered_results
|
supervisely/api/api.py
CHANGED
|
@@ -1437,7 +1437,7 @@ class Api:
|
|
|
1437
1437
|
:type range_start: int, optional
|
|
1438
1438
|
:param range_end: End byte position for streaming.
|
|
1439
1439
|
:type range_end: int, optional
|
|
1440
|
-
:param chunk_size: Size of the chunk to read from the stream.
|
|
1440
|
+
:param chunk_size: Size of the chunk to read from the stream. Default is 8192.
|
|
1441
1441
|
:type chunk_size: int, optional
|
|
1442
1442
|
:param use_public_api: Define if public API should be used.
|
|
1443
1443
|
:type use_public_api: bool, optional
|
|
@@ -3,10 +3,21 @@
|
|
|
3
3
|
# docs
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import asyncio
|
|
6
7
|
import json
|
|
7
8
|
import re
|
|
8
9
|
from collections import defaultdict
|
|
9
|
-
from typing import
|
|
10
|
+
from typing import (
|
|
11
|
+
AsyncGenerator,
|
|
12
|
+
Callable,
|
|
13
|
+
Dict,
|
|
14
|
+
Generator,
|
|
15
|
+
List,
|
|
16
|
+
NamedTuple,
|
|
17
|
+
Optional,
|
|
18
|
+
Tuple,
|
|
19
|
+
Union,
|
|
20
|
+
)
|
|
10
21
|
|
|
11
22
|
import numpy as np
|
|
12
23
|
from requests_toolbelt import MultipartDecoder, MultipartEncoder
|
|
@@ -608,3 +619,81 @@ class FigureApi(RemoveableBulkModuleApi):
|
|
|
608
619
|
)
|
|
609
620
|
encoder = MultipartEncoder(fields=fields)
|
|
610
621
|
self._api.post("figures.bulk.upload.geometry", encoder)
|
|
622
|
+
|
|
623
|
+
async def _download_geometries_generator_async(
|
|
624
|
+
self, ids: List[int], semaphore: Optional[asyncio.Semaphore] = None
|
|
625
|
+
) -> AsyncGenerator[Tuple[int, bytes], None, None]:
|
|
626
|
+
"""
|
|
627
|
+
Private method. Download figures geometries with given IDs from storage asynchronously.
|
|
628
|
+
|
|
629
|
+
:param ids: List of figure IDs in Supervisely.
|
|
630
|
+
:type ids: List[int]
|
|
631
|
+
:return: Async generator with pairs of figure ID and figure geometry.
|
|
632
|
+
:rtype: AsyncGenerator[Tuple[int, bytes], None, None]
|
|
633
|
+
"""
|
|
634
|
+
if semaphore is None:
|
|
635
|
+
semaphore = self._api.get_default_semaphore()
|
|
636
|
+
|
|
637
|
+
for batch_ids in batched(ids):
|
|
638
|
+
async with semaphore:
|
|
639
|
+
response = await self._api.post_async(
|
|
640
|
+
"figures.bulk.download.geometry", {ApiField.IDS: batch_ids}
|
|
641
|
+
)
|
|
642
|
+
decoder = MultipartDecoder.from_response(response)
|
|
643
|
+
for part in decoder.parts:
|
|
644
|
+
content_utf8 = part.headers[b"Content-Disposition"].decode("utf-8")
|
|
645
|
+
# Find name="1245" preceded by a whitespace, semicolon or beginning of line.
|
|
646
|
+
# The regex has 2 capture group: one for the prefix and one for the actual name value.
|
|
647
|
+
figure_id = int(re.findall(r'(^|[\s;])name="(\d*)"', content_utf8)[0][1])
|
|
648
|
+
yield figure_id, part.content
|
|
649
|
+
|
|
650
|
+
async def download_geometries_batch_async(
|
|
651
|
+
self,
|
|
652
|
+
ids: List[int],
|
|
653
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
654
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
655
|
+
) -> List[dict]:
|
|
656
|
+
"""
|
|
657
|
+
Download figure geometries with given IDs from storage asynchronously.
|
|
658
|
+
|
|
659
|
+
:param ids: List of figure IDs in Supervisely.
|
|
660
|
+
:type ids: List[int]
|
|
661
|
+
:param progress_cb: Progress bar to show the download progress. Shows the number of bytes downloaded.
|
|
662
|
+
:type progress_cb: Union[tqdm, Callable], optional
|
|
663
|
+
:param semaphore: Semaphore to limit the number of concurrent downloads.
|
|
664
|
+
:type semaphore: Optional[asyncio.Semaphore], optional
|
|
665
|
+
:return: List of figure geometries in Supervisely JSON format.
|
|
666
|
+
:rtype: List[dict]
|
|
667
|
+
|
|
668
|
+
:Usage example:
|
|
669
|
+
|
|
670
|
+
.. code-block:: python
|
|
671
|
+
|
|
672
|
+
import asyncio
|
|
673
|
+
import supervisely as sly
|
|
674
|
+
|
|
675
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
676
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
677
|
+
api = sly.Api.from_env()
|
|
678
|
+
|
|
679
|
+
figure_ids = [642155547, 642155548, 642155549]
|
|
680
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
681
|
+
geometries = loop.run_until_complete(
|
|
682
|
+
api.figure.download_geometries_batch_async(
|
|
683
|
+
figure_ids,
|
|
684
|
+
progress_cb=tqdm(total=len(figure_ids), desc="Downloading geometries"),
|
|
685
|
+
semaphore=asyncio.Semaphore(15),
|
|
686
|
+
)
|
|
687
|
+
)
|
|
688
|
+
"""
|
|
689
|
+
geometries = {}
|
|
690
|
+
async for idx, part in self._download_geometries_generator_async(ids, semaphore):
|
|
691
|
+
if progress_cb is not None:
|
|
692
|
+
progress_cb(len(part))
|
|
693
|
+
geometry_json = json.loads(part)
|
|
694
|
+
geometries[idx] = geometry_json
|
|
695
|
+
|
|
696
|
+
if len(geometries) != len(ids):
|
|
697
|
+
raise RuntimeError("Not all geometries were downloaded")
|
|
698
|
+
ordered_results = [geometries[i] for i in ids]
|
|
699
|
+
return ordered_results
|
supervisely/api/image_api.py
CHANGED
|
@@ -13,6 +13,7 @@ from collections import defaultdict
|
|
|
13
13
|
from concurrent.futures import ThreadPoolExecutor
|
|
14
14
|
from datetime import datetime
|
|
15
15
|
from functools import partial
|
|
16
|
+
from math import ceil
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
from time import sleep
|
|
18
19
|
from typing import (
|
|
@@ -3705,11 +3706,12 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
3705
3706
|
range_start: Optional[int] = None,
|
|
3706
3707
|
range_end: Optional[int] = None,
|
|
3707
3708
|
headers: Optional[dict] = None,
|
|
3709
|
+
chunk_size: Optional[int] = None,
|
|
3708
3710
|
) -> AsyncGenerator:
|
|
3709
3711
|
"""
|
|
3710
3712
|
Download Image with given ID asynchronously.
|
|
3711
3713
|
If is_stream is True, returns stream of bytes, otherwise returns response object.
|
|
3712
|
-
For streaming, returns tuple of chunk and hash.
|
|
3714
|
+
For streaming, returns tuple of chunk and hash. Chunk size is 8 MB by default.
|
|
3713
3715
|
|
|
3714
3716
|
:param id: Image ID in Supervisely.
|
|
3715
3717
|
:type id: int
|
|
@@ -3721,11 +3723,16 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
3721
3723
|
:type range_end: int, optional
|
|
3722
3724
|
:param headers: Headers for request.
|
|
3723
3725
|
:type headers: dict, optional
|
|
3726
|
+
:param chunk_size: Size of chunk for streaming. Default is 8 MB.
|
|
3727
|
+
:type chunk_size: int, optional
|
|
3724
3728
|
:return: Stream of bytes or response object.
|
|
3725
3729
|
:rtype: AsyncGenerator
|
|
3726
3730
|
"""
|
|
3727
3731
|
api_method_name = "images.download"
|
|
3728
3732
|
|
|
3733
|
+
if chunk_size is None:
|
|
3734
|
+
chunk_size = 8 * 1024 * 1024
|
|
3735
|
+
|
|
3729
3736
|
json_body = {ApiField.ID: id}
|
|
3730
3737
|
|
|
3731
3738
|
if is_stream:
|
|
@@ -3736,6 +3743,7 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
3736
3743
|
headers=headers,
|
|
3737
3744
|
range_start=range_start,
|
|
3738
3745
|
range_end=range_end,
|
|
3746
|
+
chunk_size=chunk_size,
|
|
3739
3747
|
):
|
|
3740
3748
|
yield chunk, hhash
|
|
3741
3749
|
else:
|
|
@@ -4157,3 +4165,180 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
4157
4165
|
tasks.append(task)
|
|
4158
4166
|
results = await asyncio.gather(*tasks)
|
|
4159
4167
|
return results
|
|
4168
|
+
|
|
4169
|
+
async def download_bytes_generator_async(
|
|
4170
|
+
self,
|
|
4171
|
+
dataset_id: int,
|
|
4172
|
+
img_ids: List[int],
|
|
4173
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
4174
|
+
headers: Optional[dict] = None,
|
|
4175
|
+
check_hash: bool = False,
|
|
4176
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
4177
|
+
progress_cb_type: Literal["number", "size"] = "number",
|
|
4178
|
+
) -> AsyncGenerator[Tuple[int, bytes]]:
|
|
4179
|
+
"""
|
|
4180
|
+
Downloads Image bytes with given ID in batch asynchronously.
|
|
4181
|
+
Yields tuple of Image ID and bytes of downloaded image.
|
|
4182
|
+
Uses bulk download API method.
|
|
4183
|
+
|
|
4184
|
+
:param dataset_id: Dataset ID in Supervisely.
|
|
4185
|
+
:type dataset_id: int
|
|
4186
|
+
:param img_ids: List of Image IDs in Supervisely.
|
|
4187
|
+
:type img_ids: :class:`List[int]`
|
|
4188
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
4189
|
+
:type semaphore: :class:`asyncio.Semaphore`, optional
|
|
4190
|
+
:param headers: Headers for request.
|
|
4191
|
+
:type headers: dict, optional
|
|
4192
|
+
:param check_hash: If True, checks hash of downloaded bytes. Default is False.
|
|
4193
|
+
:type check_hash: bool, optional
|
|
4194
|
+
:param progress_cb: Function for tracking download progress.
|
|
4195
|
+
:type progress_cb: Optional[Union[tqdm, Callable]]
|
|
4196
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
|
|
4197
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
4198
|
+
:return: Tuple of Image ID and bytes of downloaded image.
|
|
4199
|
+
:rtype: :class:`Tuple[int, bytes]`
|
|
4200
|
+
|
|
4201
|
+
:Usage example:
|
|
4202
|
+
|
|
4203
|
+
.. code-block:: python
|
|
4204
|
+
|
|
4205
|
+
import supervisely as sly
|
|
4206
|
+
import asyncio
|
|
4207
|
+
|
|
4208
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
4209
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
4210
|
+
api = sly.Api.from_env()
|
|
4211
|
+
|
|
4212
|
+
dataset_id = 123456
|
|
4213
|
+
img_ids = [770918, 770919, 770920, 770921, ... , 770992]
|
|
4214
|
+
tasks = []
|
|
4215
|
+
for batch in batched(img_ids, 50):
|
|
4216
|
+
task = api.image.download_bytes_batch_async(dataset_id, batch)
|
|
4217
|
+
tasks.append(task)
|
|
4218
|
+
results = await asyncio.gather(*tasks)
|
|
4219
|
+
"""
|
|
4220
|
+
api_method_name = "images.bulk.download"
|
|
4221
|
+
json_body = {
|
|
4222
|
+
ApiField.DATASET_ID: dataset_id,
|
|
4223
|
+
ApiField.IMAGE_IDS: img_ids,
|
|
4224
|
+
}
|
|
4225
|
+
|
|
4226
|
+
if semaphore is None:
|
|
4227
|
+
semaphore = self._api.get_default_semaphore()
|
|
4228
|
+
async with semaphore:
|
|
4229
|
+
response = await self._api.post_async(
|
|
4230
|
+
api_method_name,
|
|
4231
|
+
json=json_body,
|
|
4232
|
+
headers=headers,
|
|
4233
|
+
)
|
|
4234
|
+
decoder = MultipartDecoder.from_response(response)
|
|
4235
|
+
for part in decoder.parts:
|
|
4236
|
+
content_utf8 = part.headers[b"Content-Disposition"].decode("utf-8")
|
|
4237
|
+
# Find name="1245" preceded by a whitespace, semicolon or beginning of line.
|
|
4238
|
+
# The regex has 2 capture group: one for the prefix and one for the actual name value.
|
|
4239
|
+
img_id = int(re.findall(r'(^|[\s;])name="(\d*)"', content_utf8)[0][1])
|
|
4240
|
+
if check_hash:
|
|
4241
|
+
hhash = part.headers.get("x-content-checksum-sha256", None)
|
|
4242
|
+
if hhash is not None:
|
|
4243
|
+
downloaded_bytes_hash = get_bytes_hash(part)
|
|
4244
|
+
if hhash != downloaded_bytes_hash:
|
|
4245
|
+
raise RuntimeError(
|
|
4246
|
+
f"Downloaded hash of image with ID:{img_id} does not match the expected hash: {downloaded_bytes_hash} != {hhash}"
|
|
4247
|
+
)
|
|
4248
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
4249
|
+
progress_cb(1)
|
|
4250
|
+
elif progress_cb is not None and progress_cb_type == "size":
|
|
4251
|
+
progress_cb(len(part.content))
|
|
4252
|
+
|
|
4253
|
+
yield img_id, part.content
|
|
4254
|
+
|
|
4255
|
+
async def get_list_generator_async(
|
|
4256
|
+
self,
|
|
4257
|
+
dataset_id: int = None,
|
|
4258
|
+
filters: Optional[List[Dict[str, str]]] = None,
|
|
4259
|
+
sort: Optional[str] = "id",
|
|
4260
|
+
sort_order: Optional[str] = "asc",
|
|
4261
|
+
force_metadata_for_links: Optional[bool] = True,
|
|
4262
|
+
only_labelled: Optional[bool] = False,
|
|
4263
|
+
fields: Optional[List[str]] = None,
|
|
4264
|
+
per_page: Optional[int] = 500,
|
|
4265
|
+
semaphore: Optional[List[asyncio.Semaphore]] = None,
|
|
4266
|
+
**kwargs,
|
|
4267
|
+
) -> AsyncGenerator[List[ImageInfo]]:
|
|
4268
|
+
"""
|
|
4269
|
+
Yields list of images in dataset asynchronously page by page.
|
|
4270
|
+
|
|
4271
|
+
:param dataset_id: Dataset ID in Supervisely.
|
|
4272
|
+
:type dataset_id: int
|
|
4273
|
+
:param filters: Filters for images.
|
|
4274
|
+
:type filters: List[Dict[str, str]], optional
|
|
4275
|
+
:param sort: Sort images by field.
|
|
4276
|
+
:type sort: str, optional
|
|
4277
|
+
:param sort_order: Sort order for images.
|
|
4278
|
+
:type sort_order: str, optional
|
|
4279
|
+
:param force_metadata_for_links: If True, forces metadata for links.
|
|
4280
|
+
:type force_metadata_for_links: bool, optional
|
|
4281
|
+
:param only_labelled: If True, returns only labelled images.
|
|
4282
|
+
:type only_labelled: bool, optional
|
|
4283
|
+
:param fields: List of fields to return.
|
|
4284
|
+
:type fields: List[str], optional
|
|
4285
|
+
:param per_page: Number of images to return per page.
|
|
4286
|
+
:type per_page: int, optional
|
|
4287
|
+
:param semaphore: Semaphore for limiting the number of simultaneous requests.
|
|
4288
|
+
:type semaphore: :class:`asyncio.Semaphore`, optional
|
|
4289
|
+
:param kwargs: Additional arguments.
|
|
4290
|
+
:return: List of images in dataset.
|
|
4291
|
+
:rtype: AsyncGenerator[List[ImageInfo]]
|
|
4292
|
+
|
|
4293
|
+
:Usage example:
|
|
4294
|
+
|
|
4295
|
+
.. code-block:: python
|
|
4296
|
+
|
|
4297
|
+
import supervisely as sly
|
|
4298
|
+
import asyncio
|
|
4299
|
+
|
|
4300
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
4301
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
4302
|
+
api = sly.Api.from_env()
|
|
4303
|
+
|
|
4304
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
4305
|
+
images = loop.run_until_complete(api.image.get_list_async(123456, per_page=600))
|
|
4306
|
+
"""
|
|
4307
|
+
|
|
4308
|
+
method = "images.list"
|
|
4309
|
+
dataset_info = kwargs.get("dataset_info", None)
|
|
4310
|
+
|
|
4311
|
+
if dataset_info is None:
|
|
4312
|
+
dataset_info = self._api.dataset.get_info_by_id(dataset_id, raise_error=True)
|
|
4313
|
+
|
|
4314
|
+
total_pages = ceil(dataset_info.items_count / per_page)
|
|
4315
|
+
|
|
4316
|
+
data = {
|
|
4317
|
+
ApiField.DATASET_ID: dataset_info.id,
|
|
4318
|
+
ApiField.PROJECT_ID: dataset_info.project_id,
|
|
4319
|
+
ApiField.SORT: sort,
|
|
4320
|
+
ApiField.SORT_ORDER: sort_order,
|
|
4321
|
+
ApiField.FORCE_METADATA_FOR_LINKS: force_metadata_for_links,
|
|
4322
|
+
ApiField.FILTER: filters or [],
|
|
4323
|
+
ApiField.PER_PAGE: per_page,
|
|
4324
|
+
}
|
|
4325
|
+
if fields is not None:
|
|
4326
|
+
data[ApiField.FIELDS] = fields
|
|
4327
|
+
if only_labelled:
|
|
4328
|
+
data[ApiField.FILTERS] = [
|
|
4329
|
+
{
|
|
4330
|
+
"type": "objects_class",
|
|
4331
|
+
"data": {
|
|
4332
|
+
"from": 1,
|
|
4333
|
+
"to": 9999,
|
|
4334
|
+
"include": True,
|
|
4335
|
+
"classId": None,
|
|
4336
|
+
},
|
|
4337
|
+
}
|
|
4338
|
+
]
|
|
4339
|
+
|
|
4340
|
+
if semaphore is None:
|
|
4341
|
+
semaphore = self._api.get_default_semaphore()
|
|
4342
|
+
|
|
4343
|
+
async for page in self.get_list_page_generator_async(method, data, total_pages, semaphore):
|
|
4344
|
+
yield page
|
supervisely/api/module_api.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
|
+
import asyncio
|
|
2
3
|
from collections import namedtuple
|
|
3
4
|
from copy import deepcopy
|
|
4
|
-
from
|
|
5
|
+
from math import ceil
|
|
6
|
+
from typing import TYPE_CHECKING, Any, AsyncGenerator, List, NamedTuple, Optional, Tuple
|
|
5
7
|
|
|
6
8
|
import requests
|
|
7
9
|
|
|
@@ -867,6 +869,98 @@ class ModuleApiBase(_JsonConvertibleModule):
|
|
|
867
869
|
response = self._get_response_by_id(id, method, id_field=ApiField.ID, fields=fields)
|
|
868
870
|
return self._convert_json_info(response.json()) if (response is not None) else None
|
|
869
871
|
|
|
872
|
+
async def get_list_idx_page_async(
|
|
873
|
+
self,
|
|
874
|
+
method: str,
|
|
875
|
+
data: dict,
|
|
876
|
+
) -> Tuple[int, List[NamedTuple]]:
|
|
877
|
+
"""
|
|
878
|
+
Get the list of items for a given page number.
|
|
879
|
+
Page number is specified in the data dictionary.
|
|
880
|
+
|
|
881
|
+
:param method: Method to call for listing items.
|
|
882
|
+
:type method: str
|
|
883
|
+
:param data: Data to pass to the API method.
|
|
884
|
+
:type data: dict
|
|
885
|
+
:return: List of items.
|
|
886
|
+
:rtype: Tuple[int, List[NamedTuple]]
|
|
887
|
+
"""
|
|
888
|
+
|
|
889
|
+
response = await self._api.post_async(method, data)
|
|
890
|
+
response_json = response.json()
|
|
891
|
+
entities = response_json.get("entities", [])
|
|
892
|
+
# To avoid empty pages when a filter is applied to the data and the `pagesCount` is less than the number calculated based on the items and `per_page` size.
|
|
893
|
+
# Process `pagesCount` in the main function according to the actual number of pages returned.
|
|
894
|
+
pages_count = response_json.get("pagesCount", None)
|
|
895
|
+
if pages_count is None:
|
|
896
|
+
raise ValueError("Can not determine the number of pages to retrieve.")
|
|
897
|
+
return pages_count, [self._convert_json_info(item) for item in entities]
|
|
898
|
+
|
|
899
|
+
async def get_list_page_generator_async(
|
|
900
|
+
self,
|
|
901
|
+
method: str,
|
|
902
|
+
data: dict,
|
|
903
|
+
pages_count: Optional[int] = None,
|
|
904
|
+
semaphore: Optional[List[asyncio.Semaphore]] = None,
|
|
905
|
+
) -> AsyncGenerator[List[Any], None]:
|
|
906
|
+
"""
|
|
907
|
+
Yields list of images in dataset asynchronously page by page.
|
|
908
|
+
|
|
909
|
+
:param method: Method to call for listing items.
|
|
910
|
+
:type method: str
|
|
911
|
+
:param data: Data to pass to the API method.
|
|
912
|
+
:type data: dict
|
|
913
|
+
:param pages_count: Preferred number of pages to retrieve if used with a `per_page` limit.
|
|
914
|
+
Will be automatically adjusted if the `pagesCount` differs from the requested number.
|
|
915
|
+
:type pages_count: int, optional
|
|
916
|
+
:param semaphore: Semaphore for limiting the number of simultaneous requests.
|
|
917
|
+
:type semaphore: :class:`asyncio.Semaphore`, optional
|
|
918
|
+
:param kwargs: Additional arguments.
|
|
919
|
+
:return: List of images in dataset.
|
|
920
|
+
:rtype: AsyncGenerator[List[ImageInfo]]
|
|
921
|
+
|
|
922
|
+
:Usage example:
|
|
923
|
+
|
|
924
|
+
.. code-block:: python
|
|
925
|
+
|
|
926
|
+
import supervisely as sly
|
|
927
|
+
import asyncio
|
|
928
|
+
|
|
929
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
930
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
931
|
+
api = sly.Api.from_env()
|
|
932
|
+
|
|
933
|
+
method = 'images.list'
|
|
934
|
+
data = {
|
|
935
|
+
'datasetId': 123456
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
loop = sly.utils.get_or_create_event_loop()
|
|
939
|
+
images = loop.run_until_complete(api.image.get_list_generator_async(method, data))
|
|
940
|
+
"""
|
|
941
|
+
|
|
942
|
+
if semaphore is None:
|
|
943
|
+
semaphore = self._api.get_default_semaphore()
|
|
944
|
+
|
|
945
|
+
async def sem_task(task):
|
|
946
|
+
async with semaphore:
|
|
947
|
+
return await task
|
|
948
|
+
|
|
949
|
+
if pages_count is None:
|
|
950
|
+
pages_count = 999999 # to avoid range lesser than total pages count
|
|
951
|
+
for page_num in range(1, pages_count + 1):
|
|
952
|
+
if page_num <= pages_count:
|
|
953
|
+
data[ApiField.PAGE] = page_num
|
|
954
|
+
total_pages, items = await sem_task(self.get_list_idx_page_async(method, data))
|
|
955
|
+
|
|
956
|
+
# To correct `total_pages` count in case filter is applied
|
|
957
|
+
if page_num == 1:
|
|
958
|
+
pages_count = total_pages
|
|
959
|
+
|
|
960
|
+
yield items
|
|
961
|
+
else:
|
|
962
|
+
break
|
|
963
|
+
|
|
870
964
|
|
|
871
965
|
class ModuleApi(ModuleApiBase):
|
|
872
966
|
"""Base class for entities that have a parent object in the system."""
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import cv2
|
|
2
2
|
import numpy as np
|
|
3
|
-
import scipy
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
def one_hot(segmentation, num_classes):
|
|
@@ -18,12 +17,16 @@ def single_one_hot(segmentation, cls):
|
|
|
18
17
|
|
|
19
18
|
|
|
20
19
|
def get_single_contiguous_segment(one_hot_segmentation):
|
|
20
|
+
import scipy # pylint: disable=import-error
|
|
21
|
+
|
|
21
22
|
kernel = np.ones((3, 3), dtype=one_hot_segmentation.dtype)
|
|
22
23
|
seg = scipy.ndimage.label(one_hot_segmentation, structure=kernel)[0]
|
|
23
24
|
return [np.where(seg == l) for l in range(1, seg.max() + 1)]
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
def get_contiguous_segments(one_hot_segmentation):
|
|
28
|
+
import scipy # pylint: disable=import-error
|
|
29
|
+
|
|
27
30
|
kernel = np.ones((3, 3), dtype=one_hot_segmentation.dtype)
|
|
28
31
|
segments_tensor = np.stack(
|
|
29
32
|
[scipy.ndimage.label(seg, structure=kernel)[0] for seg in one_hot_segmentation]
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# vim: expandtab:ts=4:sw=4
|
|
2
2
|
import numpy as np
|
|
3
|
-
import scipy.linalg
|
|
4
|
-
|
|
5
3
|
|
|
6
4
|
"""
|
|
7
5
|
Table for the 0.95 quantile of the chi-square distribution with N degrees of
|
|
@@ -171,6 +169,8 @@ class KalmanFilter(object):
|
|
|
171
169
|
Returns the measurement-corrected state distribution.
|
|
172
170
|
|
|
173
171
|
"""
|
|
172
|
+
import scipy.linalg # pylint: disable=import-error
|
|
173
|
+
|
|
174
174
|
projected_mean, projected_cov = self.project(mean, covariance)
|
|
175
175
|
|
|
176
176
|
chol_factor, lower = scipy.linalg.cho_factor(
|
|
@@ -215,6 +215,8 @@ class KalmanFilter(object):
|
|
|
215
215
|
`measurements[i]`.
|
|
216
216
|
|
|
217
217
|
"""
|
|
218
|
+
import scipy.linalg # pylint: disable=import-error
|
|
219
|
+
|
|
218
220
|
mean, covariance = self.project(mean, covariance)
|
|
219
221
|
if only_position:
|
|
220
222
|
mean, covariance = mean[:2], covariance[:2, :2]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
import scipy.linalg
|
|
3
2
|
|
|
4
3
|
"""
|
|
5
4
|
Table for the 0.95 quantile of the chi-square distribution with N degrees of
|
|
@@ -215,6 +214,8 @@ class KalmanFilterXYWH(object):
|
|
|
215
214
|
Returns the measurement-corrected state distribution.
|
|
216
215
|
|
|
217
216
|
"""
|
|
217
|
+
import scipy.linalg # pylint: disable=import-error
|
|
218
|
+
|
|
218
219
|
projected_mean, projected_cov = self.project(mean, covariance)
|
|
219
220
|
|
|
220
221
|
chol_factor, lower = scipy.linalg.cho_factor(projected_cov, lower=True, check_finite=False)
|
|
@@ -254,6 +255,8 @@ class KalmanFilterXYWH(object):
|
|
|
254
255
|
squared Mahalanobis distance between (mean, covariance) and
|
|
255
256
|
`measurements[i]`.
|
|
256
257
|
"""
|
|
258
|
+
import scipy.linalg # pylint: disable=import-error
|
|
259
|
+
|
|
257
260
|
mean, covariance = self.project(mean, covariance)
|
|
258
261
|
if only_position:
|
|
259
262
|
mean, covariance = mean[:2], covariance[:2, :2]
|
|
@@ -428,6 +431,8 @@ class KalmanFilterXYAH(object):
|
|
|
428
431
|
Returns the measurement-corrected state distribution.
|
|
429
432
|
|
|
430
433
|
"""
|
|
434
|
+
import scipy.linalg # pylint: disable=import-error
|
|
435
|
+
|
|
431
436
|
projected_mean, projected_cov = self.project(mean, covariance)
|
|
432
437
|
|
|
433
438
|
chol_factor, lower = scipy.linalg.cho_factor(projected_cov, lower=True, check_finite=False)
|
|
@@ -471,6 +476,8 @@ class KalmanFilterXYAH(object):
|
|
|
471
476
|
`measurements[i]`.
|
|
472
477
|
|
|
473
478
|
"""
|
|
479
|
+
import scipy.linalg # pylint: disable=import-error
|
|
480
|
+
|
|
474
481
|
mean, covariance = self.project(mean, covariance)
|
|
475
482
|
if only_position:
|
|
476
483
|
mean, covariance = mean[:2], covariance[:2, :2]
|
supervisely/project/download.py
CHANGED
|
@@ -225,31 +225,51 @@ def download_async_or_sync(
|
|
|
225
225
|
semaphore: Optional[asyncio.Semaphore] = None,
|
|
226
226
|
**kwargs,
|
|
227
227
|
):
|
|
228
|
-
|
|
228
|
+
"""
|
|
229
|
+
Download project asynchronously if possible, otherwise download synchronously.
|
|
230
|
+
Automatically detects project type.
|
|
231
|
+
You can pass :class:`ProjectInfo` as `project_info` kwarg to avoid additional API requests.
|
|
232
|
+
|
|
233
|
+
In case of error during asynchronous download, the function will switch to synchronous download.
|
|
234
|
+
"""
|
|
235
|
+
project_info = kwargs.pop("project_info", None)
|
|
236
|
+
if not isinstance(project_info, ProjectInfo) or project_info.id != project_id:
|
|
237
|
+
project_info = api.project.get_info_by_id(project_id)
|
|
229
238
|
|
|
230
239
|
if progress_cb is not None:
|
|
231
240
|
log_progress = False
|
|
232
241
|
|
|
233
242
|
project_class = get_project_class(project_info.type)
|
|
234
|
-
if hasattr(project_class, "download_async"):
|
|
235
|
-
download_coro = project_class.download_async(
|
|
236
|
-
api=api,
|
|
237
|
-
project_id=project_id,
|
|
238
|
-
dest_dir=dest_dir,
|
|
239
|
-
semaphore=semaphore,
|
|
240
|
-
dataset_ids=dataset_ids,
|
|
241
|
-
log_progress=log_progress,
|
|
242
|
-
progress_cb=progress_cb,
|
|
243
|
-
**kwargs,
|
|
244
|
-
)
|
|
245
|
-
loop = get_or_create_event_loop()
|
|
246
|
-
if loop.is_running():
|
|
247
|
-
future = asyncio.run_coroutine_threadsafe(download_coro, loop=loop)
|
|
248
|
-
future.result()
|
|
249
|
-
else:
|
|
250
|
-
loop.run_until_complete(download_coro)
|
|
251
243
|
|
|
244
|
+
switch_to_sync = False
|
|
245
|
+
if hasattr(project_class, "download_async"):
|
|
246
|
+
try:
|
|
247
|
+
download_coro = project_class.download_async(
|
|
248
|
+
api=api,
|
|
249
|
+
project_id=project_id,
|
|
250
|
+
dest_dir=dest_dir,
|
|
251
|
+
semaphore=semaphore,
|
|
252
|
+
dataset_ids=dataset_ids,
|
|
253
|
+
log_progress=log_progress,
|
|
254
|
+
progress_cb=progress_cb,
|
|
255
|
+
**kwargs,
|
|
256
|
+
)
|
|
257
|
+
loop = get_or_create_event_loop()
|
|
258
|
+
if loop.is_running():
|
|
259
|
+
future = asyncio.run_coroutine_threadsafe(download_coro, loop=loop)
|
|
260
|
+
future.result()
|
|
261
|
+
else:
|
|
262
|
+
loop.run_until_complete(download_coro)
|
|
263
|
+
except Exception as e:
|
|
264
|
+
if kwargs.get("resume_download", False) is False:
|
|
265
|
+
remove_dir(dest_dir)
|
|
266
|
+
logger.error(f"Failed to download project {project_id} asynchronously: {e}")
|
|
267
|
+
logger.warning("Switching to synchronous download")
|
|
268
|
+
switch_to_sync = True
|
|
252
269
|
else:
|
|
270
|
+
switch_to_sync = True
|
|
271
|
+
|
|
272
|
+
if switch_to_sync:
|
|
253
273
|
project_class.download(
|
|
254
274
|
api=api,
|
|
255
275
|
project_id=project_id,
|
|
@@ -338,7 +358,7 @@ def _validate_dataset(
|
|
|
338
358
|
project_meta_changed = _project_meta_changed(project_meta, project.meta)
|
|
339
359
|
for dataset in project.datasets:
|
|
340
360
|
dataset: Dataset
|
|
341
|
-
if dataset.name.endswith(dataset_info.name):
|
|
361
|
+
if dataset.name.endswith(dataset_info.name): # TODO: fix it later
|
|
342
362
|
diff = set(items_infos_dict.keys()).difference(set(dataset.get_items_names()))
|
|
343
363
|
if diff:
|
|
344
364
|
logger.debug(
|
supervisely/project/project.py
CHANGED
|
@@ -40,9 +40,9 @@ from supervisely.io.fs import (
|
|
|
40
40
|
copy_file,
|
|
41
41
|
copy_file_async,
|
|
42
42
|
dir_empty,
|
|
43
|
-
file_exists,
|
|
44
43
|
dir_exists,
|
|
45
44
|
ensure_base_path,
|
|
45
|
+
file_exists,
|
|
46
46
|
get_file_name_with_ext,
|
|
47
47
|
list_dir_recursively,
|
|
48
48
|
list_files,
|
|
@@ -3434,6 +3434,7 @@ class Project:
|
|
|
3434
3434
|
save_image_meta: bool = False,
|
|
3435
3435
|
images_ids: Optional[List[int]] = None,
|
|
3436
3436
|
resume_download: Optional[bool] = False,
|
|
3437
|
+
**kwargs,
|
|
3437
3438
|
) -> None:
|
|
3438
3439
|
"""
|
|
3439
3440
|
Download project from Supervisely to the given directory in asynchronous mode.
|
|
@@ -3488,6 +3489,12 @@ class Project:
|
|
|
3488
3489
|
else:
|
|
3489
3490
|
loop.run_until_complete(coroutine)
|
|
3490
3491
|
"""
|
|
3492
|
+
if kwargs.pop("cache", None) is not None:
|
|
3493
|
+
logger.warning(
|
|
3494
|
+
"Cache is not supported in async mode and will be ignored. "
|
|
3495
|
+
"Use resume_download parameter instead to optimize download process."
|
|
3496
|
+
)
|
|
3497
|
+
|
|
3491
3498
|
await _download_project_async(
|
|
3492
3499
|
api=api,
|
|
3493
3500
|
project_id=project_id,
|
|
@@ -3733,7 +3740,8 @@ def _download_project(
|
|
|
3733
3740
|
batch, image_names, batch_imgs_bytes, ann_jsons
|
|
3734
3741
|
):
|
|
3735
3742
|
dataset_fs: Dataset
|
|
3736
|
-
|
|
3743
|
+
# to fix already downloaded images that doesn't have info files
|
|
3744
|
+
dataset_fs.delete_item(name)
|
|
3737
3745
|
dataset_fs.add_item_raw_bytes(
|
|
3738
3746
|
item_name=name,
|
|
3739
3747
|
item_raw_bytes=img_bytes if save_images is True else None,
|
|
@@ -4401,14 +4409,42 @@ async def _download_project_async(
|
|
|
4401
4409
|
only_image_tags: Optional[bool] = False,
|
|
4402
4410
|
save_image_info: Optional[bool] = False,
|
|
4403
4411
|
save_images: Optional[bool] = True,
|
|
4404
|
-
progress_cb: Optional[Callable] = None,
|
|
4412
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
4405
4413
|
save_image_meta: Optional[bool] = False,
|
|
4406
4414
|
images_ids: Optional[List[int]] = None,
|
|
4407
4415
|
resume_download: Optional[bool] = False,
|
|
4416
|
+
**kwargs,
|
|
4408
4417
|
):
|
|
4418
|
+
"""
|
|
4419
|
+
Download image project to the local directory asynchronously.
|
|
4420
|
+
Uses queue and semaphore to control the number of parallel downloads.
|
|
4421
|
+
Every image goes through size check to decide if it should be downloaded in bulk or one by one.
|
|
4422
|
+
Checked images are split into two lists: small and large. Small images are downloaded in bulk, large images are downloaded one by one.
|
|
4423
|
+
As soon as the task is created, it is put into the queue. Workers take tasks from the queue and execute them.
|
|
4424
|
+
|
|
4425
|
+
"""
|
|
4426
|
+
# to switch between single and bulk download
|
|
4427
|
+
switch_size = kwargs.get("switch_size", 1.28 * 1024 * 1024)
|
|
4428
|
+
# batch size for bulk download
|
|
4429
|
+
batch_size = kwargs.get("batch_size", 100)
|
|
4430
|
+
# number of workers
|
|
4431
|
+
num_workers = kwargs.get("num_workers", 5)
|
|
4432
|
+
|
|
4409
4433
|
if semaphore is None:
|
|
4410
4434
|
semaphore = api.get_default_semaphore()
|
|
4411
4435
|
|
|
4436
|
+
async def worker(queue: asyncio.Queue, semaphore: asyncio.Semaphore):
|
|
4437
|
+
while True:
|
|
4438
|
+
task = await queue.get()
|
|
4439
|
+
if task is None:
|
|
4440
|
+
break
|
|
4441
|
+
async with semaphore:
|
|
4442
|
+
await task
|
|
4443
|
+
queue.task_done()
|
|
4444
|
+
|
|
4445
|
+
queue = asyncio.Queue()
|
|
4446
|
+
workers = [asyncio.create_task(worker(queue, semaphore)) for _ in range(num_workers)]
|
|
4447
|
+
|
|
4412
4448
|
dataset_ids = set(dataset_ids) if (dataset_ids is not None) else None
|
|
4413
4449
|
project_fs = None
|
|
4414
4450
|
meta = ProjectMeta.from_json(api.project.get_meta(project_id, with_settings=True))
|
|
@@ -4448,16 +4484,25 @@ async def _download_project_async(
|
|
|
4448
4484
|
force_metadata_for_links = False
|
|
4449
4485
|
if save_images is False and only_image_tags is True:
|
|
4450
4486
|
force_metadata_for_links = True
|
|
4451
|
-
all_images = api.image.
|
|
4452
|
-
dataset_id, force_metadata_for_links=force_metadata_for_links
|
|
4487
|
+
all_images = api.image.get_list_generator_async(
|
|
4488
|
+
dataset_id, force_metadata_for_links=force_metadata_for_links, dataset_info=dataset
|
|
4453
4489
|
)
|
|
4454
|
-
|
|
4490
|
+
small_images = []
|
|
4491
|
+
large_images = []
|
|
4492
|
+
async for image_batch in all_images:
|
|
4493
|
+
for image in image_batch:
|
|
4494
|
+
if images_ids is None or image.id in images_ids:
|
|
4495
|
+
if image.size < switch_size:
|
|
4496
|
+
small_images.append(image)
|
|
4497
|
+
else:
|
|
4498
|
+
large_images.append(image)
|
|
4455
4499
|
|
|
4456
4500
|
ds_progress = progress_cb
|
|
4457
4501
|
if log_progress is True:
|
|
4458
4502
|
ds_progress = tqdm_sly(
|
|
4459
4503
|
desc="Downloading images from {!r}".format(dataset.name),
|
|
4460
|
-
total=len(
|
|
4504
|
+
total=len(small_images) + len(large_images),
|
|
4505
|
+
leave=False,
|
|
4461
4506
|
)
|
|
4462
4507
|
|
|
4463
4508
|
with ApiContext(
|
|
@@ -4466,18 +4511,42 @@ async def _download_project_async(
|
|
|
4466
4511
|
dataset_id=dataset_id,
|
|
4467
4512
|
project_meta=meta,
|
|
4468
4513
|
):
|
|
4469
|
-
tasks = []
|
|
4470
|
-
for image in images:
|
|
4471
|
-
try:
|
|
4472
|
-
existing = dataset_fs.get_item_info(image.name)
|
|
4473
|
-
except:
|
|
4474
|
-
existing = None
|
|
4475
|
-
else:
|
|
4476
|
-
if existing.updated_at == image.updated_at:
|
|
4477
|
-
if ds_progress is not None:
|
|
4478
|
-
ds_progress(1)
|
|
4479
|
-
continue
|
|
4480
4514
|
|
|
4515
|
+
async def check_items(check_list: List[sly.ImageInfo]):
|
|
4516
|
+
to_download = []
|
|
4517
|
+
for image in check_list:
|
|
4518
|
+
try:
|
|
4519
|
+
existing = dataset_fs.get_item_info(image.name)
|
|
4520
|
+
except:
|
|
4521
|
+
pass
|
|
4522
|
+
else:
|
|
4523
|
+
if existing.updated_at == image.updated_at:
|
|
4524
|
+
if ds_progress is not None:
|
|
4525
|
+
ds_progress(1)
|
|
4526
|
+
continue
|
|
4527
|
+
to_download.append(image)
|
|
4528
|
+
return to_download
|
|
4529
|
+
|
|
4530
|
+
small_images = await check_items(small_images)
|
|
4531
|
+
large_images = await check_items(large_images)
|
|
4532
|
+
if len(small_images) == 1:
|
|
4533
|
+
large_images.append(small_images.pop())
|
|
4534
|
+
for images_batch in batched(small_images, batch_size=batch_size):
|
|
4535
|
+
task = _download_project_items_batch_async(
|
|
4536
|
+
api=api,
|
|
4537
|
+
dataset_id=dataset_id,
|
|
4538
|
+
img_infos=images_batch,
|
|
4539
|
+
meta=meta,
|
|
4540
|
+
dataset_fs=dataset_fs,
|
|
4541
|
+
id_to_tagmeta=id_to_tagmeta,
|
|
4542
|
+
semaphore=semaphore,
|
|
4543
|
+
save_images=save_images,
|
|
4544
|
+
save_image_info=save_image_info,
|
|
4545
|
+
only_image_tags=only_image_tags,
|
|
4546
|
+
progress_cb=ds_progress,
|
|
4547
|
+
)
|
|
4548
|
+
await queue.put(task)
|
|
4549
|
+
for image in large_images:
|
|
4481
4550
|
task = _download_project_item_async(
|
|
4482
4551
|
api=api,
|
|
4483
4552
|
img_info=image,
|
|
@@ -4490,11 +4559,15 @@ async def _download_project_async(
|
|
|
4490
4559
|
only_image_tags=only_image_tags,
|
|
4491
4560
|
progress_cb=ds_progress,
|
|
4492
4561
|
)
|
|
4493
|
-
|
|
4494
|
-
|
|
4562
|
+
await queue.put(task)
|
|
4563
|
+
|
|
4564
|
+
await queue.join()
|
|
4565
|
+
|
|
4566
|
+
all_images = small_images + large_images
|
|
4567
|
+
|
|
4495
4568
|
if save_image_meta:
|
|
4496
4569
|
meta_dir = dataset_fs.meta_dir
|
|
4497
|
-
for image_info in
|
|
4570
|
+
for image_info in all_images:
|
|
4498
4571
|
if image_info.meta:
|
|
4499
4572
|
sly.fs.mkdir(meta_dir)
|
|
4500
4573
|
sly.json.dump_json_file(
|
|
@@ -4506,6 +4579,10 @@ async def _download_project_async(
|
|
|
4506
4579
|
for item_name in dataset_fs.get_items_names():
|
|
4507
4580
|
if item_name not in items_names_set:
|
|
4508
4581
|
dataset_fs.delete_item(item_name)
|
|
4582
|
+
|
|
4583
|
+
for _ in range(num_workers):
|
|
4584
|
+
await queue.put(None)
|
|
4585
|
+
await asyncio.gather(*workers)
|
|
4509
4586
|
try:
|
|
4510
4587
|
create_readme(dest_dir, project_id, api)
|
|
4511
4588
|
except Exception as e:
|
|
@@ -4557,8 +4634,7 @@ async def _download_project_item_async(
|
|
|
4557
4634
|
tmp_ann = Annotation(img_size=(img_info.height, img_info.width), img_tags=tags)
|
|
4558
4635
|
ann_json = tmp_ann.to_json()
|
|
4559
4636
|
|
|
4560
|
-
|
|
4561
|
-
dataset_fs.delete_item(img_info.name)
|
|
4637
|
+
dataset_fs.delete_item(img_info.name)
|
|
4562
4638
|
await dataset_fs.add_item_raw_bytes_async(
|
|
4563
4639
|
item_name=img_info.name,
|
|
4564
4640
|
item_raw_bytes=img_bytes if save_images is True else None,
|
|
@@ -4569,4 +4645,78 @@ async def _download_project_item_async(
|
|
|
4569
4645
|
progress_cb(1)
|
|
4570
4646
|
|
|
4571
4647
|
|
|
4648
|
+
async def _download_project_items_batch_async(
|
|
4649
|
+
api: sly.Api,
|
|
4650
|
+
dataset_id: int,
|
|
4651
|
+
img_infos: List[sly.ImageInfo],
|
|
4652
|
+
meta: ProjectMeta,
|
|
4653
|
+
dataset_fs: Dataset,
|
|
4654
|
+
id_to_tagmeta: Dict[int, sly.TagMeta],
|
|
4655
|
+
semaphore: asyncio.Semaphore,
|
|
4656
|
+
save_images: bool,
|
|
4657
|
+
save_image_info: bool,
|
|
4658
|
+
only_image_tags: bool,
|
|
4659
|
+
progress_cb: Optional[Callable],
|
|
4660
|
+
):
|
|
4661
|
+
"""
|
|
4662
|
+
Download images and annotations from Supervisely API and save them to the local filesystem.
|
|
4663
|
+
Uses parameters from the parent function _download_project_async.
|
|
4664
|
+
It is used for batch download of images and annotations with the bulk download API methods.
|
|
4665
|
+
"""
|
|
4666
|
+
if save_images:
|
|
4667
|
+
img_ids = [img_info.id for img_info in img_infos]
|
|
4668
|
+
imgs_bytes = [None] * len(img_ids)
|
|
4669
|
+
temp_dict = {}
|
|
4670
|
+
async for img_id, img_bytes in api.image.download_bytes_generator_async(
|
|
4671
|
+
dataset_id,
|
|
4672
|
+
img_ids,
|
|
4673
|
+
semaphore=semaphore,
|
|
4674
|
+
check_hash=True,
|
|
4675
|
+
):
|
|
4676
|
+
temp_dict[img_id] = img_bytes
|
|
4677
|
+
# to be sure that the order is correct
|
|
4678
|
+
for idx, img_id in enumerate(img_ids):
|
|
4679
|
+
imgs_bytes[idx] = temp_dict[img_id]
|
|
4680
|
+
for img_info, img_bytes in zip(img_infos, imgs_bytes):
|
|
4681
|
+
if None in [img_info.height, img_info.width]:
|
|
4682
|
+
width, height = sly.image.get_size_from_bytes(img_bytes)
|
|
4683
|
+
img_info = img_info._replace(height=height, width=width)
|
|
4684
|
+
else:
|
|
4685
|
+
imgs_bytes = [None] * len(img_infos)
|
|
4686
|
+
|
|
4687
|
+
if only_image_tags is False:
|
|
4688
|
+
ann_infos = await api.annotation.download_bulk_async(
|
|
4689
|
+
dataset_id,
|
|
4690
|
+
img_ids,
|
|
4691
|
+
semaphore=semaphore,
|
|
4692
|
+
force_metadata_for_links=not save_images,
|
|
4693
|
+
)
|
|
4694
|
+
tmps_anns = [Annotation.from_json(ann_info.annotation, meta) for ann_info in ann_infos]
|
|
4695
|
+
ann_jsons = []
|
|
4696
|
+
for tmp_ann in tmps_anns:
|
|
4697
|
+
if None in tmp_ann.img_size:
|
|
4698
|
+
tmp_ann = tmp_ann.clone(img_size=(img_info.height, img_info.width))
|
|
4699
|
+
ann_jsons.append(tmp_ann.to_json())
|
|
4700
|
+
else:
|
|
4701
|
+
ann_jsons = []
|
|
4702
|
+
for img_info in img_infos:
|
|
4703
|
+
tags = TagCollection.from_api_response(
|
|
4704
|
+
img_info.tags,
|
|
4705
|
+
meta.tag_metas,
|
|
4706
|
+
id_to_tagmeta,
|
|
4707
|
+
)
|
|
4708
|
+
tmp_ann = Annotation(img_size=(img_info.height, img_info.width), img_tags=tags)
|
|
4709
|
+
ann_jsons.append(tmp_ann.to_json())
|
|
4710
|
+
for img_info, ann_json, img_bytes in zip(img_infos, ann_jsons, imgs_bytes):
|
|
4711
|
+
dataset_fs.delete_item(img_info.name)
|
|
4712
|
+
await dataset_fs.add_item_raw_bytes_async(
|
|
4713
|
+
item_name=img_info.name,
|
|
4714
|
+
item_raw_bytes=img_bytes,
|
|
4715
|
+
ann=dataset_fs.get_ann(img_info.name, meta) if ann_json is None else ann_json,
|
|
4716
|
+
img_info=img_info if save_image_info is True else None,
|
|
4717
|
+
)
|
|
4718
|
+
if progress_cb is not None:
|
|
4719
|
+
progress_cb(1)
|
|
4720
|
+
|
|
4721
|
+
|
|
4572
4722
|
DatasetDict = Project.DatasetDict
|
|
@@ -21,18 +21,18 @@ supervisely/annotation/tag_meta_mapper.py,sha256=RWeTrxJ64syodyhXIRSH007bX6Hr3B4
|
|
|
21
21
|
supervisely/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
supervisely/api/advanced_api.py,sha256=Nd5cCnHFWc3PSUrCtENxTGtDjS37_lCHXsgXvUI3Ti8,2054
|
|
23
23
|
supervisely/api/agent_api.py,sha256=ShWAIlXcWXcyI9fqVuP5GZVCigCMJmjnvdGUfLspD6Y,8890
|
|
24
|
-
supervisely/api/annotation_api.py,sha256=
|
|
25
|
-
supervisely/api/api.py,sha256=
|
|
24
|
+
supervisely/api/annotation_api.py,sha256=kB9l0NhQEkunGDC9fWjNzf5DdhqRF1tv-RRnIbkV2k0,64941
|
|
25
|
+
supervisely/api/api.py,sha256=aSFD2Q05q0CK9nmMyFTEv5pHKyTm_ysu9cagwt4GDhs,64746
|
|
26
26
|
supervisely/api/app_api.py,sha256=-T4sISQ7POyR2yirf1kEWj4JaJFpJxCyRWqbf_99Jak,67036
|
|
27
27
|
supervisely/api/dataset_api.py,sha256=2-SQBlgEnIN-0uvDbtPlSXr6ztBeZ3WPryhkOtpBmk4,40786
|
|
28
28
|
supervisely/api/file_api.py,sha256=c4iIzH2BF8-GLFLk_wc9Qz225AbHhbzH22wv5HdsGg4,83128
|
|
29
29
|
supervisely/api/github_api.py,sha256=NIexNjEer9H5rf5sw2LEZd7C1WR-tK4t6IZzsgeAAwQ,623
|
|
30
30
|
supervisely/api/image_annotation_tool_api.py,sha256=YcUo78jRDBJYvIjrd-Y6FJAasLta54nnxhyaGyanovA,5237
|
|
31
|
-
supervisely/api/image_api.py,sha256=
|
|
31
|
+
supervisely/api/image_api.py,sha256=2cki-IzA5jnN3QqqdSIbIbHJhDWxFGYxXY94WqBOoio,176836
|
|
32
32
|
supervisely/api/import_storage_api.py,sha256=BDCgmR0Hv6OoiRHLCVPKt3iDxSVlQp1WrnKhAK_Zl84,460
|
|
33
33
|
supervisely/api/issues_api.py,sha256=BqDJXmNoTzwc3xe6_-mA7FDFC5QQ-ahGbXk_HmpkSeQ,17925
|
|
34
34
|
supervisely/api/labeling_job_api.py,sha256=odnzZjp29yM16Gq-FYkv-OA4WFMNJCLFo4qSikW2A7c,56280
|
|
35
|
-
supervisely/api/module_api.py,sha256=
|
|
35
|
+
supervisely/api/module_api.py,sha256=ajVPU5bH1sB-l2PwWq_xtFxSmZIBikflousOxiAYmW4,43141
|
|
36
36
|
supervisely/api/neural_network_api.py,sha256=ktPVRO4Jeulougio8F0mioJJHwRJcX250Djp1wBoQ9c,7620
|
|
37
37
|
supervisely/api/object_class_api.py,sha256=-rQcKwhBw3iL9KNH9c1ROgoimgWM1ls6Wi_tb1R-MzY,7683
|
|
38
38
|
supervisely/api/plugin_api.py,sha256=TlfrosdRuYG4NUxk92QiQoVaOdztFspPpygyVa3M3zk,5283
|
|
@@ -49,7 +49,7 @@ supervisely/api/video_annotation_tool_api.py,sha256=Uy1MvT-M7vjC6y-0-V4wFCO-fZt8
|
|
|
49
49
|
supervisely/api/workspace_api.py,sha256=5KAxpI9DKBmgF_pyQaXHpGT30HZ9wRtR6DP3FoYFZtY,9228
|
|
50
50
|
supervisely/api/entity_annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
51
|
supervisely/api/entity_annotation/entity_annotation_api.py,sha256=K79KdDyepQv4FiNQHBj9V4-zLIemxK9WG1ig1bfBKb8,3083
|
|
52
|
-
supervisely/api/entity_annotation/figure_api.py,sha256=
|
|
52
|
+
supervisely/api/entity_annotation/figure_api.py,sha256=_JS1x0jn5neoCnZCBKHUBwspoHg92N-sJ9Ty9h07Mvg,24427
|
|
53
53
|
supervisely/api/entity_annotation/object_api.py,sha256=gbcNvN_KY6G80Me8fHKQgryc2Co7VU_kfFd1GYILZ4E,8875
|
|
54
54
|
supervisely/api/entity_annotation/tag_api.py,sha256=M-28m9h8R4k9Eqo6P1S0UH8_D5kqCwAvQLYY6_Yz4oM,11161
|
|
55
55
|
supervisely/api/pointcloud/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -814,7 +814,7 @@ supervisely/nn/benchmark/utils/semantic_segmentation/__init__.py,sha256=47DEQpj8
|
|
|
814
814
|
supervisely/nn/benchmark/utils/semantic_segmentation/calculate_metrics.py,sha256=4ifC5r_Q880yIr8gWnjEzwKbS0vizMWqSF4XeyaMvh0,924
|
|
815
815
|
supervisely/nn/benchmark/utils/semantic_segmentation/evaluator.py,sha256=R1U_mnOiUUH6P87xiKalYqMpj1uGCJKuOBcsTmVHawY,32894
|
|
816
816
|
supervisely/nn/benchmark/utils/semantic_segmentation/loader.py,sha256=_5ZZ7Nkd8WWYJnKwc1Dx3bEPS_1R84gG_hQc0w0TXWw,1957
|
|
817
|
-
supervisely/nn/benchmark/utils/semantic_segmentation/utils.py,sha256=
|
|
817
|
+
supervisely/nn/benchmark/utils/semantic_segmentation/utils.py,sha256=X5NiR02R-0To2_SuSGHZZccl_-Bupg5F9d7nziIMRMc,3874
|
|
818
818
|
supervisely/nn/benchmark/visualization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
819
819
|
supervisely/nn/benchmark/visualization/evaluation_result.py,sha256=733HJL4rJa5XqCJydW9vSyaepvpHzym9wQsw1wFEgeI,10251
|
|
820
820
|
supervisely/nn/benchmark/visualization/renderer.py,sha256=qxWUYu2glTqksxI5UG08nwDmZA4A2bfGb1wk9DaDct8,3340
|
|
@@ -931,14 +931,14 @@ supervisely/nn/tracker/deep_sort/sly_tracker.py,sha256=uhOiwJMt7kh53X_zeWecSm6f3
|
|
|
931
931
|
supervisely/nn/tracker/deep_sort/deep_sort/__init__.py,sha256=RleTF3kMKdZteclPHRm7E49pw1I1GlooqToAv3YmBd0,27
|
|
932
932
|
supervisely/nn/tracker/deep_sort/deep_sort/detection.py,sha256=cS7YqK8RPbr7C3l6myKTHGBvQbYoKxnEyVa4ZGVbsLY,1430
|
|
933
933
|
supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py,sha256=HSzn9FjOSHrqH9h6qJlYe1i4o0ZJhjERxCcuIYnLmPE,2830
|
|
934
|
-
supervisely/nn/tracker/deep_sort/deep_sort/kalman_filter.py,sha256
|
|
934
|
+
supervisely/nn/tracker/deep_sort/deep_sort/kalman_filter.py,sha256=wDSbs2QA3cnnWB-WAzjNcKKqGiNZmfpN0_Cm9dTxU38,7888
|
|
935
935
|
supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py,sha256=hAqMNk2CFPDJvmGJCfNbGVngUgKGM2oYldaGaU2zWC4,7871
|
|
936
936
|
supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py,sha256=U0NYReianEohMc9eA_eAuXjqMkqkNjWlq40S7VvYKHs,5469
|
|
937
937
|
supervisely/nn/tracker/deep_sort/deep_sort/track.py,sha256=eNvBlhhBcTzyTWOX_TKyT3sWJM6fSz3vPi30kfz8wE4,4976
|
|
938
938
|
supervisely/nn/tracker/deep_sort/deep_sort/tracker.py,sha256=cz--uel4ROLHPem8dOofitCoQj_bTd8srWFp2FpaBR0,5357
|
|
939
939
|
supervisely/nn/tracker/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
940
940
|
supervisely/nn/tracker/utils/gmc.py,sha256=3JX8979H3NA-YHNaRQyj9Z-xb9qtyMittPEjGw8y2Jo,11557
|
|
941
|
-
supervisely/nn/tracker/utils/kalman_filter.py,sha256=
|
|
941
|
+
supervisely/nn/tracker/utils/kalman_filter.py,sha256=eSFmCjM0mikHCAFvj-KCVzw-0Jxpoc3Cfc2NWEjJC1Q,17268
|
|
942
942
|
supervisely/nn/training/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
943
943
|
supervisely/nn/training/train_app.py,sha256=E4Qpr9KSEuPLAUJCVUHyt_-tpPQrPRnmpNBGCjnURyw,80464
|
|
944
944
|
supervisely/nn/training/gui/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
|
|
@@ -976,10 +976,10 @@ supervisely/pointcloud_episodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
|
|
|
976
976
|
supervisely/pointcloud_episodes/pointcloud_episodes.py,sha256=cRXdtw7bMsbsdVQjxfWxFSESrO-LGiqqsZyyExl2Mbg,3430
|
|
977
977
|
supervisely/project/__init__.py,sha256=hlzdj9Pgy53Q3qdP8LMtGTChvZHQuuShdtui2eRUQeE,2601
|
|
978
978
|
supervisely/project/data_version.py,sha256=nknaWJSUCwoDyNG9_d1KA-GjzidhV9zd9Cn8cg15DOU,19270
|
|
979
|
-
supervisely/project/download.py,sha256=
|
|
979
|
+
supervisely/project/download.py,sha256=zb8sb4XZ6Qi3CP7fmtLRUAYzaxs_W0WnOfe2x3ZVRMs,24639
|
|
980
980
|
supervisely/project/pointcloud_episode_project.py,sha256=fcaFAaHVn_VvdiIfHl4IyEFE5-Q3VFGfo7_YoxEma0I,41341
|
|
981
981
|
supervisely/project/pointcloud_project.py,sha256=Y8Xhi6Hg-KyztwFncezuDfKTt2FILss96EU_LdXzmrA,49172
|
|
982
|
-
supervisely/project/project.py,sha256=
|
|
982
|
+
supervisely/project/project.py,sha256=UY7nLGRNcUO6nDvBlirsUg0mbX6XW7_tXoqGaofojqo,188125
|
|
983
983
|
supervisely/project/project_meta.py,sha256=26s8IiHC5Pg8B1AQi6_CrsWteioJP2in00cRNe8QlW0,51423
|
|
984
984
|
supervisely/project/project_settings.py,sha256=NLThzU_DCynOK6hkHhVdFyezwprn9UqlnrLDe_3qhkY,9347
|
|
985
985
|
supervisely/project/project_type.py,sha256=_3RqW2CnDBKFOvSIrQT1RJQaiHirs34_jiQS8CkwCpo,530
|
|
@@ -1041,9 +1041,9 @@ supervisely/worker_proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
1041
1041
|
supervisely/worker_proto/worker_api_pb2.py,sha256=VQfi5JRBHs2pFCK1snec3JECgGnua3Xjqw_-b3aFxuM,59142
|
|
1042
1042
|
supervisely/worker_proto/worker_api_pb2_grpc.py,sha256=3BwQXOaP9qpdi0Dt9EKG--Lm8KGN0C5AgmUfRv77_Jk,28940
|
|
1043
1043
|
supervisely_lib/__init__.py,sha256=7-3QnN8Zf0wj8NCr2oJmqoQWMKKPKTECvjH9pd2S5vY,159
|
|
1044
|
-
supervisely-6.73.
|
|
1045
|
-
supervisely-6.73.
|
|
1046
|
-
supervisely-6.73.
|
|
1047
|
-
supervisely-6.73.
|
|
1048
|
-
supervisely-6.73.
|
|
1049
|
-
supervisely-6.73.
|
|
1044
|
+
supervisely-6.73.248.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
1045
|
+
supervisely-6.73.248.dist-info/METADATA,sha256=0R4I5m8-EAFFV8m29WqXyaj3kbtRi5O8AbRa3OeyCpI,33351
|
|
1046
|
+
supervisely-6.73.248.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
1047
|
+
supervisely-6.73.248.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
|
|
1048
|
+
supervisely-6.73.248.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
|
|
1049
|
+
supervisely-6.73.248.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|