supervisely 6.73.410__py3-none-any.whl → 6.73.470__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 +136 -1
- supervisely/_utils.py +81 -0
- supervisely/annotation/json_geometries_map.py +2 -0
- supervisely/annotation/label.py +80 -3
- supervisely/api/annotation_api.py +9 -9
- supervisely/api/api.py +67 -43
- supervisely/api/app_api.py +72 -5
- supervisely/api/dataset_api.py +108 -33
- supervisely/api/entity_annotation/figure_api.py +113 -49
- supervisely/api/image_api.py +82 -0
- supervisely/api/module_api.py +10 -0
- supervisely/api/nn/deploy_api.py +15 -9
- supervisely/api/nn/ecosystem_models_api.py +201 -0
- supervisely/api/nn/neural_network_api.py +12 -3
- supervisely/api/pointcloud/pointcloud_api.py +38 -0
- supervisely/api/pointcloud/pointcloud_episode_annotation_api.py +3 -0
- supervisely/api/project_api.py +213 -6
- supervisely/api/task_api.py +11 -1
- supervisely/api/video/video_annotation_api.py +4 -2
- supervisely/api/video/video_api.py +79 -1
- supervisely/api/video/video_figure_api.py +24 -11
- supervisely/api/volume/volume_api.py +38 -0
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +14 -6
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +88 -0
- supervisely/app/fastapi/subapp.py +175 -42
- supervisely/app/fastapi/templating.py +1 -1
- supervisely/app/fastapi/websocket.py +77 -9
- supervisely/app/singleton.py +21 -0
- supervisely/app/v1/app_service.py +18 -2
- supervisely/app/v1/constants.py +7 -1
- supervisely/app/widgets/__init__.py +11 -1
- supervisely/app/widgets/agent_selector/template.html +1 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/dataset_thumbnail/dataset_thumbnail.py +11 -2
- supervisely/app/widgets/dataset_thumbnail/template.html +3 -1
- supervisely/app/widgets/deploy_model/deploy_model.py +750 -0
- supervisely/app/widgets/dialog/dialog.py +12 -0
- supervisely/app/widgets/dialog/template.html +2 -1
- supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
- supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
- supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
- supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +195 -0
- supervisely/app/widgets/experiment_selector/experiment_selector.py +454 -263
- supervisely/app/widgets/fast_table/fast_table.py +713 -126
- supervisely/app/widgets/fast_table/script.js +492 -95
- supervisely/app/widgets/fast_table/style.css +54 -0
- supervisely/app/widgets/fast_table/template.html +45 -5
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +523 -0
- supervisely/app/widgets/heatmap/script.js +378 -0
- supervisely/app/widgets/heatmap/style.css +227 -0
- supervisely/app/widgets/heatmap/template.html +21 -0
- supervisely/app/widgets/input_tag/input_tag.py +102 -15
- supervisely/app/widgets/input_tag_list/__init__.py +0 -0
- supervisely/app/widgets/input_tag_list/input_tag_list.py +274 -0
- supervisely/app/widgets/input_tag_list/template.html +70 -0
- supervisely/app/widgets/radio_table/radio_table.py +10 -2
- supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
- supervisely/app/widgets/radio_tabs/template.html +1 -0
- supervisely/app/widgets/select/select.py +6 -4
- supervisely/app/widgets/select_dataset/select_dataset.py +6 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +83 -7
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tabs/tabs.py +22 -6
- supervisely/app/widgets/tabs/template.html +5 -1
- supervisely/app/widgets/transfer/style.css +3 -0
- supervisely/app/widgets/transfer/template.html +3 -1
- supervisely/app/widgets/transfer/transfer.py +48 -45
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/convert/image/csv/csv_converter.py +24 -15
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +43 -41
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +75 -51
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +137 -124
- supervisely/convert/video/video_converter.py +2 -2
- supervisely/geometry/polyline_3d.py +110 -0
- supervisely/io/env.py +161 -1
- supervisely/nn/artifacts/__init__.py +1 -1
- supervisely/nn/artifacts/artifacts.py +10 -2
- supervisely/nn/artifacts/detectron2.py +1 -0
- supervisely/nn/artifacts/hrda.py +1 -0
- supervisely/nn/artifacts/mmclassification.py +20 -0
- supervisely/nn/artifacts/mmdetection.py +5 -3
- supervisely/nn/artifacts/mmsegmentation.py +1 -0
- supervisely/nn/artifacts/ritm.py +1 -0
- supervisely/nn/artifacts/rtdetr.py +1 -0
- supervisely/nn/artifacts/unet.py +1 -0
- supervisely/nn/artifacts/utils.py +3 -0
- supervisely/nn/artifacts/yolov5.py +2 -0
- supervisely/nn/artifacts/yolov8.py +1 -0
- supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
- supervisely/nn/experiments.py +9 -0
- supervisely/nn/inference/cache.py +37 -17
- supervisely/nn/inference/gui/serving_gui_template.py +39 -13
- supervisely/nn/inference/inference.py +953 -211
- supervisely/nn/inference/inference_request.py +15 -8
- supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
- supervisely/nn/inference/object_detection/object_detection.py +1 -0
- supervisely/nn/inference/predict_app/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/classes_selector.py +160 -0
- supervisely/nn/inference/predict_app/gui/gui.py +915 -0
- supervisely/nn/inference/predict_app/gui/input_selector.py +344 -0
- supervisely/nn/inference/predict_app/gui/model_selector.py +77 -0
- supervisely/nn/inference/predict_app/gui/output_selector.py +179 -0
- supervisely/nn/inference/predict_app/gui/preview.py +93 -0
- supervisely/nn/inference/predict_app/gui/settings_selector.py +881 -0
- supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
- supervisely/nn/inference/predict_app/gui/utils.py +399 -0
- supervisely/nn/inference/predict_app/predict_app.py +176 -0
- supervisely/nn/inference/session.py +47 -39
- supervisely/nn/inference/tracking/bbox_tracking.py +5 -1
- supervisely/nn/inference/tracking/point_tracking.py +5 -1
- supervisely/nn/inference/tracking/tracker_interface.py +4 -0
- supervisely/nn/inference/uploader.py +9 -5
- supervisely/nn/model/model_api.py +44 -22
- supervisely/nn/model/prediction.py +15 -1
- supervisely/nn/model/prediction_session.py +70 -14
- supervisely/nn/prediction_dto.py +7 -0
- supervisely/nn/tracker/__init__.py +6 -8
- supervisely/nn/tracker/base_tracker.py +54 -0
- supervisely/nn/tracker/botsort/__init__.py +1 -0
- supervisely/nn/tracker/botsort/botsort_config.yaml +30 -0
- supervisely/nn/tracker/botsort/osnet_reid/__init__.py +0 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
- supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
- supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
- supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
- supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
- supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
- supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
- supervisely/nn/tracker/botsort_tracker.py +273 -0
- supervisely/nn/tracker/calculate_metrics.py +264 -0
- supervisely/nn/tracker/utils.py +273 -0
- supervisely/nn/tracker/visualize.py +520 -0
- supervisely/nn/training/gui/gui.py +152 -49
- supervisely/nn/training/gui/hyperparameters_selector.py +1 -1
- supervisely/nn/training/gui/model_selector.py +8 -6
- supervisely/nn/training/gui/train_val_splits_selector.py +144 -71
- supervisely/nn/training/gui/training_artifacts.py +3 -1
- supervisely/nn/training/train_app.py +225 -46
- supervisely/project/pointcloud_episode_project.py +12 -8
- supervisely/project/pointcloud_project.py +12 -8
- supervisely/project/project.py +221 -75
- supervisely/template/experiment/experiment.html.jinja +105 -55
- supervisely/template/experiment/experiment_generator.py +258 -112
- supervisely/template/experiment/header.html.jinja +31 -13
- supervisely/template/experiment/sly-style.css +7 -2
- supervisely/versions.json +3 -1
- supervisely/video/sampling.py +42 -20
- supervisely/video/video.py +41 -12
- supervisely/video_annotation/video_figure.py +38 -4
- supervisely/volume/stl_converter.py +2 -0
- supervisely/worker_api/agent_rpc.py +24 -1
- supervisely/worker_api/rpc_servicer.py +31 -7
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/METADATA +22 -14
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/RECORD +167 -148
- supervisely_lib/__init__.py +6 -1
- supervisely/app/widgets/experiment_selector/style.css +0 -27
- supervisely/app/widgets/experiment_selector/template.html +0 -61
- supervisely/nn/tracker/bot_sort/__init__.py +0 -21
- supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
- supervisely/nn/tracker/bot_sort/matching.py +0 -127
- supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
- supervisely/nn/tracker/deep_sort/__init__.py +0 -6
- supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
- supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
- supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
- supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
- supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
- supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
- supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
- supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
- supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
- supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
- supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
- supervisely/nn/tracker/tracker.py +0 -285
- supervisely/nn/tracker/utils/kalman_filter.py +0 -492
- supervisely/nn/tracking/__init__.py +0 -1
- supervisely/nn/tracking/boxmot.py +0 -114
- supervisely/nn/tracking/tracking.py +0 -24
- /supervisely/{nn/tracker/utils → app/widgets/deploy_model}/__init__.py +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/LICENSE +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/WHEEL +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/top_level.txt +0 -0
supervisely/__init__.py
CHANGED
|
@@ -8,6 +8,22 @@ try:
|
|
|
8
8
|
except TypeError as e:
|
|
9
9
|
__version__ = "development"
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
class _ApiProtoNotAvailable:
|
|
13
|
+
"""Placeholder class that raises an error when accessing any attribute"""
|
|
14
|
+
|
|
15
|
+
def __getattr__(self, name):
|
|
16
|
+
from supervisely.app.v1.constants import PROTOBUF_REQUIRED_ERROR
|
|
17
|
+
|
|
18
|
+
raise ImportError(f"Cannot access `api_proto.{name}` : " + PROTOBUF_REQUIRED_ERROR)
|
|
19
|
+
|
|
20
|
+
def __bool__(self):
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
def __repr__(self):
|
|
24
|
+
return "<api_proto: not available - install supervisely[agent] to enable>"
|
|
25
|
+
|
|
26
|
+
|
|
11
27
|
from supervisely.sly_logger import (
|
|
12
28
|
logger,
|
|
13
29
|
ServiceType,
|
|
@@ -112,7 +128,14 @@ from supervisely.worker_api.chunking import (
|
|
|
112
128
|
ChunkedFileWriter,
|
|
113
129
|
ChunkedFileReader,
|
|
114
130
|
)
|
|
115
|
-
|
|
131
|
+
|
|
132
|
+
# Global import of api_proto works only if protobuf is installed and compatible
|
|
133
|
+
# Otherwise, we use a placeholder that raises an error when accessed
|
|
134
|
+
try:
|
|
135
|
+
import supervisely.worker_proto.worker_api_pb2 as api_proto
|
|
136
|
+
except Exception:
|
|
137
|
+
api_proto = _ApiProtoNotAvailable()
|
|
138
|
+
|
|
116
139
|
|
|
117
140
|
from supervisely.api.api import Api, UserSession, ApiContext
|
|
118
141
|
from supervisely.api import api
|
|
@@ -318,3 +341,115 @@ except Exception as e:
|
|
|
318
341
|
from supervisely.io.env import configure_minimum_instance_version
|
|
319
342
|
|
|
320
343
|
configure_minimum_instance_version()
|
|
344
|
+
|
|
345
|
+
LARGE_ENV_PLACEHOLDER = "@.@SLY_LARGE_ENV@.@"
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def restore_env_vars():
|
|
349
|
+
try:
|
|
350
|
+
large_env_keys = []
|
|
351
|
+
for key, value in os.environ.items():
|
|
352
|
+
if value == LARGE_ENV_PLACEHOLDER:
|
|
353
|
+
large_env_keys.append(key)
|
|
354
|
+
if len(large_env_keys) == 0:
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
if utils.is_development():
|
|
358
|
+
logger.info(
|
|
359
|
+
"Large environment variables detected. Skipping restoration in development mode.",
|
|
360
|
+
extra={"keys": large_env_keys},
|
|
361
|
+
)
|
|
362
|
+
return
|
|
363
|
+
|
|
364
|
+
unknown_keys = []
|
|
365
|
+
state_keys = []
|
|
366
|
+
context_keys = []
|
|
367
|
+
for key in large_env_keys:
|
|
368
|
+
if key == "CONTEXT" or key.startswith("context."):
|
|
369
|
+
context_keys.append(key)
|
|
370
|
+
elif key.startswith("MODAL_STATE") or key.startswith("modal.state."):
|
|
371
|
+
state_keys.append(key)
|
|
372
|
+
else:
|
|
373
|
+
unknown_keys.append(key)
|
|
374
|
+
|
|
375
|
+
if state_keys or context_keys:
|
|
376
|
+
api = Api()
|
|
377
|
+
if state_keys:
|
|
378
|
+
task_info = api.task.get_info_by_id(env.task_id())
|
|
379
|
+
state = task_info.get("meta", {}).get("params", {}).get("state", {})
|
|
380
|
+
modal_state_envs = json.flatten_json(state)
|
|
381
|
+
modal_state_envs = json.modify_keys(modal_state_envs, prefix="modal.state.")
|
|
382
|
+
|
|
383
|
+
restored_keys = []
|
|
384
|
+
not_found_keys = []
|
|
385
|
+
for key in state_keys:
|
|
386
|
+
if key == "MODAL_STATE":
|
|
387
|
+
os.environ[key] = json.json.dumps(state)
|
|
388
|
+
elif key in modal_state_envs:
|
|
389
|
+
os.environ[key] = str(modal_state_envs[key])
|
|
390
|
+
elif key.replace("_", ".") in [k.upper() for k in modal_state_envs]:
|
|
391
|
+
# some env vars do not support dots in their names
|
|
392
|
+
k = next(k for k in modal_state_envs if k.upper() == key.replace("_", "."))
|
|
393
|
+
os.environ[key] = str(modal_state_envs[k])
|
|
394
|
+
else:
|
|
395
|
+
not_found_keys.append(key)
|
|
396
|
+
continue
|
|
397
|
+
restored_keys.append(key)
|
|
398
|
+
|
|
399
|
+
if restored_keys:
|
|
400
|
+
logger.info(
|
|
401
|
+
"Restored large environment variables from task state",
|
|
402
|
+
extra={"keys": restored_keys},
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
if not_found_keys:
|
|
406
|
+
logger.warning(
|
|
407
|
+
"Failed to restore some large environment variables from task state. "
|
|
408
|
+
"No such keys in the state.",
|
|
409
|
+
extra={"keys": not_found_keys},
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
if context_keys:
|
|
413
|
+
context = api.task.get_context(env.task_id())
|
|
414
|
+
context_envs = json.flatten_json(context)
|
|
415
|
+
context_envs = json.modify_keys(context_envs, prefix="context.")
|
|
416
|
+
|
|
417
|
+
restored_keys = []
|
|
418
|
+
not_found_keys = []
|
|
419
|
+
for key in context_keys:
|
|
420
|
+
if key == "CONTEXT":
|
|
421
|
+
os.environ[key] = json.json.dumps(context)
|
|
422
|
+
elif key in context_envs:
|
|
423
|
+
os.environ[key] = context_envs[key]
|
|
424
|
+
else:
|
|
425
|
+
not_found_keys.append(key)
|
|
426
|
+
continue
|
|
427
|
+
restored_keys.append(key)
|
|
428
|
+
|
|
429
|
+
if restored_keys:
|
|
430
|
+
logger.info(
|
|
431
|
+
"Restored large environment variables from task context",
|
|
432
|
+
extra={"keys": restored_keys},
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
if not_found_keys:
|
|
436
|
+
logger.warning(
|
|
437
|
+
"Failed to restore some large environment variables from task context. "
|
|
438
|
+
"No such keys in the context.",
|
|
439
|
+
extra={"keys": not_found_keys},
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
if unknown_keys:
|
|
443
|
+
logger.warning(
|
|
444
|
+
"Found unknown large environment variables. Can't restore them.",
|
|
445
|
+
extra={"keys": unknown_keys},
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
except Exception as e:
|
|
449
|
+
logger.warning(
|
|
450
|
+
"Failed to restore large environment variables.",
|
|
451
|
+
exc_info=True,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
restore_env_vars()
|
supervisely/_utils.py
CHANGED
|
@@ -319,6 +319,87 @@ def resize_image_url(
|
|
|
319
319
|
return full_storage_url
|
|
320
320
|
|
|
321
321
|
|
|
322
|
+
def get_storage_url(
|
|
323
|
+
entity_type: Literal["dataset-entities", "dataset", "project", "file-storage"],
|
|
324
|
+
entity_id: int,
|
|
325
|
+
source_type: Literal["original", "preview"],
|
|
326
|
+
) -> str:
|
|
327
|
+
"""
|
|
328
|
+
Generate URL for storage resources endpoints.
|
|
329
|
+
|
|
330
|
+
:param entity_type: Type of entity ("dataset-entities", "dataset", "project", "file-storage")
|
|
331
|
+
:type entity_type: str
|
|
332
|
+
:param entity_id: ID of the entity
|
|
333
|
+
:type entity_id: int
|
|
334
|
+
:param source_type: Type of source ("original" or "preview")
|
|
335
|
+
:type source_type: Literal["original", "preview"]
|
|
336
|
+
:return: Storage URL
|
|
337
|
+
:rtype: str
|
|
338
|
+
"""
|
|
339
|
+
relative_url = f"/storage-resources/{entity_type}/{source_type}/{entity_id}"
|
|
340
|
+
if is_development():
|
|
341
|
+
return abs_url(relative_url)
|
|
342
|
+
return relative_url
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def get_image_storage_url(image_id: int, source_type: Literal["original", "preview"]) -> str:
|
|
346
|
+
"""
|
|
347
|
+
Generate URL for image storage resources.
|
|
348
|
+
|
|
349
|
+
:param image_id: ID of the image
|
|
350
|
+
:type image_id: int
|
|
351
|
+
:param source_type: Type of source ("original" or "preview")
|
|
352
|
+
:type source_type: Literal["original", "preview"]
|
|
353
|
+
:return: Storage URL for image
|
|
354
|
+
:rtype: str
|
|
355
|
+
"""
|
|
356
|
+
return get_storage_url("dataset-entities", image_id, source_type)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def get_dataset_storage_url(
|
|
360
|
+
dataset_id: int, source_type: Literal["original", "preview", "raw"]
|
|
361
|
+
) -> str:
|
|
362
|
+
"""
|
|
363
|
+
Generate URL for dataset storage resources.
|
|
364
|
+
|
|
365
|
+
:param dataset_id: ID of the dataset
|
|
366
|
+
:type dataset_id: int
|
|
367
|
+
:param source_type: Type of source ("original", "preview", or "raw")
|
|
368
|
+
:type source_type: Literal["original", "preview", "raw"]
|
|
369
|
+
:return: Storage URL for dataset
|
|
370
|
+
:rtype: str
|
|
371
|
+
"""
|
|
372
|
+
return get_storage_url("dataset", dataset_id, source_type)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def get_project_storage_url(
|
|
376
|
+
project_id: int, source_type: Literal["original", "preview", "raw"]
|
|
377
|
+
) -> str:
|
|
378
|
+
"""
|
|
379
|
+
Generate URL for project storage resources.
|
|
380
|
+
|
|
381
|
+
:param project_id: ID of the project
|
|
382
|
+
:type project_id: int
|
|
383
|
+
:param source_type: Type of source ("original", "preview", or "raw")
|
|
384
|
+
:type source_type: Literal["original", "preview", "raw"]
|
|
385
|
+
:return: Storage URL for project
|
|
386
|
+
:rtype: str
|
|
387
|
+
"""
|
|
388
|
+
return get_storage_url("project", project_id, source_type)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def get_file_storage_url(file_id: int) -> str:
|
|
392
|
+
"""
|
|
393
|
+
Generate URL for file storage resources (raw files).
|
|
394
|
+
|
|
395
|
+
:param file_id: ID of the file
|
|
396
|
+
:type file_id: int
|
|
397
|
+
:return: Storage URL for file
|
|
398
|
+
:rtype: str
|
|
399
|
+
"""
|
|
400
|
+
return get_storage_url("file-storage", file_id, "raw")
|
|
401
|
+
|
|
402
|
+
|
|
322
403
|
def get_preview_link(title="preview"):
|
|
323
404
|
return (
|
|
324
405
|
f'<a href="javascript:;">{title}<i class="zmdi zmdi-cast" style="margin-left: 5px"></i></a>'
|
|
@@ -15,6 +15,7 @@ from supervisely.geometry.multichannel_bitmap import MultichannelBitmap
|
|
|
15
15
|
from supervisely.geometry.closed_surface_mesh import ClosedSurfaceMesh
|
|
16
16
|
from supervisely.geometry.alpha_mask import AlphaMask
|
|
17
17
|
from supervisely.geometry.cuboid_2d import Cuboid2d
|
|
18
|
+
from supervisely.geometry.polyline_3d import Polyline3D
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
_INPUT_GEOMETRIES = [
|
|
@@ -34,6 +35,7 @@ _INPUT_GEOMETRIES = [
|
|
|
34
35
|
ClosedSurfaceMesh,
|
|
35
36
|
AlphaMask,
|
|
36
37
|
Cuboid2d,
|
|
38
|
+
Polyline3D,
|
|
37
39
|
]
|
|
38
40
|
_JSON_SHAPE_TO_GEOMETRY_TYPE = {
|
|
39
41
|
geometry.geometry_name(): geometry for geometry in _INPUT_GEOMETRIES
|
supervisely/annotation/label.py
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
# docs
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
from supervisely.collection.str_enum import StrEnum
|
|
7
9
|
from copy import deepcopy
|
|
8
10
|
from typing import Dict, List, Optional, Tuple, Union
|
|
9
11
|
|
|
@@ -45,6 +47,47 @@ class LabelJsonFields:
|
|
|
45
47
|
""""""
|
|
46
48
|
SMART_TOOL_INPUT = "smartToolInput"
|
|
47
49
|
""""""
|
|
50
|
+
NN_CREATED = "nnCreated"
|
|
51
|
+
"""Flag indicating if the label was created by NN model or manually."""
|
|
52
|
+
NN_UPDATED = "nnUpdated"
|
|
53
|
+
"""Flag indicating if the label was corrected by NN model or manually."""
|
|
54
|
+
|
|
55
|
+
class LabelingStatus(StrEnum):
|
|
56
|
+
"""
|
|
57
|
+
Shows status of the label. Can be one of the following:
|
|
58
|
+
|
|
59
|
+
- AUTO: Specifies if the label was created by NN model.
|
|
60
|
+
- nn_created: True | Created by NN model
|
|
61
|
+
- nn_updated: True | Corrected by NN model
|
|
62
|
+
- MANUAL: Specifies if the label was created manually.
|
|
63
|
+
- nn_created: False | Manually created
|
|
64
|
+
- nn_updated: False | Not corrected by NN model
|
|
65
|
+
- CORRECTED: Specifies if the label was initially created by NN model and then manually corrected.
|
|
66
|
+
- nn_created: True | Created by NN model
|
|
67
|
+
- nn_updated: False | Manually corrected
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
AUTO = "auto"
|
|
71
|
+
MANUAL = "manual"
|
|
72
|
+
CORRECTED = "corrected"
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def to_flags(cls, status: LabelingStatus) -> Tuple[bool, bool]:
|
|
76
|
+
if status == cls.AUTO:
|
|
77
|
+
return True, True
|
|
78
|
+
elif status == cls.CORRECTED:
|
|
79
|
+
return True, False
|
|
80
|
+
else:
|
|
81
|
+
return False, False
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def from_flags(cls, nn_created: bool, nn_updated: bool) -> LabelingStatus:
|
|
85
|
+
if nn_created and nn_updated:
|
|
86
|
+
return cls.AUTO
|
|
87
|
+
elif nn_created and not nn_updated:
|
|
88
|
+
return cls.CORRECTED
|
|
89
|
+
else:
|
|
90
|
+
return cls.MANUAL
|
|
48
91
|
|
|
49
92
|
|
|
50
93
|
class LabelBase:
|
|
@@ -65,6 +108,8 @@ class LabelBase:
|
|
|
65
108
|
:type smart_tool_input: dict, optional
|
|
66
109
|
:param sly_id: Label unique identifier.
|
|
67
110
|
:type sly_id: int, optional
|
|
111
|
+
:param status: Sets labeling status. Shows how label was created and corrected.
|
|
112
|
+
:type status: LabelingStatus, optional
|
|
68
113
|
|
|
69
114
|
:Usage example:
|
|
70
115
|
|
|
@@ -99,6 +144,7 @@ class LabelBase:
|
|
|
99
144
|
binding_key: Optional[str] = None,
|
|
100
145
|
smart_tool_input: Optional[Dict] = None,
|
|
101
146
|
sly_id: Optional[int] = None,
|
|
147
|
+
status: Optional[LabelingStatus] = None,
|
|
102
148
|
):
|
|
103
149
|
self._geometry = geometry
|
|
104
150
|
self._obj_class = obj_class
|
|
@@ -112,9 +158,14 @@ class LabelBase:
|
|
|
112
158
|
|
|
113
159
|
self._binding_key = binding_key
|
|
114
160
|
self._smart_tool_input = smart_tool_input
|
|
115
|
-
|
|
116
161
|
self._sly_id = sly_id
|
|
117
162
|
|
|
163
|
+
if status is None:
|
|
164
|
+
status = LabelingStatus.MANUAL
|
|
165
|
+
self._status = status
|
|
166
|
+
self._nn_created, self._nn_updated = LabelingStatus.to_flags(self.status)
|
|
167
|
+
|
|
168
|
+
|
|
118
169
|
def _validate_geometry(self):
|
|
119
170
|
"""
|
|
120
171
|
The function checks the name of the Object for compliance.
|
|
@@ -268,7 +319,9 @@ class LabelBase:
|
|
|
268
319
|
# "interior": []
|
|
269
320
|
# },
|
|
270
321
|
# "geometryType": "rectangle",
|
|
271
|
-
# "shape": "rectangle"
|
|
322
|
+
# "shape": "rectangle",
|
|
323
|
+
# "nnCreated": false,
|
|
324
|
+
# "nnUpdated": false
|
|
272
325
|
# }
|
|
273
326
|
"""
|
|
274
327
|
res = {
|
|
@@ -278,6 +331,8 @@ class LabelBase:
|
|
|
278
331
|
**self.geometry.to_json(),
|
|
279
332
|
GEOMETRY_TYPE: self.geometry.geometry_name(),
|
|
280
333
|
GEOMETRY_SHAPE: self.geometry.geometry_name(),
|
|
334
|
+
LabelJsonFields.NN_CREATED: self._nn_created,
|
|
335
|
+
LabelJsonFields.NN_UPDATED: self._nn_updated,
|
|
281
336
|
}
|
|
282
337
|
|
|
283
338
|
if self.obj_class.sly_id is not None:
|
|
@@ -328,7 +383,9 @@ class LabelBase:
|
|
|
328
383
|
"points": {
|
|
329
384
|
"exterior": [[100, 100], [900, 700]],
|
|
330
385
|
"interior": []
|
|
331
|
-
}
|
|
386
|
+
},
|
|
387
|
+
"nnCreated": false,
|
|
388
|
+
"nnUpdated": false
|
|
332
389
|
}
|
|
333
390
|
|
|
334
391
|
label_dog = sly.Label.from_json(data, meta)
|
|
@@ -352,6 +409,10 @@ class LabelBase:
|
|
|
352
409
|
binding_key = data.get(LabelJsonFields.INSTANCE_KEY)
|
|
353
410
|
smart_tool_input = data.get(LabelJsonFields.SMART_TOOL_INPUT)
|
|
354
411
|
|
|
412
|
+
nn_created = data.get(LabelJsonFields.NN_CREATED, False)
|
|
413
|
+
nn_updated = data.get(LabelJsonFields.NN_UPDATED, False)
|
|
414
|
+
status = LabelingStatus.from_flags(nn_created, nn_updated)
|
|
415
|
+
|
|
355
416
|
return cls(
|
|
356
417
|
geometry=geometry,
|
|
357
418
|
obj_class=obj_class,
|
|
@@ -360,6 +421,7 @@ class LabelBase:
|
|
|
360
421
|
binding_key=binding_key,
|
|
361
422
|
smart_tool_input=smart_tool_input,
|
|
362
423
|
sly_id=data.get(LabelJsonFields.ID),
|
|
424
|
+
status=status,
|
|
363
425
|
)
|
|
364
426
|
|
|
365
427
|
@property
|
|
@@ -441,6 +503,7 @@ class LabelBase:
|
|
|
441
503
|
description: Optional[str] = None,
|
|
442
504
|
binding_key: Optional[str] = None,
|
|
443
505
|
smart_tool_input: Optional[Dict] = None,
|
|
506
|
+
status: Optional[LabelingStatus] = None,
|
|
444
507
|
) -> LabelBase:
|
|
445
508
|
"""
|
|
446
509
|
Makes a copy of Label with new fields, if fields are given, otherwise it will use fields of the original Label.
|
|
@@ -457,6 +520,8 @@ class LabelBase:
|
|
|
457
520
|
:type binding_key: str, optional
|
|
458
521
|
:param smart_tool_input: Smart Tool parameters that were used for labeling.
|
|
459
522
|
:type smart_tool_input: dict, optional
|
|
523
|
+
:param status: Sets labeling status. Specifies if the label was created by NN model, manually or created by NN and then manually corrected.
|
|
524
|
+
:type status: LabelingStatus, optional
|
|
460
525
|
:return: New instance of Label
|
|
461
526
|
:rtype: :class:`Label<LabelBase>`
|
|
462
527
|
:Usage example:
|
|
@@ -501,6 +566,7 @@ class LabelBase:
|
|
|
501
566
|
description=take_with_default(description, self.description),
|
|
502
567
|
binding_key=take_with_default(binding_key, self.binding_key),
|
|
503
568
|
smart_tool_input=take_with_default(smart_tool_input, self._smart_tool_input),
|
|
569
|
+
status=take_with_default(status, self.status),
|
|
504
570
|
)
|
|
505
571
|
|
|
506
572
|
def crop(self, rect: Rectangle) -> List[LabelBase]:
|
|
@@ -864,6 +930,17 @@ class LabelBase:
|
|
|
864
930
|
def labeler_login(self):
|
|
865
931
|
return self.geometry.labeler_login
|
|
866
932
|
|
|
933
|
+
@property
|
|
934
|
+
def status(self) -> LabelingStatus:
|
|
935
|
+
"""Labeling status. Specifies if the Label was created by NN model, manually or created by NN and then manually corrected."""
|
|
936
|
+
return self._status
|
|
937
|
+
|
|
938
|
+
@status.setter
|
|
939
|
+
def status(self, status: LabelingStatus):
|
|
940
|
+
"""Set labeling status."""
|
|
941
|
+
self._status = status
|
|
942
|
+
self._nn_created, self._nn_updated = LabelingStatus.to_flags(self.status)
|
|
943
|
+
|
|
867
944
|
@classmethod
|
|
868
945
|
def _to_pixel_coordinate_system_json(cls, data: Dict, image_size: List[int]) -> Dict:
|
|
869
946
|
"""
|
|
@@ -865,7 +865,7 @@ class AnnotationApi(ModuleApi):
|
|
|
865
865
|
return
|
|
866
866
|
if len(img_ids) != len(anns):
|
|
867
867
|
raise RuntimeError(
|
|
868
|
-
'
|
|
868
|
+
f'Lists "img_ids" and "anns" have different lengths: {len(img_ids)} != {len(anns)}.'
|
|
869
869
|
)
|
|
870
870
|
|
|
871
871
|
# use context to avoid redundant API calls
|
|
@@ -1312,14 +1312,14 @@ class AnnotationApi(ModuleApi):
|
|
|
1312
1312
|
|
|
1313
1313
|
api.annotation.update_label(label_id, new_label)
|
|
1314
1314
|
"""
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
)
|
|
1315
|
+
payload = {
|
|
1316
|
+
ApiField.ID: label_id,
|
|
1317
|
+
ApiField.TAGS: [tag.to_json() for tag in label.tags],
|
|
1318
|
+
ApiField.GEOMETRY: label.geometry.to_json(),
|
|
1319
|
+
ApiField.NN_CREATED: label._nn_created,
|
|
1320
|
+
ApiField.NN_UPDATED: label._nn_updated,
|
|
1321
|
+
}
|
|
1322
|
+
self._api.post("figures.editInfo", payload)
|
|
1323
1323
|
|
|
1324
1324
|
def update_label_priority(self, label_id: int, priority: int) -> None:
|
|
1325
1325
|
"""Updates label's priority with given ID in Supervisely.
|
supervisely/api/api.py
CHANGED
|
@@ -10,6 +10,7 @@ import glob
|
|
|
10
10
|
import json
|
|
11
11
|
import os
|
|
12
12
|
import shutil
|
|
13
|
+
import threading
|
|
13
14
|
from logging import Logger
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import (
|
|
@@ -392,13 +393,15 @@ class Api:
|
|
|
392
393
|
else not self.server_address.startswith("https://")
|
|
393
394
|
)
|
|
394
395
|
|
|
395
|
-
if check_instance_version:
|
|
396
|
-
self._check_version(None if check_instance_version is True else check_instance_version)
|
|
397
|
-
|
|
398
396
|
self.async_httpx_client: httpx.AsyncClient = None
|
|
399
397
|
self.httpx_client: httpx.Client = None
|
|
400
398
|
self._semaphore = None
|
|
401
399
|
self._instance_version = None
|
|
400
|
+
self._version_check_completed = False
|
|
401
|
+
self._version_check_lock = threading.Lock()
|
|
402
|
+
|
|
403
|
+
if check_instance_version:
|
|
404
|
+
self._check_version(None if check_instance_version is True else check_instance_version)
|
|
402
405
|
|
|
403
406
|
@classmethod
|
|
404
407
|
def normalize_server_address(cls, server_address: str) -> str:
|
|
@@ -600,38 +603,49 @@ class Api:
|
|
|
600
603
|
:type version: Optional[str], e.g. "6.9.13"
|
|
601
604
|
"""
|
|
602
605
|
|
|
603
|
-
#
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
606
|
+
# Thread-safe one-time check with double-checked locking pattern
|
|
607
|
+
if self._version_check_completed:
|
|
608
|
+
return
|
|
609
|
+
|
|
610
|
+
with self._version_check_lock:
|
|
611
|
+
# Double-check inside the lock
|
|
612
|
+
if self._version_check_completed:
|
|
613
|
+
return
|
|
614
|
+
|
|
615
|
+
self._version_check_completed = True
|
|
616
|
+
|
|
617
|
+
# Since it's a informational message, we don't raise an exception if the check fails
|
|
618
|
+
# in any case, we don't want to interrupt the user's workflow.
|
|
619
|
+
try:
|
|
620
|
+
check_result = self.is_version_supported(version)
|
|
621
|
+
if check_result is None:
|
|
622
|
+
logger.debug(
|
|
623
|
+
"Failed to check if the instance version meets the minimum requirements "
|
|
624
|
+
"of current SDK version. "
|
|
625
|
+
"Ensure that the MINIMUM_INSTANCE_VERSION_FOR_SDK environment variable is set. "
|
|
626
|
+
"Usually you can ignore this message, but if you're adding new features, "
|
|
627
|
+
"which will require upgrade of the Supervisely instance, you should update "
|
|
628
|
+
"it supervisely.__init__.py file."
|
|
629
|
+
)
|
|
630
|
+
if check_result is False:
|
|
631
|
+
message = (
|
|
632
|
+
"The current version of the Supervisely instance is not supported by the SDK. "
|
|
633
|
+
"Some features may not work correctly."
|
|
634
|
+
)
|
|
635
|
+
if not is_community():
|
|
636
|
+
message += (
|
|
637
|
+
" Please upgrade the Supervisely instance to the latest version (recommended) "
|
|
638
|
+
"or downgrade the SDK to the version that supports the current instance (not recommended). "
|
|
639
|
+
"Refer to this docs for more information: "
|
|
640
|
+
"https://docs.supervisely.com/enterprise-edition/get-supervisely/upgrade "
|
|
641
|
+
"Check out changelog for the latest version of Supervisely: "
|
|
642
|
+
"https://app.supervisely.com/changelog"
|
|
643
|
+
)
|
|
644
|
+
logger.warning(message)
|
|
645
|
+
except Exception as e:
|
|
608
646
|
logger.debug(
|
|
609
|
-
"
|
|
610
|
-
"of current SDK version. "
|
|
611
|
-
"Ensure that the MINIMUM_INSTANCE_VERSION_FOR_SDK environment variable is set. "
|
|
612
|
-
"Usually you can ignore this message, but if you're adding new features, "
|
|
613
|
-
"which will require upgrade of the Supervisely instance, you should update "
|
|
614
|
-
"it supervisely.__init__.py file."
|
|
647
|
+
f"Tried to check version compatibility between SDK and instance, but failed: {e}"
|
|
615
648
|
)
|
|
616
|
-
if check_result is False:
|
|
617
|
-
message = (
|
|
618
|
-
"The current version of the Supervisely instance is not supported by the SDK. "
|
|
619
|
-
"Some features may not work correctly."
|
|
620
|
-
)
|
|
621
|
-
if not is_community():
|
|
622
|
-
message += (
|
|
623
|
-
" Please upgrade the Supervisely instance to the latest version (recommended) "
|
|
624
|
-
"or downgrade the SDK to the version that supports the current instance (not recommended). "
|
|
625
|
-
"Refer to this docs for more information: "
|
|
626
|
-
"https://docs.supervisely.com/enterprise-edition/get-supervisely/upgrade "
|
|
627
|
-
"Check out changelog for the latest version of Supervisely: "
|
|
628
|
-
"https://app.supervisely.com/changelog"
|
|
629
|
-
)
|
|
630
|
-
logger.warning(message)
|
|
631
|
-
except Exception as e:
|
|
632
|
-
logger.debug(
|
|
633
|
-
f"Tried to check version compatibility between SDK and instance, but failed: {e}"
|
|
634
|
-
)
|
|
635
649
|
|
|
636
650
|
def post(
|
|
637
651
|
self,
|
|
@@ -686,7 +700,8 @@ class Api:
|
|
|
686
700
|
)
|
|
687
701
|
|
|
688
702
|
if response.status_code != requests.codes.ok: # pylint: disable=no-member
|
|
689
|
-
self.
|
|
703
|
+
if not self._version_check_completed:
|
|
704
|
+
self._check_version()
|
|
690
705
|
Api._raise_for_status(response)
|
|
691
706
|
return response
|
|
692
707
|
except requests.RequestException as exc:
|
|
@@ -723,6 +738,7 @@ class Api:
|
|
|
723
738
|
retries: Optional[int] = None,
|
|
724
739
|
stream: Optional[bool] = False,
|
|
725
740
|
use_public_api: Optional[bool] = True,
|
|
741
|
+
data: Optional[Dict] = None,
|
|
726
742
|
) -> requests.Response:
|
|
727
743
|
"""
|
|
728
744
|
Performs GET request to server with given parameters.
|
|
@@ -730,13 +746,15 @@ class Api:
|
|
|
730
746
|
:param method:
|
|
731
747
|
:type method: str
|
|
732
748
|
:param params: Dictionary to send in the body of the :class:`Request`.
|
|
733
|
-
:type
|
|
749
|
+
:type params: dict
|
|
734
750
|
:param retries: The number of attempts to connect to the server.
|
|
735
|
-
:type
|
|
751
|
+
:type retries: int, optional
|
|
736
752
|
:param stream: Define, if you'd like to get the raw socket response from the server.
|
|
737
|
-
:type
|
|
753
|
+
:type stream: bool, optional
|
|
738
754
|
:param use_public_api:
|
|
739
|
-
:type
|
|
755
|
+
:type use_public_api: bool, optional
|
|
756
|
+
:param data: Dictionary to send in the body of the :class:`Request`.
|
|
757
|
+
:type data: dict, optional
|
|
740
758
|
:return: Response object
|
|
741
759
|
:rtype: :class:`Response<Response>`
|
|
742
760
|
"""
|
|
@@ -756,7 +774,9 @@ class Api:
|
|
|
756
774
|
json_body = params
|
|
757
775
|
if type(params) is dict:
|
|
758
776
|
json_body = {**params, **self.additional_fields}
|
|
759
|
-
response = requests.get(
|
|
777
|
+
response = requests.get(
|
|
778
|
+
url, params=json_body, data=data, headers=self.headers, stream=stream
|
|
779
|
+
)
|
|
760
780
|
|
|
761
781
|
if response.status_code != requests.codes.ok: # pylint: disable=no-member
|
|
762
782
|
Api._raise_for_status(response)
|
|
@@ -1098,7 +1118,8 @@ class Api:
|
|
|
1098
1118
|
timeout=timeout,
|
|
1099
1119
|
)
|
|
1100
1120
|
if response.status_code != httpx.codes.OK:
|
|
1101
|
-
self.
|
|
1121
|
+
if not self._version_check_completed:
|
|
1122
|
+
self._check_version()
|
|
1102
1123
|
Api._raise_for_status_httpx(response)
|
|
1103
1124
|
return response
|
|
1104
1125
|
except (httpx.RequestError, httpx.HTTPStatusError) as exc:
|
|
@@ -1314,7 +1335,8 @@ class Api:
|
|
|
1314
1335
|
httpx.codes.OK,
|
|
1315
1336
|
httpx.codes.PARTIAL_CONTENT,
|
|
1316
1337
|
]:
|
|
1317
|
-
self.
|
|
1338
|
+
if not self._version_check_completed:
|
|
1339
|
+
self._check_version()
|
|
1318
1340
|
Api._raise_for_status_httpx(resp)
|
|
1319
1341
|
|
|
1320
1342
|
hhash = resp.headers.get("x-content-checksum-sha256", None)
|
|
@@ -1428,7 +1450,8 @@ class Api:
|
|
|
1428
1450
|
timeout=timeout,
|
|
1429
1451
|
)
|
|
1430
1452
|
if response.status_code != httpx.codes.OK:
|
|
1431
|
-
self.
|
|
1453
|
+
if not self._version_check_completed:
|
|
1454
|
+
self._check_version()
|
|
1432
1455
|
Api._raise_for_status_httpx(response)
|
|
1433
1456
|
return response
|
|
1434
1457
|
except (httpx.RequestError, httpx.HTTPStatusError) as exc:
|
|
@@ -1569,7 +1592,8 @@ class Api:
|
|
|
1569
1592
|
httpx.codes.OK,
|
|
1570
1593
|
httpx.codes.PARTIAL_CONTENT,
|
|
1571
1594
|
]:
|
|
1572
|
-
self.
|
|
1595
|
+
if not self._version_check_completed:
|
|
1596
|
+
self._check_version()
|
|
1573
1597
|
Api._raise_for_status_httpx(resp)
|
|
1574
1598
|
|
|
1575
1599
|
# received hash of the content to check integrity of the data stream
|