supervisely 6.73.343__py3-none-any.whl → 6.73.345__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.
- supervisely/__init__.py +3 -2
- supervisely/_utils.py +33 -1
- supervisely/api/annotation_api.py +369 -2
- supervisely/api/api.py +14 -5
- supervisely/api/dataset_api.py +177 -1
- supervisely/api/entity_annotation/figure_api.py +84 -0
- supervisely/api/file_api.py +2 -2
- supervisely/api/image_api.py +740 -52
- supervisely/api/module_api.py +11 -0
- supervisely/api/project_api.py +6 -1
- supervisely/convert/converter.py +4 -0
- supervisely/convert/image/sly/fast_sly_image_converter.py +11 -5
- supervisely/convert/image/sly/sly_image_converter.py +41 -1
- supervisely/io/fs.py +238 -4
- supervisely/project/data_version.py +7 -1
- supervisely/project/download.py +5 -16
- supervisely/project/project.py +721 -79
- supervisely/project/project_type.py +2 -0
- supervisely/project/readme_template.md +19 -13
- {supervisely-6.73.343.dist-info → supervisely-6.73.345.dist-info}/METADATA +1 -1
- {supervisely-6.73.343.dist-info → supervisely-6.73.345.dist-info}/RECORD +25 -25
- {supervisely-6.73.343.dist-info → supervisely-6.73.345.dist-info}/LICENSE +0 -0
- {supervisely-6.73.343.dist-info → supervisely-6.73.345.dist-info}/WHEEL +0 -0
- {supervisely-6.73.343.dist-info → supervisely-6.73.345.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.343.dist-info → supervisely-6.73.345.dist-info}/top_level.txt +0 -0
supervisely/api/dataset_api.py
CHANGED
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
# docs
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
7
9
|
from typing import (
|
|
10
|
+
TYPE_CHECKING,
|
|
8
11
|
Any,
|
|
9
12
|
Dict,
|
|
10
13
|
Generator,
|
|
@@ -16,7 +19,15 @@ from typing import (
|
|
|
16
19
|
Union,
|
|
17
20
|
)
|
|
18
21
|
|
|
19
|
-
from
|
|
22
|
+
from tqdm import tqdm
|
|
23
|
+
|
|
24
|
+
from supervisely._utils import (
|
|
25
|
+
abs_url,
|
|
26
|
+
compress_image_url,
|
|
27
|
+
is_development,
|
|
28
|
+
run_coroutine,
|
|
29
|
+
)
|
|
30
|
+
from supervisely.annotation.annotation import Annotation
|
|
20
31
|
from supervisely.api.module_api import (
|
|
21
32
|
ApiField,
|
|
22
33
|
ModuleApi,
|
|
@@ -24,8 +35,14 @@ from supervisely.api.module_api import (
|
|
|
24
35
|
UpdateableModule,
|
|
25
36
|
_get_single_item,
|
|
26
37
|
)
|
|
38
|
+
from supervisely.io.json import load_json_file
|
|
27
39
|
from supervisely.project.project_type import ProjectType
|
|
28
40
|
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from supervisely.project.project import ProjectMeta
|
|
43
|
+
|
|
44
|
+
from supervisely import logger
|
|
45
|
+
|
|
29
46
|
|
|
30
47
|
class DatasetInfo(NamedTuple):
|
|
31
48
|
"""Represent Supervisely Dataset information.
|
|
@@ -1090,3 +1107,162 @@ class DatasetApi(UpdateableModule, RemoveableModuleApi):
|
|
|
1090
1107
|
:rtype: bool
|
|
1091
1108
|
"""
|
|
1092
1109
|
return self.get_info_by_name(project_id, name, parent_id=parent_id) is not None
|
|
1110
|
+
|
|
1111
|
+
def quick_import(
|
|
1112
|
+
self,
|
|
1113
|
+
dataset: Union[int, DatasetInfo],
|
|
1114
|
+
blob_path: str,
|
|
1115
|
+
offsets_path: str,
|
|
1116
|
+
anns: List[str],
|
|
1117
|
+
project_meta: Optional[ProjectMeta] = None,
|
|
1118
|
+
project_type: Optional[ProjectType] = None,
|
|
1119
|
+
log_progress: bool = True,
|
|
1120
|
+
):
|
|
1121
|
+
"""
|
|
1122
|
+
Quick import of images and annotations to the dataset.
|
|
1123
|
+
Used only for extended Supervisely format with blobs.
|
|
1124
|
+
Project will be automatically marked as blob project.
|
|
1125
|
+
|
|
1126
|
+
IMPORTANT: Number of annotations must be equal to the number of images in offset file.
|
|
1127
|
+
Image names in the offset file and annotation files must match.
|
|
1128
|
+
|
|
1129
|
+
:param dataset: Dataset ID or DatasetInfo object.
|
|
1130
|
+
:type dataset: Union[int, DatasetInfo]
|
|
1131
|
+
:param blob_path: Local path to the blob file.
|
|
1132
|
+
:type blob_path: str
|
|
1133
|
+
:param offsets_path: Local path to the offsets file.
|
|
1134
|
+
:type offsets_path: str
|
|
1135
|
+
:param anns: List of annotation paths.
|
|
1136
|
+
:type anns: List[str]
|
|
1137
|
+
:param project_meta: ProjectMeta object.
|
|
1138
|
+
:type project_meta: Optional[ProjectMeta], optional
|
|
1139
|
+
:param project_type: Project type.
|
|
1140
|
+
:type project_type: Optional[ProjectType], optional
|
|
1141
|
+
:param log_progress: If True, show progress bar.
|
|
1142
|
+
:type log_progress: bool, optional
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
:Usage example:
|
|
1146
|
+
|
|
1147
|
+
.. code-block:: python
|
|
1148
|
+
|
|
1149
|
+
import supervisely as sly
|
|
1150
|
+
from supervisely.project.project_meta import ProjectMeta
|
|
1151
|
+
from supervisely.project.project_type import ProjectType
|
|
1152
|
+
|
|
1153
|
+
api = sly.Api.from_env()
|
|
1154
|
+
|
|
1155
|
+
dataset_id = 123
|
|
1156
|
+
workspace_id = 456
|
|
1157
|
+
blob_path = "/path/to/blob"
|
|
1158
|
+
offsets_path = "/path/to/offsets"
|
|
1159
|
+
project_meta_path = "/path/to/project_meta.json"
|
|
1160
|
+
anns = ["/path/to/ann1.json", "/path/to/ann2.json", ...]
|
|
1161
|
+
|
|
1162
|
+
# Create a new project, dataset and update its meta
|
|
1163
|
+
project = api.project.create(
|
|
1164
|
+
workspace_id,
|
|
1165
|
+
"Quick Import",
|
|
1166
|
+
type=sly.ProjectType.IMAGES,
|
|
1167
|
+
change_name_if_conflict=True,
|
|
1168
|
+
)
|
|
1169
|
+
dataset = api.dataset.create(project.id, "ds1")
|
|
1170
|
+
project_meta_json = sly.json.load_json_file(project_meta_path)
|
|
1171
|
+
meta = api.project.update_meta(project.id, meta=project_meta_json)
|
|
1172
|
+
|
|
1173
|
+
dataset_info = api.dataset.quick_import(
|
|
1174
|
+
dataset=dataset.id,
|
|
1175
|
+
blob_path=blob_path,
|
|
1176
|
+
offsets_path=offsets_path,
|
|
1177
|
+
anns=anns,
|
|
1178
|
+
project_meta=ProjectMeta(),
|
|
1179
|
+
project_type=ProjectType.IMAGES,
|
|
1180
|
+
log_progress=True
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1183
|
+
"""
|
|
1184
|
+
from supervisely.api.api import Api, ApiContext
|
|
1185
|
+
from supervisely.api.image_api import _BLOB_TAG_NAME
|
|
1186
|
+
from supervisely.project.project import TF_BLOB_DIR, ProjectMeta
|
|
1187
|
+
|
|
1188
|
+
def _ann_objects_generator(ann_paths, project_meta):
|
|
1189
|
+
for ann in ann_paths:
|
|
1190
|
+
ann_json = load_json_file(ann)
|
|
1191
|
+
yield Annotation.from_json(ann_json, project_meta)
|
|
1192
|
+
|
|
1193
|
+
self._api: Api
|
|
1194
|
+
|
|
1195
|
+
if isinstance(dataset, int):
|
|
1196
|
+
dataset = self.get_info_by_id(dataset)
|
|
1197
|
+
|
|
1198
|
+
project_info = self._api.project.get_info_by_id(dataset.project_id)
|
|
1199
|
+
|
|
1200
|
+
if project_meta is None:
|
|
1201
|
+
meta_dict = self._api.project.get_meta(dataset.project_id)
|
|
1202
|
+
project_meta = ProjectMeta.from_json(meta_dict)
|
|
1203
|
+
|
|
1204
|
+
if project_type is None:
|
|
1205
|
+
project_type = project_info.type
|
|
1206
|
+
|
|
1207
|
+
if project_type != ProjectType.IMAGES:
|
|
1208
|
+
raise NotImplementedError(
|
|
1209
|
+
f"Quick import is not implemented for project type {project_type}"
|
|
1210
|
+
)
|
|
1211
|
+
|
|
1212
|
+
# Set optimization context
|
|
1213
|
+
with ApiContext(
|
|
1214
|
+
api=self._api,
|
|
1215
|
+
project_id=dataset.project_id,
|
|
1216
|
+
dataset_id=dataset.id,
|
|
1217
|
+
project_meta=project_meta,
|
|
1218
|
+
):
|
|
1219
|
+
dst_blob_path = os.path.join(f"/{TF_BLOB_DIR}", os.path.basename(blob_path))
|
|
1220
|
+
dst_offset_path = os.path.join(f"/{TF_BLOB_DIR}", os.path.basename(offsets_path))
|
|
1221
|
+
if log_progress:
|
|
1222
|
+
sizeb = os.path.getsize(blob_path) + os.path.getsize(offsets_path)
|
|
1223
|
+
b_progress_cb = tqdm(
|
|
1224
|
+
total=sizeb,
|
|
1225
|
+
unit="B",
|
|
1226
|
+
unit_scale=True,
|
|
1227
|
+
desc=f"Uploading blob to file storage",
|
|
1228
|
+
)
|
|
1229
|
+
else:
|
|
1230
|
+
b_progress_cb = None
|
|
1231
|
+
|
|
1232
|
+
self._api.file.upload_bulk_fast(
|
|
1233
|
+
team_id=project_info.team_id,
|
|
1234
|
+
src_paths=[blob_path, offsets_path],
|
|
1235
|
+
dst_paths=[dst_blob_path, dst_offset_path],
|
|
1236
|
+
progress_cb=b_progress_cb.update,
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
blob_file_id = self._api.file.get_info_by_path(project_info.team_id, dst_blob_path).id
|
|
1240
|
+
|
|
1241
|
+
if log_progress:
|
|
1242
|
+
of_progress_cb = tqdm(desc=f"Uploading images by offsets", total=len(anns)).update
|
|
1243
|
+
else:
|
|
1244
|
+
of_progress_cb = None
|
|
1245
|
+
|
|
1246
|
+
image_info_generator = self._api.image.upload_by_offsets_generator(
|
|
1247
|
+
dataset=dataset,
|
|
1248
|
+
team_file_id=blob_file_id,
|
|
1249
|
+
progress_cb=of_progress_cb,
|
|
1250
|
+
)
|
|
1251
|
+
|
|
1252
|
+
ann_map = {Path(ann).stem: ann for ann in anns}
|
|
1253
|
+
|
|
1254
|
+
for image_info_batch in image_info_generator:
|
|
1255
|
+
img_ids = [img_info.id for img_info in image_info_batch]
|
|
1256
|
+
img_names = [img_info.name for img_info in image_info_batch]
|
|
1257
|
+
img_anns = [ann_map[img_name] for img_name in img_names]
|
|
1258
|
+
ann_objects = _ann_objects_generator(img_anns, project_meta)
|
|
1259
|
+
coroutine = self._api.annotation.upload_anns_async(
|
|
1260
|
+
image_ids=img_ids, anns=ann_objects, log_progress=log_progress
|
|
1261
|
+
)
|
|
1262
|
+
run_coroutine(coroutine)
|
|
1263
|
+
try:
|
|
1264
|
+
custom_data = self._api.project.get_custom_data(dataset.project_id)
|
|
1265
|
+
custom_data[_BLOB_TAG_NAME] = True
|
|
1266
|
+
self._api.project.update_custom_data(dataset.project_id, custom_data)
|
|
1267
|
+
except:
|
|
1268
|
+
logger.warning("Failed to set blob tag for project")
|
|
@@ -700,3 +700,87 @@ class FigureApi(RemoveableBulkModuleApi):
|
|
|
700
700
|
raise RuntimeError("Not all geometries were downloaded")
|
|
701
701
|
ordered_results = [geometries[i] for i in ids]
|
|
702
702
|
return ordered_results
|
|
703
|
+
|
|
704
|
+
async def upload_geometries_batch_async(
|
|
705
|
+
self,
|
|
706
|
+
figure_ids: List[int],
|
|
707
|
+
geometries: List[dict],
|
|
708
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
709
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
710
|
+
) -> None:
|
|
711
|
+
"""
|
|
712
|
+
Upload figure geometries with given figure IDs to storage asynchronously in batches.
|
|
713
|
+
|
|
714
|
+
:param figure_ids: List of figure IDs in Supervisely.
|
|
715
|
+
:type figure_ids: List[int]
|
|
716
|
+
:param geometries: List of figure geometries in Supervisely JSON format.
|
|
717
|
+
:type geometries: List[dict]
|
|
718
|
+
:param semaphore: Semaphore to limit the number of concurrent uploads.
|
|
719
|
+
:type semaphore: Optional[asyncio.Semaphore], optional
|
|
720
|
+
:param progress_cb: Progress bar to show the upload progress. Shows the number of geometries uploaded.
|
|
721
|
+
:type progress_cb: Union[tqdm, Callable], optional
|
|
722
|
+
:return: None
|
|
723
|
+
:rtype: None
|
|
724
|
+
|
|
725
|
+
:Usage example:
|
|
726
|
+
|
|
727
|
+
.. code-block:: python
|
|
728
|
+
|
|
729
|
+
import asyncio
|
|
730
|
+
import supervisely as sly
|
|
731
|
+
|
|
732
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
733
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
734
|
+
api = sly.Api.from_env()
|
|
735
|
+
|
|
736
|
+
figure_ids = [642155547, 642155548, 642155549]
|
|
737
|
+
geometries = [{...}, {...}, {...}] # Your geometry data
|
|
738
|
+
|
|
739
|
+
upload_coroutine = api.figure.upload_geometries_batch_async(
|
|
740
|
+
figure_ids,
|
|
741
|
+
geometries,
|
|
742
|
+
semaphore=asyncio.Semaphore(10),
|
|
743
|
+
)
|
|
744
|
+
sly.run_coroutine(upload_coroutine)
|
|
745
|
+
"""
|
|
746
|
+
if semaphore is None:
|
|
747
|
+
semaphore = self._api.get_default_semaphore()
|
|
748
|
+
|
|
749
|
+
encoded_geometries = [json.dumps(geometry).encode("utf-8") for geometry in geometries]
|
|
750
|
+
|
|
751
|
+
batch_size = 100
|
|
752
|
+
tasks = []
|
|
753
|
+
|
|
754
|
+
for batch_ids, batch_geometries in zip(
|
|
755
|
+
batched(figure_ids, batch_size),
|
|
756
|
+
batched(encoded_geometries, batch_size),
|
|
757
|
+
):
|
|
758
|
+
fields = []
|
|
759
|
+
for figure_id, geometry in zip(batch_ids, batch_geometries):
|
|
760
|
+
fields.append((ApiField.FIGURE_ID, str(figure_id)))
|
|
761
|
+
fields.append(
|
|
762
|
+
(
|
|
763
|
+
ApiField.GEOMETRY,
|
|
764
|
+
(str(figure_id), geometry, "application/octet-stream"),
|
|
765
|
+
)
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
async def upload_batch(fields, progress_cb, num):
|
|
769
|
+
async with semaphore:
|
|
770
|
+
encoder = MultipartEncoder(fields=fields)
|
|
771
|
+
headers = {"Content-Type": encoder.content_type}
|
|
772
|
+
async for _, _ in self._api.stream_async(
|
|
773
|
+
"figures.bulk.upload.geometry",
|
|
774
|
+
"POST",
|
|
775
|
+
data=encoder,
|
|
776
|
+
content=encoder.to_string(),
|
|
777
|
+
headers=headers,
|
|
778
|
+
):
|
|
779
|
+
pass
|
|
780
|
+
if progress_cb is not None:
|
|
781
|
+
progress_cb.update(num)
|
|
782
|
+
|
|
783
|
+
tasks.append(upload_batch(fields, progress_cb, len(batch_ids)))
|
|
784
|
+
|
|
785
|
+
if tasks:
|
|
786
|
+
await asyncio.gather(*tasks)
|
supervisely/api/file_api.py
CHANGED
|
@@ -813,7 +813,7 @@ class FileApi(ModuleApiBase):
|
|
|
813
813
|
:type src: List[str]
|
|
814
814
|
:param dst: Destination paths for Files to Team Files.
|
|
815
815
|
:type dst: List[str]
|
|
816
|
-
:param progress_cb: Function for tracking
|
|
816
|
+
:param progress_cb: Function for tracking upload progress.
|
|
817
817
|
:type progress_cb: tqdm or callable, optional
|
|
818
818
|
:return: Information about Files. See :class:`info_sequence<info_sequence>`
|
|
819
819
|
:rtype: :class:`List[FileInfo]`
|
|
@@ -1546,7 +1546,7 @@ class FileApi(ModuleApiBase):
|
|
|
1546
1546
|
sly_fs.remove_dir(temp_path)
|
|
1547
1547
|
return content
|
|
1548
1548
|
else:
|
|
1549
|
-
raise FileNotFoundError(f"File not found: {remote_path}")
|
|
1549
|
+
raise FileNotFoundError(f"File not found in Team Files at path: {remote_path}")
|
|
1550
1550
|
|
|
1551
1551
|
async def _download_async(
|
|
1552
1552
|
self,
|