supervisely 6.73.438__py3-none-any.whl → 6.73.513__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 +137 -1
- supervisely/_utils.py +81 -0
- supervisely/annotation/annotation.py +8 -2
- supervisely/annotation/json_geometries_map.py +14 -11
- supervisely/annotation/label.py +80 -3
- supervisely/api/annotation_api.py +14 -11
- supervisely/api/api.py +59 -38
- supervisely/api/app_api.py +11 -2
- supervisely/api/dataset_api.py +74 -12
- supervisely/api/entities_collection_api.py +10 -0
- supervisely/api/entity_annotation/figure_api.py +52 -4
- supervisely/api/entity_annotation/object_api.py +3 -3
- supervisely/api/entity_annotation/tag_api.py +63 -12
- supervisely/api/guides_api.py +210 -0
- supervisely/api/image_api.py +72 -1
- supervisely/api/labeling_job_api.py +83 -1
- supervisely/api/labeling_queue_api.py +33 -7
- supervisely/api/module_api.py +9 -0
- supervisely/api/project_api.py +71 -26
- supervisely/api/storage_api.py +3 -1
- supervisely/api/task_api.py +13 -2
- supervisely/api/team_api.py +4 -3
- supervisely/api/video/video_annotation_api.py +119 -3
- supervisely/api/video/video_api.py +65 -14
- supervisely/api/video/video_figure_api.py +24 -11
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +23 -7
- supervisely/app/development/development.py +18 -2
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +105 -0
- supervisely/app/fastapi/subapp.py +88 -42
- 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 +6 -0
- supervisely/app/widgets/activity_feed/__init__.py +0 -0
- supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
- supervisely/app/widgets/activity_feed/style.css +78 -0
- supervisely/app/widgets/activity_feed/template.html +22 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
- supervisely/app/widgets/classes_list_selector/template.html +60 -93
- supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
- supervisely/app/widgets/classes_table/classes_table.py +1 -0
- supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
- supervisely/app/widgets/dialog/dialog.py +12 -0
- supervisely/app/widgets/dialog/template.html +2 -1
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
- supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
- supervisely/app/widgets/fast_table/fast_table.py +184 -60
- supervisely/app/widgets/fast_table/template.html +1 -1
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +564 -0
- supervisely/app/widgets/heatmap/script.js +533 -0
- supervisely/app/widgets/heatmap/style.css +233 -0
- supervisely/app/widgets/heatmap/template.html +21 -0
- supervisely/app/widgets/modal/__init__.py +0 -0
- supervisely/app/widgets/modal/modal.py +198 -0
- supervisely/app/widgets/modal/template.html +10 -0
- supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
- 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 -3
- supervisely/app/widgets/select_class/__init__.py +0 -0
- supervisely/app/widgets/select_class/select_class.py +363 -0
- supervisely/app/widgets/select_class/template.html +50 -0
- supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
- supervisely/app/widgets/select_tag/__init__.py +0 -0
- supervisely/app/widgets/select_tag/select_tag.py +352 -0
- supervisely/app/widgets/select_tag/template.html +64 -0
- supervisely/app/widgets/select_team/select_team.py +37 -4
- supervisely/app/widgets/select_team/template.html +4 -5
- supervisely/app/widgets/select_user/__init__.py +0 -0
- supervisely/app/widgets/select_user/select_user.py +270 -0
- supervisely/app/widgets/select_user/template.html +13 -0
- supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
- supervisely/app/widgets/select_workspace/template.html +9 -12
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/aug/aug.py +6 -2
- supervisely/convert/base_converter.py +1 -0
- supervisely/convert/converter.py +2 -2
- supervisely/convert/image/csv/csv_converter.py +24 -15
- supervisely/convert/image/image_converter.py +3 -1
- supervisely/convert/image/image_helper.py +48 -4
- supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
- supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
- supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
- supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
- supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
- supervisely/convert/pointcloud/las/las_converter.py +13 -1
- supervisely/convert/pointcloud/las/las_helper.py +110 -11
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
- supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
- supervisely/convert/video/__init__.py +1 -0
- supervisely/convert/video/multi_view/__init__.py +0 -0
- supervisely/convert/video/multi_view/multi_view.py +543 -0
- supervisely/convert/video/sly/sly_video_converter.py +359 -3
- supervisely/convert/video/video_converter.py +24 -4
- supervisely/convert/volume/dicom/dicom_converter.py +13 -5
- supervisely/convert/volume/dicom/dicom_helper.py +30 -18
- supervisely/geometry/constants.py +1 -0
- supervisely/geometry/geometry.py +4 -0
- supervisely/geometry/helpers.py +5 -1
- supervisely/geometry/oriented_bbox.py +676 -0
- supervisely/geometry/polyline_3d.py +110 -0
- supervisely/geometry/rectangle.py +2 -1
- supervisely/io/env.py +76 -1
- supervisely/io/fs.py +21 -0
- supervisely/nn/benchmark/base_evaluator.py +104 -11
- supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
- supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
- supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
- supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
- supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
- supervisely/nn/inference/cache.py +43 -18
- supervisely/nn/inference/gui/serving_gui_template.py +5 -2
- supervisely/nn/inference/inference.py +916 -222
- supervisely/nn/inference/inference_request.py +55 -10
- supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
- supervisely/nn/inference/predict_app/gui/gui.py +676 -488
- supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
- supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
- supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
- supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
- supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
- supervisely/nn/inference/predict_app/gui/utils.py +236 -119
- supervisely/nn/inference/predict_app/predict_app.py +2 -2
- supervisely/nn/inference/session.py +43 -35
- supervisely/nn/inference/tracking/bbox_tracking.py +118 -35
- supervisely/nn/inference/tracking/point_tracking.py +5 -1
- supervisely/nn/inference/tracking/tracker_interface.py +10 -1
- supervisely/nn/inference/uploader.py +139 -12
- supervisely/nn/live_training/__init__.py +7 -0
- supervisely/nn/live_training/api_server.py +111 -0
- supervisely/nn/live_training/artifacts_utils.py +243 -0
- supervisely/nn/live_training/checkpoint_utils.py +229 -0
- supervisely/nn/live_training/dynamic_sampler.py +44 -0
- supervisely/nn/live_training/helpers.py +14 -0
- supervisely/nn/live_training/incremental_dataset.py +146 -0
- supervisely/nn/live_training/live_training.py +497 -0
- supervisely/nn/live_training/loss_plateau_detector.py +111 -0
- supervisely/nn/live_training/request_queue.py +52 -0
- supervisely/nn/model/model_api.py +9 -0
- supervisely/nn/model/prediction.py +2 -1
- supervisely/nn/model/prediction_session.py +26 -14
- supervisely/nn/prediction_dto.py +19 -1
- supervisely/nn/tracker/base_tracker.py +11 -1
- supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
- supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
- supervisely/nn/tracker/botsort_tracker.py +94 -65
- supervisely/nn/tracker/utils.py +4 -5
- supervisely/nn/tracker/visualize.py +93 -93
- supervisely/nn/training/gui/classes_selector.py +16 -1
- supervisely/nn/training/gui/train_val_splits_selector.py +52 -31
- supervisely/nn/training/train_app.py +46 -31
- supervisely/project/data_version.py +115 -51
- supervisely/project/download.py +1 -1
- supervisely/project/pointcloud_episode_project.py +37 -8
- supervisely/project/pointcloud_project.py +30 -2
- supervisely/project/project.py +14 -2
- supervisely/project/project_meta.py +27 -1
- supervisely/project/project_settings.py +32 -18
- supervisely/project/versioning/__init__.py +1 -0
- supervisely/project/versioning/common.py +20 -0
- supervisely/project/versioning/schema_fields.py +35 -0
- supervisely/project/versioning/video_schema.py +221 -0
- supervisely/project/versioning/volume_schema.py +87 -0
- supervisely/project/video_project.py +717 -15
- supervisely/project/volume_project.py +623 -5
- supervisely/template/experiment/experiment.html.jinja +4 -4
- supervisely/template/experiment/experiment_generator.py +14 -21
- supervisely/template/live_training/__init__.py +0 -0
- supervisely/template/live_training/header.html.jinja +96 -0
- supervisely/template/live_training/live_training.html.jinja +51 -0
- supervisely/template/live_training/live_training_generator.py +464 -0
- supervisely/template/live_training/sly-style.css +402 -0
- supervisely/template/live_training/template.html.jinja +18 -0
- supervisely/versions.json +28 -26
- supervisely/video/sampling.py +39 -20
- supervisely/video/video.py +41 -12
- supervisely/video_annotation/video_figure.py +38 -4
- supervisely/video_annotation/video_object.py +29 -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.438.dist-info → supervisely-6.73.513.dist-info}/METADATA +58 -40
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/RECORD +203 -155
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
- supervisely_lib/__init__.py +6 -1
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
- {supervisely-6.73.438.dist-info → supervisely-6.73.513.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,
|
|
@@ -90,6 +106,7 @@ from supervisely.geometry.graph import GraphNodes, Node
|
|
|
90
106
|
from supervisely.geometry.multichannel_bitmap import MultichannelBitmap
|
|
91
107
|
from supervisely.geometry.alpha_mask import AlphaMask
|
|
92
108
|
from supervisely.geometry.cuboid_2d import Cuboid2d
|
|
109
|
+
from supervisely.geometry.oriented_bbox import OrientedBBox
|
|
93
110
|
|
|
94
111
|
from supervisely.geometry.helpers import geometry_to_bitmap
|
|
95
112
|
from supervisely.geometry.helpers import deserialize_geometry
|
|
@@ -112,7 +129,14 @@ from supervisely.worker_api.chunking import (
|
|
|
112
129
|
ChunkedFileWriter,
|
|
113
130
|
ChunkedFileReader,
|
|
114
131
|
)
|
|
115
|
-
|
|
132
|
+
|
|
133
|
+
# Global import of api_proto works only if protobuf is installed and compatible
|
|
134
|
+
# Otherwise, we use a placeholder that raises an error when accessed
|
|
135
|
+
try:
|
|
136
|
+
import supervisely.worker_proto.worker_api_pb2 as api_proto
|
|
137
|
+
except Exception:
|
|
138
|
+
api_proto = _ApiProtoNotAvailable()
|
|
139
|
+
|
|
116
140
|
|
|
117
141
|
from supervisely.api.api import Api, UserSession, ApiContext
|
|
118
142
|
from supervisely.api import api
|
|
@@ -318,3 +342,115 @@ except Exception as e:
|
|
|
318
342
|
from supervisely.io.env import configure_minimum_instance_version
|
|
319
343
|
|
|
320
344
|
configure_minimum_instance_version()
|
|
345
|
+
|
|
346
|
+
LARGE_ENV_PLACEHOLDER = "@.@SLY_LARGE_ENV@.@"
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def restore_env_vars():
|
|
350
|
+
try:
|
|
351
|
+
large_env_keys = []
|
|
352
|
+
for key, value in os.environ.items():
|
|
353
|
+
if value == LARGE_ENV_PLACEHOLDER:
|
|
354
|
+
large_env_keys.append(key)
|
|
355
|
+
if len(large_env_keys) == 0:
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
if utils.is_development():
|
|
359
|
+
logger.info(
|
|
360
|
+
"Large environment variables detected. Skipping restoration in development mode.",
|
|
361
|
+
extra={"keys": large_env_keys},
|
|
362
|
+
)
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
unknown_keys = []
|
|
366
|
+
state_keys = []
|
|
367
|
+
context_keys = []
|
|
368
|
+
for key in large_env_keys:
|
|
369
|
+
if key == "CONTEXT" or key.startswith("context."):
|
|
370
|
+
context_keys.append(key)
|
|
371
|
+
elif key.startswith("MODAL_STATE") or key.startswith("modal.state."):
|
|
372
|
+
state_keys.append(key)
|
|
373
|
+
else:
|
|
374
|
+
unknown_keys.append(key)
|
|
375
|
+
|
|
376
|
+
if state_keys or context_keys:
|
|
377
|
+
api = Api()
|
|
378
|
+
if state_keys:
|
|
379
|
+
task_info = api.task.get_info_by_id(env.task_id())
|
|
380
|
+
state = task_info.get("meta", {}).get("params", {}).get("state", {})
|
|
381
|
+
modal_state_envs = json.flatten_json(state)
|
|
382
|
+
modal_state_envs = json.modify_keys(modal_state_envs, prefix="modal.state.")
|
|
383
|
+
|
|
384
|
+
restored_keys = []
|
|
385
|
+
not_found_keys = []
|
|
386
|
+
for key in state_keys:
|
|
387
|
+
if key == "MODAL_STATE":
|
|
388
|
+
os.environ[key] = json.json.dumps(state)
|
|
389
|
+
elif key in modal_state_envs:
|
|
390
|
+
os.environ[key] = str(modal_state_envs[key])
|
|
391
|
+
elif key.replace("_", ".") in [k.upper() for k in modal_state_envs]:
|
|
392
|
+
# some env vars do not support dots in their names
|
|
393
|
+
k = next(k for k in modal_state_envs if k.upper() == key.replace("_", "."))
|
|
394
|
+
os.environ[key] = str(modal_state_envs[k])
|
|
395
|
+
else:
|
|
396
|
+
not_found_keys.append(key)
|
|
397
|
+
continue
|
|
398
|
+
restored_keys.append(key)
|
|
399
|
+
|
|
400
|
+
if restored_keys:
|
|
401
|
+
logger.info(
|
|
402
|
+
"Restored large environment variables from task state",
|
|
403
|
+
extra={"keys": restored_keys},
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
if not_found_keys:
|
|
407
|
+
logger.warning(
|
|
408
|
+
"Failed to restore some large environment variables from task state. "
|
|
409
|
+
"No such keys in the state.",
|
|
410
|
+
extra={"keys": not_found_keys},
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
if context_keys:
|
|
414
|
+
context = api.task.get_context(env.task_id())
|
|
415
|
+
context_envs = json.flatten_json(context)
|
|
416
|
+
context_envs = json.modify_keys(context_envs, prefix="context.")
|
|
417
|
+
|
|
418
|
+
restored_keys = []
|
|
419
|
+
not_found_keys = []
|
|
420
|
+
for key in context_keys:
|
|
421
|
+
if key == "CONTEXT":
|
|
422
|
+
os.environ[key] = json.json.dumps(context)
|
|
423
|
+
elif key in context_envs:
|
|
424
|
+
os.environ[key] = context_envs[key]
|
|
425
|
+
else:
|
|
426
|
+
not_found_keys.append(key)
|
|
427
|
+
continue
|
|
428
|
+
restored_keys.append(key)
|
|
429
|
+
|
|
430
|
+
if restored_keys:
|
|
431
|
+
logger.info(
|
|
432
|
+
"Restored large environment variables from task context",
|
|
433
|
+
extra={"keys": restored_keys},
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
if not_found_keys:
|
|
437
|
+
logger.warning(
|
|
438
|
+
"Failed to restore some large environment variables from task context. "
|
|
439
|
+
"No such keys in the context.",
|
|
440
|
+
extra={"keys": not_found_keys},
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
if unknown_keys:
|
|
444
|
+
logger.warning(
|
|
445
|
+
"Found unknown large environment variables. Can't restore them.",
|
|
446
|
+
extra={"keys": unknown_keys},
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
except Exception as e:
|
|
450
|
+
logger.warning(
|
|
451
|
+
"Failed to restore large environment variables.",
|
|
452
|
+
exc_info=True,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
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>'
|
|
@@ -26,6 +26,7 @@ from supervisely.geometry.bitmap import Bitmap
|
|
|
26
26
|
from supervisely.geometry.geometry import Geometry
|
|
27
27
|
from supervisely.geometry.image_rotator import ImageRotator
|
|
28
28
|
from supervisely.geometry.multichannel_bitmap import MultichannelBitmap
|
|
29
|
+
from supervisely.geometry.oriented_bbox import OrientedBBox
|
|
29
30
|
from supervisely.geometry.polygon import Polygon
|
|
30
31
|
from supervisely.geometry.rectangle import Rectangle
|
|
31
32
|
from supervisely.imaging import font as sly_font
|
|
@@ -551,7 +552,7 @@ class Annotation:
|
|
|
551
552
|
:return: list of the Label class objects
|
|
552
553
|
"""
|
|
553
554
|
for label in labels:
|
|
554
|
-
if self.img_size.count(None) == 0:
|
|
555
|
+
if self.img_size.count(None) == 0 and not isinstance(label.geometry, OrientedBBox):
|
|
555
556
|
# image has resolution in DB
|
|
556
557
|
canvas_rect = Rectangle.from_size(self.img_size)
|
|
557
558
|
try:
|
|
@@ -566,6 +567,7 @@ class Annotation:
|
|
|
566
567
|
else:
|
|
567
568
|
# image was uploaded by link and does not have resolution in DB
|
|
568
569
|
# add label without normalization and validation
|
|
570
|
+
# OrientedBBox geometries can be outside of image bounds
|
|
569
571
|
dest.append(label)
|
|
570
572
|
|
|
571
573
|
def add_label(self, label: Label) -> Annotation:
|
|
@@ -1417,7 +1419,7 @@ class Annotation:
|
|
|
1417
1419
|
if draw_tags is True:
|
|
1418
1420
|
tags_font = self._get_font()
|
|
1419
1421
|
for label in self._labels:
|
|
1420
|
-
if not fill_rectangles and isinstance(label.geometry, Rectangle):
|
|
1422
|
+
if not fill_rectangles and isinstance(label.geometry, (Rectangle, OrientedBBox)):
|
|
1421
1423
|
label.draw_contour(
|
|
1422
1424
|
bitmap,
|
|
1423
1425
|
color=color,
|
|
@@ -2962,6 +2964,8 @@ class Annotation:
|
|
|
2962
2964
|
for label in data[AnnotationJsonFields.LABELS]:
|
|
2963
2965
|
if label[LabelJsonFields.GEOMETRY_TYPE] == Rectangle.geometry_name():
|
|
2964
2966
|
label = Rectangle._to_pixel_coordinate_system_json(label, image_size)
|
|
2967
|
+
elif label[LabelJsonFields.GEOMETRY_TYPE] == OrientedBBox.geometry_name():
|
|
2968
|
+
label = OrientedBBox._to_pixel_coordinate_system_json(label, image_size)
|
|
2965
2969
|
else:
|
|
2966
2970
|
label = Geometry._to_pixel_coordinate_system_json(label, image_size)
|
|
2967
2971
|
new_labels.append(label)
|
|
@@ -2988,6 +2992,8 @@ class Annotation:
|
|
|
2988
2992
|
for label in data[AnnotationJsonFields.LABELS]:
|
|
2989
2993
|
if label[LabelJsonFields.GEOMETRY_TYPE] == Rectangle.geometry_name():
|
|
2990
2994
|
label = Rectangle._to_subpixel_coordinate_system_json(label)
|
|
2995
|
+
elif label[LabelJsonFields.GEOMETRY_TYPE] == OrientedBBox.geometry_name():
|
|
2996
|
+
label = OrientedBBox._to_subpixel_coordinate_system_json(label)
|
|
2991
2997
|
else:
|
|
2992
2998
|
label = Geometry._to_subpixel_coordinate_system_json(label)
|
|
2993
2999
|
new_labels.append(label)
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
|
+
from supervisely.geometry.alpha_mask import AlphaMask
|
|
3
|
+
from supervisely.geometry.any_geometry import AnyGeometry
|
|
2
4
|
from supervisely.geometry.bitmap import Bitmap
|
|
3
|
-
from supervisely.geometry.
|
|
5
|
+
from supervisely.geometry.closed_surface_mesh import ClosedSurfaceMesh
|
|
4
6
|
from supervisely.geometry.cuboid import Cuboid
|
|
7
|
+
from supervisely.geometry.cuboid_2d import Cuboid2d
|
|
8
|
+
from supervisely.geometry.cuboid_3d import Cuboid3d
|
|
9
|
+
from supervisely.geometry.graph import GraphNodes
|
|
10
|
+
from supervisely.geometry.mask_3d import Mask3D
|
|
11
|
+
from supervisely.geometry.multichannel_bitmap import MultichannelBitmap
|
|
12
|
+
from supervisely.geometry.oriented_bbox import OrientedBBox
|
|
5
13
|
from supervisely.geometry.point import Point
|
|
14
|
+
from supervisely.geometry.point_3d import Point3d
|
|
15
|
+
from supervisely.geometry.pointcloud import Pointcloud
|
|
6
16
|
from supervisely.geometry.polygon import Polygon
|
|
7
17
|
from supervisely.geometry.polyline import Polyline
|
|
18
|
+
from supervisely.geometry.polyline_3d import Polyline3D
|
|
8
19
|
from supervisely.geometry.rectangle import Rectangle
|
|
9
|
-
from supervisely.geometry.graph import GraphNodes
|
|
10
|
-
from supervisely.geometry.any_geometry import AnyGeometry
|
|
11
|
-
from supervisely.geometry.cuboid_3d import Cuboid3d
|
|
12
|
-
from supervisely.geometry.pointcloud import Pointcloud
|
|
13
|
-
from supervisely.geometry.point_3d import Point3d
|
|
14
|
-
from supervisely.geometry.multichannel_bitmap import MultichannelBitmap
|
|
15
|
-
from supervisely.geometry.closed_surface_mesh import ClosedSurfaceMesh
|
|
16
|
-
from supervisely.geometry.alpha_mask import AlphaMask
|
|
17
|
-
from supervisely.geometry.cuboid_2d import Cuboid2d
|
|
18
|
-
|
|
19
20
|
|
|
20
21
|
_INPUT_GEOMETRIES = [
|
|
21
22
|
Bitmap,
|
|
@@ -34,6 +35,8 @@ _INPUT_GEOMETRIES = [
|
|
|
34
35
|
ClosedSurfaceMesh,
|
|
35
36
|
AlphaMask,
|
|
36
37
|
Cuboid2d,
|
|
38
|
+
Polyline3D,
|
|
39
|
+
OrientedBBox,
|
|
37
40
|
]
|
|
38
41
|
_JSON_SHAPE_TO_GEOMETRY_TYPE = {
|
|
39
42
|
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
|
"""
|
|
@@ -869,9 +869,12 @@ class AnnotationApi(ModuleApi):
|
|
|
869
869
|
)
|
|
870
870
|
|
|
871
871
|
# use context to avoid redundant API calls
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
872
|
+
image_info = self._api.image.get_info_by_id(img_ids[0], force_metadata_for_links=False)
|
|
873
|
+
if image_info is None:
|
|
874
|
+
raise RuntimeError(
|
|
875
|
+
f"Cannot get dataset ID from image info. Image with ID={img_ids[0]} not found."
|
|
876
|
+
)
|
|
877
|
+
dataset_id = image_info.dataset_id
|
|
875
878
|
context = self._api.optimization_context
|
|
876
879
|
context_dataset_id = context.get("dataset_id")
|
|
877
880
|
project_id = context.get("project_id")
|
|
@@ -1312,14 +1315,14 @@ class AnnotationApi(ModuleApi):
|
|
|
1312
1315
|
|
|
1313
1316
|
api.annotation.update_label(label_id, new_label)
|
|
1314
1317
|
"""
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
)
|
|
1318
|
+
payload = {
|
|
1319
|
+
ApiField.ID: label_id,
|
|
1320
|
+
ApiField.TAGS: [tag.to_json() for tag in label.tags],
|
|
1321
|
+
ApiField.GEOMETRY: label.geometry.to_json(),
|
|
1322
|
+
ApiField.NN_CREATED: label._nn_created,
|
|
1323
|
+
ApiField.NN_UPDATED: label._nn_updated,
|
|
1324
|
+
}
|
|
1325
|
+
self._api.post("figures.editInfo", payload)
|
|
1323
1326
|
|
|
1324
1327
|
def update_label_priority(self, label_id: int, priority: int) -> None:
|
|
1325
1328
|
"""Updates label's priority with given ID in Supervisely.
|