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.
Files changed (203) hide show
  1. supervisely/__init__.py +137 -1
  2. supervisely/_utils.py +81 -0
  3. supervisely/annotation/annotation.py +8 -2
  4. supervisely/annotation/json_geometries_map.py +14 -11
  5. supervisely/annotation/label.py +80 -3
  6. supervisely/api/annotation_api.py +14 -11
  7. supervisely/api/api.py +59 -38
  8. supervisely/api/app_api.py +11 -2
  9. supervisely/api/dataset_api.py +74 -12
  10. supervisely/api/entities_collection_api.py +10 -0
  11. supervisely/api/entity_annotation/figure_api.py +52 -4
  12. supervisely/api/entity_annotation/object_api.py +3 -3
  13. supervisely/api/entity_annotation/tag_api.py +63 -12
  14. supervisely/api/guides_api.py +210 -0
  15. supervisely/api/image_api.py +72 -1
  16. supervisely/api/labeling_job_api.py +83 -1
  17. supervisely/api/labeling_queue_api.py +33 -7
  18. supervisely/api/module_api.py +9 -0
  19. supervisely/api/project_api.py +71 -26
  20. supervisely/api/storage_api.py +3 -1
  21. supervisely/api/task_api.py +13 -2
  22. supervisely/api/team_api.py +4 -3
  23. supervisely/api/video/video_annotation_api.py +119 -3
  24. supervisely/api/video/video_api.py +65 -14
  25. supervisely/api/video/video_figure_api.py +24 -11
  26. supervisely/app/__init__.py +1 -1
  27. supervisely/app/content.py +23 -7
  28. supervisely/app/development/development.py +18 -2
  29. supervisely/app/fastapi/__init__.py +1 -0
  30. supervisely/app/fastapi/custom_static_files.py +1 -1
  31. supervisely/app/fastapi/multi_user.py +105 -0
  32. supervisely/app/fastapi/subapp.py +88 -42
  33. supervisely/app/fastapi/websocket.py +77 -9
  34. supervisely/app/singleton.py +21 -0
  35. supervisely/app/v1/app_service.py +18 -2
  36. supervisely/app/v1/constants.py +7 -1
  37. supervisely/app/widgets/__init__.py +6 -0
  38. supervisely/app/widgets/activity_feed/__init__.py +0 -0
  39. supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
  40. supervisely/app/widgets/activity_feed/style.css +78 -0
  41. supervisely/app/widgets/activity_feed/template.html +22 -0
  42. supervisely/app/widgets/card/card.py +20 -0
  43. supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
  44. supervisely/app/widgets/classes_list_selector/template.html +60 -93
  45. supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
  46. supervisely/app/widgets/classes_table/classes_table.py +1 -0
  47. supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
  48. supervisely/app/widgets/dialog/dialog.py +12 -0
  49. supervisely/app/widgets/dialog/template.html +2 -1
  50. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
  51. supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
  52. supervisely/app/widgets/fast_table/fast_table.py +184 -60
  53. supervisely/app/widgets/fast_table/template.html +1 -1
  54. supervisely/app/widgets/heatmap/__init__.py +0 -0
  55. supervisely/app/widgets/heatmap/heatmap.py +564 -0
  56. supervisely/app/widgets/heatmap/script.js +533 -0
  57. supervisely/app/widgets/heatmap/style.css +233 -0
  58. supervisely/app/widgets/heatmap/template.html +21 -0
  59. supervisely/app/widgets/modal/__init__.py +0 -0
  60. supervisely/app/widgets/modal/modal.py +198 -0
  61. supervisely/app/widgets/modal/template.html +10 -0
  62. supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
  63. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  64. supervisely/app/widgets/radio_tabs/template.html +1 -0
  65. supervisely/app/widgets/select/select.py +6 -3
  66. supervisely/app/widgets/select_class/__init__.py +0 -0
  67. supervisely/app/widgets/select_class/select_class.py +363 -0
  68. supervisely/app/widgets/select_class/template.html +50 -0
  69. supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
  70. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
  71. supervisely/app/widgets/select_tag/__init__.py +0 -0
  72. supervisely/app/widgets/select_tag/select_tag.py +352 -0
  73. supervisely/app/widgets/select_tag/template.html +64 -0
  74. supervisely/app/widgets/select_team/select_team.py +37 -4
  75. supervisely/app/widgets/select_team/template.html +4 -5
  76. supervisely/app/widgets/select_user/__init__.py +0 -0
  77. supervisely/app/widgets/select_user/select_user.py +270 -0
  78. supervisely/app/widgets/select_user/template.html +13 -0
  79. supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
  80. supervisely/app/widgets/select_workspace/template.html +9 -12
  81. supervisely/app/widgets/table/table.py +68 -13
  82. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  83. supervisely/aug/aug.py +6 -2
  84. supervisely/convert/base_converter.py +1 -0
  85. supervisely/convert/converter.py +2 -2
  86. supervisely/convert/image/csv/csv_converter.py +24 -15
  87. supervisely/convert/image/image_converter.py +3 -1
  88. supervisely/convert/image/image_helper.py +48 -4
  89. supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
  90. supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
  91. supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
  92. supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
  93. supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
  94. supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
  95. supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
  96. supervisely/convert/pointcloud/las/las_converter.py +13 -1
  97. supervisely/convert/pointcloud/las/las_helper.py +110 -11
  98. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
  99. supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
  100. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
  101. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
  102. supervisely/convert/video/__init__.py +1 -0
  103. supervisely/convert/video/multi_view/__init__.py +0 -0
  104. supervisely/convert/video/multi_view/multi_view.py +543 -0
  105. supervisely/convert/video/sly/sly_video_converter.py +359 -3
  106. supervisely/convert/video/video_converter.py +24 -4
  107. supervisely/convert/volume/dicom/dicom_converter.py +13 -5
  108. supervisely/convert/volume/dicom/dicom_helper.py +30 -18
  109. supervisely/geometry/constants.py +1 -0
  110. supervisely/geometry/geometry.py +4 -0
  111. supervisely/geometry/helpers.py +5 -1
  112. supervisely/geometry/oriented_bbox.py +676 -0
  113. supervisely/geometry/polyline_3d.py +110 -0
  114. supervisely/geometry/rectangle.py +2 -1
  115. supervisely/io/env.py +76 -1
  116. supervisely/io/fs.py +21 -0
  117. supervisely/nn/benchmark/base_evaluator.py +104 -11
  118. supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
  119. supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
  120. supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
  121. supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
  122. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
  123. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
  124. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
  125. supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
  126. supervisely/nn/inference/cache.py +43 -18
  127. supervisely/nn/inference/gui/serving_gui_template.py +5 -2
  128. supervisely/nn/inference/inference.py +916 -222
  129. supervisely/nn/inference/inference_request.py +55 -10
  130. supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
  131. supervisely/nn/inference/predict_app/gui/gui.py +676 -488
  132. supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
  133. supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
  134. supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
  135. supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
  136. supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
  137. supervisely/nn/inference/predict_app/gui/utils.py +236 -119
  138. supervisely/nn/inference/predict_app/predict_app.py +2 -2
  139. supervisely/nn/inference/session.py +43 -35
  140. supervisely/nn/inference/tracking/bbox_tracking.py +118 -35
  141. supervisely/nn/inference/tracking/point_tracking.py +5 -1
  142. supervisely/nn/inference/tracking/tracker_interface.py +10 -1
  143. supervisely/nn/inference/uploader.py +139 -12
  144. supervisely/nn/live_training/__init__.py +7 -0
  145. supervisely/nn/live_training/api_server.py +111 -0
  146. supervisely/nn/live_training/artifacts_utils.py +243 -0
  147. supervisely/nn/live_training/checkpoint_utils.py +229 -0
  148. supervisely/nn/live_training/dynamic_sampler.py +44 -0
  149. supervisely/nn/live_training/helpers.py +14 -0
  150. supervisely/nn/live_training/incremental_dataset.py +146 -0
  151. supervisely/nn/live_training/live_training.py +497 -0
  152. supervisely/nn/live_training/loss_plateau_detector.py +111 -0
  153. supervisely/nn/live_training/request_queue.py +52 -0
  154. supervisely/nn/model/model_api.py +9 -0
  155. supervisely/nn/model/prediction.py +2 -1
  156. supervisely/nn/model/prediction_session.py +26 -14
  157. supervisely/nn/prediction_dto.py +19 -1
  158. supervisely/nn/tracker/base_tracker.py +11 -1
  159. supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
  160. supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
  161. supervisely/nn/tracker/botsort_tracker.py +94 -65
  162. supervisely/nn/tracker/utils.py +4 -5
  163. supervisely/nn/tracker/visualize.py +93 -93
  164. supervisely/nn/training/gui/classes_selector.py +16 -1
  165. supervisely/nn/training/gui/train_val_splits_selector.py +52 -31
  166. supervisely/nn/training/train_app.py +46 -31
  167. supervisely/project/data_version.py +115 -51
  168. supervisely/project/download.py +1 -1
  169. supervisely/project/pointcloud_episode_project.py +37 -8
  170. supervisely/project/pointcloud_project.py +30 -2
  171. supervisely/project/project.py +14 -2
  172. supervisely/project/project_meta.py +27 -1
  173. supervisely/project/project_settings.py +32 -18
  174. supervisely/project/versioning/__init__.py +1 -0
  175. supervisely/project/versioning/common.py +20 -0
  176. supervisely/project/versioning/schema_fields.py +35 -0
  177. supervisely/project/versioning/video_schema.py +221 -0
  178. supervisely/project/versioning/volume_schema.py +87 -0
  179. supervisely/project/video_project.py +717 -15
  180. supervisely/project/volume_project.py +623 -5
  181. supervisely/template/experiment/experiment.html.jinja +4 -4
  182. supervisely/template/experiment/experiment_generator.py +14 -21
  183. supervisely/template/live_training/__init__.py +0 -0
  184. supervisely/template/live_training/header.html.jinja +96 -0
  185. supervisely/template/live_training/live_training.html.jinja +51 -0
  186. supervisely/template/live_training/live_training_generator.py +464 -0
  187. supervisely/template/live_training/sly-style.css +402 -0
  188. supervisely/template/live_training/template.html.jinja +18 -0
  189. supervisely/versions.json +28 -26
  190. supervisely/video/sampling.py +39 -20
  191. supervisely/video/video.py +41 -12
  192. supervisely/video_annotation/video_figure.py +38 -4
  193. supervisely/video_annotation/video_object.py +29 -4
  194. supervisely/volume/stl_converter.py +2 -0
  195. supervisely/worker_api/agent_rpc.py +24 -1
  196. supervisely/worker_api/rpc_servicer.py +31 -7
  197. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/METADATA +58 -40
  198. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/RECORD +203 -155
  199. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
  200. supervisely_lib/__init__.py +6 -1
  201. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
  202. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
  203. {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
- import supervisely.worker_proto.worker_api_pb2 as api_proto
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.mask_3d import Mask3D
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
@@ -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
- dataset_id = self._api.image.get_info_by_id(
873
- img_ids[0], force_metadata_for_links=False
874
- ).dataset_id
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
- self._api.post(
1316
- "figures.editInfo",
1317
- {
1318
- ApiField.ID: label_id,
1319
- ApiField.TAGS: [tag.to_json() for tag in label.tags],
1320
- ApiField.GEOMETRY: label.geometry.to_json(),
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.