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.

Files changed (190) hide show
  1. supervisely/__init__.py +136 -1
  2. supervisely/_utils.py +81 -0
  3. supervisely/annotation/json_geometries_map.py +2 -0
  4. supervisely/annotation/label.py +80 -3
  5. supervisely/api/annotation_api.py +9 -9
  6. supervisely/api/api.py +67 -43
  7. supervisely/api/app_api.py +72 -5
  8. supervisely/api/dataset_api.py +108 -33
  9. supervisely/api/entity_annotation/figure_api.py +113 -49
  10. supervisely/api/image_api.py +82 -0
  11. supervisely/api/module_api.py +10 -0
  12. supervisely/api/nn/deploy_api.py +15 -9
  13. supervisely/api/nn/ecosystem_models_api.py +201 -0
  14. supervisely/api/nn/neural_network_api.py +12 -3
  15. supervisely/api/pointcloud/pointcloud_api.py +38 -0
  16. supervisely/api/pointcloud/pointcloud_episode_annotation_api.py +3 -0
  17. supervisely/api/project_api.py +213 -6
  18. supervisely/api/task_api.py +11 -1
  19. supervisely/api/video/video_annotation_api.py +4 -2
  20. supervisely/api/video/video_api.py +79 -1
  21. supervisely/api/video/video_figure_api.py +24 -11
  22. supervisely/api/volume/volume_api.py +38 -0
  23. supervisely/app/__init__.py +1 -1
  24. supervisely/app/content.py +14 -6
  25. supervisely/app/fastapi/__init__.py +1 -0
  26. supervisely/app/fastapi/custom_static_files.py +1 -1
  27. supervisely/app/fastapi/multi_user.py +88 -0
  28. supervisely/app/fastapi/subapp.py +175 -42
  29. supervisely/app/fastapi/templating.py +1 -1
  30. supervisely/app/fastapi/websocket.py +77 -9
  31. supervisely/app/singleton.py +21 -0
  32. supervisely/app/v1/app_service.py +18 -2
  33. supervisely/app/v1/constants.py +7 -1
  34. supervisely/app/widgets/__init__.py +11 -1
  35. supervisely/app/widgets/agent_selector/template.html +1 -0
  36. supervisely/app/widgets/card/card.py +20 -0
  37. supervisely/app/widgets/dataset_thumbnail/dataset_thumbnail.py +11 -2
  38. supervisely/app/widgets/dataset_thumbnail/template.html +3 -1
  39. supervisely/app/widgets/deploy_model/deploy_model.py +750 -0
  40. supervisely/app/widgets/dialog/dialog.py +12 -0
  41. supervisely/app/widgets/dialog/template.html +2 -1
  42. supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
  43. supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
  44. supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
  45. supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
  46. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +195 -0
  47. supervisely/app/widgets/experiment_selector/experiment_selector.py +454 -263
  48. supervisely/app/widgets/fast_table/fast_table.py +713 -126
  49. supervisely/app/widgets/fast_table/script.js +492 -95
  50. supervisely/app/widgets/fast_table/style.css +54 -0
  51. supervisely/app/widgets/fast_table/template.html +45 -5
  52. supervisely/app/widgets/heatmap/__init__.py +0 -0
  53. supervisely/app/widgets/heatmap/heatmap.py +523 -0
  54. supervisely/app/widgets/heatmap/script.js +378 -0
  55. supervisely/app/widgets/heatmap/style.css +227 -0
  56. supervisely/app/widgets/heatmap/template.html +21 -0
  57. supervisely/app/widgets/input_tag/input_tag.py +102 -15
  58. supervisely/app/widgets/input_tag_list/__init__.py +0 -0
  59. supervisely/app/widgets/input_tag_list/input_tag_list.py +274 -0
  60. supervisely/app/widgets/input_tag_list/template.html +70 -0
  61. supervisely/app/widgets/radio_table/radio_table.py +10 -2
  62. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  63. supervisely/app/widgets/radio_tabs/template.html +1 -0
  64. supervisely/app/widgets/select/select.py +6 -4
  65. supervisely/app/widgets/select_dataset/select_dataset.py +6 -0
  66. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +83 -7
  67. supervisely/app/widgets/table/table.py +68 -13
  68. supervisely/app/widgets/tabs/tabs.py +22 -6
  69. supervisely/app/widgets/tabs/template.html +5 -1
  70. supervisely/app/widgets/transfer/style.css +3 -0
  71. supervisely/app/widgets/transfer/template.html +3 -1
  72. supervisely/app/widgets/transfer/transfer.py +48 -45
  73. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  74. supervisely/convert/image/csv/csv_converter.py +24 -15
  75. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +43 -41
  76. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +75 -51
  77. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +137 -124
  78. supervisely/convert/video/video_converter.py +2 -2
  79. supervisely/geometry/polyline_3d.py +110 -0
  80. supervisely/io/env.py +161 -1
  81. supervisely/nn/artifacts/__init__.py +1 -1
  82. supervisely/nn/artifacts/artifacts.py +10 -2
  83. supervisely/nn/artifacts/detectron2.py +1 -0
  84. supervisely/nn/artifacts/hrda.py +1 -0
  85. supervisely/nn/artifacts/mmclassification.py +20 -0
  86. supervisely/nn/artifacts/mmdetection.py +5 -3
  87. supervisely/nn/artifacts/mmsegmentation.py +1 -0
  88. supervisely/nn/artifacts/ritm.py +1 -0
  89. supervisely/nn/artifacts/rtdetr.py +1 -0
  90. supervisely/nn/artifacts/unet.py +1 -0
  91. supervisely/nn/artifacts/utils.py +3 -0
  92. supervisely/nn/artifacts/yolov5.py +2 -0
  93. supervisely/nn/artifacts/yolov8.py +1 -0
  94. supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
  95. supervisely/nn/experiments.py +9 -0
  96. supervisely/nn/inference/cache.py +37 -17
  97. supervisely/nn/inference/gui/serving_gui_template.py +39 -13
  98. supervisely/nn/inference/inference.py +953 -211
  99. supervisely/nn/inference/inference_request.py +15 -8
  100. supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
  101. supervisely/nn/inference/object_detection/object_detection.py +1 -0
  102. supervisely/nn/inference/predict_app/__init__.py +0 -0
  103. supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
  104. supervisely/nn/inference/predict_app/gui/classes_selector.py +160 -0
  105. supervisely/nn/inference/predict_app/gui/gui.py +915 -0
  106. supervisely/nn/inference/predict_app/gui/input_selector.py +344 -0
  107. supervisely/nn/inference/predict_app/gui/model_selector.py +77 -0
  108. supervisely/nn/inference/predict_app/gui/output_selector.py +179 -0
  109. supervisely/nn/inference/predict_app/gui/preview.py +93 -0
  110. supervisely/nn/inference/predict_app/gui/settings_selector.py +881 -0
  111. supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
  112. supervisely/nn/inference/predict_app/gui/utils.py +399 -0
  113. supervisely/nn/inference/predict_app/predict_app.py +176 -0
  114. supervisely/nn/inference/session.py +47 -39
  115. supervisely/nn/inference/tracking/bbox_tracking.py +5 -1
  116. supervisely/nn/inference/tracking/point_tracking.py +5 -1
  117. supervisely/nn/inference/tracking/tracker_interface.py +4 -0
  118. supervisely/nn/inference/uploader.py +9 -5
  119. supervisely/nn/model/model_api.py +44 -22
  120. supervisely/nn/model/prediction.py +15 -1
  121. supervisely/nn/model/prediction_session.py +70 -14
  122. supervisely/nn/prediction_dto.py +7 -0
  123. supervisely/nn/tracker/__init__.py +6 -8
  124. supervisely/nn/tracker/base_tracker.py +54 -0
  125. supervisely/nn/tracker/botsort/__init__.py +1 -0
  126. supervisely/nn/tracker/botsort/botsort_config.yaml +30 -0
  127. supervisely/nn/tracker/botsort/osnet_reid/__init__.py +0 -0
  128. supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
  129. supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
  130. supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
  131. supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
  132. supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
  133. supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
  134. supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
  135. supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
  136. supervisely/nn/tracker/botsort_tracker.py +273 -0
  137. supervisely/nn/tracker/calculate_metrics.py +264 -0
  138. supervisely/nn/tracker/utils.py +273 -0
  139. supervisely/nn/tracker/visualize.py +520 -0
  140. supervisely/nn/training/gui/gui.py +152 -49
  141. supervisely/nn/training/gui/hyperparameters_selector.py +1 -1
  142. supervisely/nn/training/gui/model_selector.py +8 -6
  143. supervisely/nn/training/gui/train_val_splits_selector.py +144 -71
  144. supervisely/nn/training/gui/training_artifacts.py +3 -1
  145. supervisely/nn/training/train_app.py +225 -46
  146. supervisely/project/pointcloud_episode_project.py +12 -8
  147. supervisely/project/pointcloud_project.py +12 -8
  148. supervisely/project/project.py +221 -75
  149. supervisely/template/experiment/experiment.html.jinja +105 -55
  150. supervisely/template/experiment/experiment_generator.py +258 -112
  151. supervisely/template/experiment/header.html.jinja +31 -13
  152. supervisely/template/experiment/sly-style.css +7 -2
  153. supervisely/versions.json +3 -1
  154. supervisely/video/sampling.py +42 -20
  155. supervisely/video/video.py +41 -12
  156. supervisely/video_annotation/video_figure.py +38 -4
  157. supervisely/volume/stl_converter.py +2 -0
  158. supervisely/worker_api/agent_rpc.py +24 -1
  159. supervisely/worker_api/rpc_servicer.py +31 -7
  160. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/METADATA +22 -14
  161. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/RECORD +167 -148
  162. supervisely_lib/__init__.py +6 -1
  163. supervisely/app/widgets/experiment_selector/style.css +0 -27
  164. supervisely/app/widgets/experiment_selector/template.html +0 -61
  165. supervisely/nn/tracker/bot_sort/__init__.py +0 -21
  166. supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
  167. supervisely/nn/tracker/bot_sort/matching.py +0 -127
  168. supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
  169. supervisely/nn/tracker/deep_sort/__init__.py +0 -6
  170. supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
  171. supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
  172. supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
  173. supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
  174. supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
  175. supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
  176. supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
  177. supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
  178. supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
  179. supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
  180. supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
  181. supervisely/nn/tracker/tracker.py +0 -285
  182. supervisely/nn/tracker/utils/kalman_filter.py +0 -492
  183. supervisely/nn/tracking/__init__.py +0 -1
  184. supervisely/nn/tracking/boxmot.py +0 -114
  185. supervisely/nn/tracking/tracking.py +0 -24
  186. /supervisely/{nn/tracker/utils → app/widgets/deploy_model}/__init__.py +0 -0
  187. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/LICENSE +0 -0
  188. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/WHEEL +0 -0
  189. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/entry_points.txt +0 -0
  190. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/top_level.txt +0 -0
@@ -271,7 +271,7 @@ class SessionJSON:
271
271
  start_frame_index: int = None,
272
272
  frames_count: int = None,
273
273
  frames_direction: Literal["forward", "backward"] = None,
274
- tracker: Literal["bot", "deepsort"] = None,
274
+ tracker: Literal["botsort"] = None,
275
275
  batch_size: int = None,
276
276
  ) -> Dict[str, Any]:
277
277
  endpoint = "inference_video_id"
@@ -295,7 +295,7 @@ class SessionJSON:
295
295
  frames_direction: Literal["forward", "backward"] = None,
296
296
  process_fn=None,
297
297
  preparing_cb=None,
298
- tracker: Literal["bot", "deepsort"] = None,
298
+ tracker: Literal["botsort"] = None,
299
299
  batch_size: int = None,
300
300
  ) -> Iterator:
301
301
  if self._async_inference_uuid:
@@ -441,46 +441,52 @@ class SessionJSON:
441
441
  prev_current = 0
442
442
  if preparing_cb:
443
443
  # wait for inference status
444
- resp = self._get_preparing_progress()
445
- awaiting_preparing_progress = 0
446
- break_flag = False
447
- while resp.get("status") is None:
448
- time.sleep(1)
449
- awaiting_preparing_progress += 1
450
- if awaiting_preparing_progress > 30:
451
- break_flag = True
444
+ try:
452
445
  resp = self._get_preparing_progress()
453
- if break_flag:
454
- logger.warning(
455
- "Unable to get preparing progress. Continue without prepaing progress status."
456
- )
457
- if not break_flag:
458
- if resp["status"] == "download_info":
446
+ for i in range(30):
447
+ logger.info(
448
+ f"Waiting for preparing progress... {30 - i} seconds left until timeout"
449
+ )
450
+ resp = self._get_preparing_progress()
451
+ if resp.get("status") is not None:
452
+ break
453
+ time.sleep(1)
454
+ if not resp.get("status"):
455
+ raise RuntimeError("Preparing progress status is not available.")
456
+
457
+ if resp.get("status") == "download_info":
458
+ logger.info("Downloading infos...")
459
459
  progress_widget = preparing_cb(
460
460
  message="Downloading infos", total=resp["total"], unit="it"
461
461
  )
462
- while resp["status"] == "download_info":
463
- current = resp["current"]
464
- # pylint: disable=possibly-used-before-assignment
465
- progress_widget.update(current - prev_current)
466
- prev_current = current
467
- resp = self._get_preparing_progress()
468
-
469
- if resp["status"] == "download_project":
462
+ while resp["status"] == "download_info":
463
+ current = resp["current"]
464
+ # pylint: disable=possibly-used-before-assignment
465
+ progress_widget.update(current - prev_current)
466
+ prev_current = current
467
+ resp = self._get_preparing_progress()
468
+
469
+ if resp.get("status") == "download_project":
470
+ logger.info("Downloading project...")
470
471
  progress_widget = preparing_cb(message="Download project", total=resp["total"])
471
- while resp["status"] == "download_project":
472
- current = resp["current"]
473
- progress_widget.update(current - prev_current)
474
- prev_current = current
475
- resp = self._get_preparing_progress()
476
-
477
- if resp["status"] == "warmup":
472
+ while resp.get("status") == "download_project":
473
+ current = resp["current"]
474
+ progress_widget.update(current - prev_current)
475
+ prev_current = current
476
+ resp = self._get_preparing_progress()
477
+
478
+ if resp.get("status") == "warmup":
479
+ logger.info("Running warmup...")
478
480
  progress_widget = preparing_cb(message="Running warmup", total=resp["total"])
479
- while resp["status"] == "warmup":
480
- current = resp["current"]
481
- progress_widget.update(current - prev_current)
482
- prev_current = current
483
- resp = self._get_preparing_progress()
481
+ while resp.get("status") == "warmup":
482
+ current = resp["current"]
483
+ progress_widget.update(current - prev_current)
484
+ prev_current = current
485
+ resp = self._get_preparing_progress()
486
+ except Exception as ex:
487
+ logger.warning(
488
+ f"An error occurred while getting preparing progress: {ex}. Continue without preparing progress status."
489
+ )
484
490
 
485
491
  logger.info("Inference has started:", extra={"response": resp})
486
492
  resp, has_started = self._wait_for_async_inference_start()
@@ -537,7 +543,9 @@ class SessionJSON:
537
543
  t0 = time.time()
538
544
  while not has_started and not timeout_exceeded:
539
545
  resp = self._get_inference_progress()
540
- has_started = bool(resp["result"]) or resp["progress"]["total"] != 1
546
+ pending_results = resp.get("pending_results", None)
547
+ has_results = bool(pending_results)
548
+ has_started = bool(resp.get("result")) or resp["progress"]["total"] != 1 or has_results
541
549
  if not has_started:
542
550
  time.sleep(delay)
543
551
  timeout_exceeded = timeout and time.time() - t0 > timeout
@@ -795,7 +803,7 @@ class Session(SessionJSON):
795
803
  start_frame_index: int = None,
796
804
  frames_count: int = None,
797
805
  frames_direction: Literal["forward", "backward"] = None,
798
- tracker: Literal["bot", "deepsort"] = None,
806
+ tracker: Literal["botsort"] = None,
799
807
  batch_size: int = None,
800
808
  ) -> List[sly.Annotation]:
801
809
  pred_list_raw = super().inference_video_id(
@@ -811,7 +819,7 @@ class Session(SessionJSON):
811
819
  start_frame_index: int = None,
812
820
  frames_count: int = None,
813
821
  frames_direction: Literal["forward", "backward"] = None,
814
- tracker: Literal["bot", "deepsort"] = None,
822
+ tracker: Literal["botsort"] = None,
815
823
  batch_size: int = None,
816
824
  preparing_cb=None,
817
825
  ) -> AsyncInferenceIterator:
@@ -7,7 +7,7 @@ import numpy as np
7
7
  from pydantic import ValidationError
8
8
 
9
9
  from supervisely.annotation.annotation import Annotation
10
- from supervisely.annotation.label import Geometry, Label
10
+ from supervisely.annotation.label import Geometry, Label, LabelingStatus
11
11
  from supervisely.annotation.obj_class import ObjClass
12
12
  from supervisely.api.api import Api
13
13
  from supervisely.api.module_api import ApiField
@@ -588,8 +588,12 @@ class BBoxTracking(BaseTracking):
588
588
  # for example empty mask
589
589
  continue
590
590
  if isinstance(label, list):
591
+ for lb in label:
592
+ lb.status = LabelingStatus.AUTO
591
593
  labels.extend(label)
592
594
  continue
595
+
596
+ label.status = LabelingStatus.AUTO
593
597
  labels.append(label)
594
598
 
595
599
  # create annotation with correct image resolution
@@ -10,7 +10,7 @@ from pydantic import ValidationError
10
10
 
11
11
  import supervisely.nn.inference.tracking.functional as F
12
12
  from supervisely.annotation.annotation import Annotation
13
- from supervisely.annotation.label import Geometry, Label
13
+ from supervisely.annotation.label import Geometry, Label, LabelingStatus
14
14
  from supervisely.annotation.obj_class import ObjClass
15
15
  from supervisely.api.api import Api
16
16
  from supervisely.api.module_api import ApiField
@@ -610,8 +610,12 @@ class PointTracking(BaseTracking):
610
610
  # for example empty mask
611
611
  continue
612
612
  if isinstance(label, list):
613
+ for lb in label:
614
+ lb.status = LabelingStatus.AUTO
613
615
  labels.extend(label)
614
616
  continue
617
+
618
+ label.status = LabelingStatus.AUTO
615
619
  labels.append(label)
616
620
 
617
621
  # create annotation with correct image resolution
@@ -22,6 +22,7 @@ from supervisely.geometry.rectangle import Rectangle
22
22
  from supervisely.nn.inference.cache import InferenceImageCache
23
23
  from supervisely.sly_logger import logger
24
24
  from supervisely.video_annotation.key_id_map import KeyIdMap
25
+ from supervisely.annotation.label import LabelingStatus
25
26
 
26
27
 
27
28
  class TrackerInterface:
@@ -166,6 +167,8 @@ class TrackerInterface:
166
167
  ApiField.GEOMETRY: geometry.to_json(),
167
168
  ApiField.META: {ApiField.FRAME: frame_index},
168
169
  ApiField.TRACK_ID: self.track_id,
170
+ ApiField.NN_CREATED: True,
171
+ ApiField.NN_UPDATED: True,
169
172
  }
170
173
  for geometry, frame_index in geometries_frame_indexes
171
174
  ]
@@ -195,6 +198,7 @@ class TrackerInterface:
195
198
  geometry.to_json(),
196
199
  geometry.geometry_name(),
197
200
  self.track_id,
201
+ status=LabelingStatus.AUTO,
198
202
  )
199
203
  self.logger.debug(f"Added {geometry.geometry_name()} to frame #{frame_ind}")
200
204
  if notify:
@@ -105,10 +105,6 @@ class Uploader:
105
105
  self.stop()
106
106
  return
107
107
  except Exception as e:
108
- try:
109
- raise RuntimeError("Error in upload loop") from e
110
- except RuntimeError as e_:
111
- e = e_
112
108
  if self._logger is not None:
113
109
  self._logger.error("Error in upload loop: %s", str(e), exc_info=True)
114
110
  if not self._exception_event.is_set():
@@ -152,7 +148,9 @@ class Uploader:
152
148
  def __exit__(self, exc_type, exc_val, exc_tb):
153
149
  self.stop()
154
150
  try:
155
- self.join(timeout=5)
151
+ self.join(timeout=30)
152
+ if self._upload_thread.is_alive():
153
+ raise TimeoutError("Uploader thread didn't finish in time")
156
154
  except TimeoutError:
157
155
  _logger = logger
158
156
  if self._logger is not None:
@@ -161,4 +159,10 @@ class Uploader:
161
159
  if exc_type is not None:
162
160
  exc = exc_val.with_traceback(exc_tb)
163
161
  return self._exception_handler(exc)
162
+ if self.has_exception():
163
+ exc = self.exception
164
+ try:
165
+ raise RuntimeError(f"Error in uploader loop: {str(exc)}") from exc
166
+ except Exception as exc:
167
+ return self._exception_handler(exc)
164
168
  return False
@@ -2,6 +2,7 @@
2
2
  """load and inference models"""
3
3
 
4
4
  from __future__ import annotations
5
+
5
6
  import os
6
7
  from os import PathLike
7
8
  from typing import List, Union
@@ -11,6 +12,7 @@ import requests
11
12
 
12
13
  import supervisely.io.env as sly_env
13
14
  import supervisely.io.json as sly_json
15
+ from supervisely.api.api import Api
14
16
  from supervisely.api.module_api import ApiField
15
17
  from supervisely.api.task_api import TaskApi
16
18
  from supervisely.nn.experiments import ExperimentInfo
@@ -18,7 +20,6 @@ from supervisely.nn.model.prediction import Prediction
18
20
  from supervisely.nn.model.prediction_session import PredictionSession
19
21
  from supervisely.nn.utils import ModelSource
20
22
  from supervisely.project.project_meta import ProjectMeta
21
- from supervisely.api.api import Api
22
23
 
23
24
 
24
25
  class ModelAPI:
@@ -71,6 +72,15 @@ class ModelAPI:
71
72
  else:
72
73
  return self._post("get_custom_inference_settings", {})["settings"]
73
74
 
75
+ def get_tracking_settings(self):
76
+ # @TODO: botsort hardcoded
77
+ # Add dropdown selector for tracking algorithms later
78
+ if self.task_id is not None:
79
+ return self.api.task.send_request(self.task_id, "get_tracking_settings", {})["botsort"]
80
+ else:
81
+ return self._post("get_tracking_settings", {})["botsort"]
82
+
83
+
74
84
  def get_model_meta(self):
75
85
  if self.task_id is not None:
76
86
  return ProjectMeta.from_json(
@@ -126,7 +136,8 @@ class ModelAPI:
126
136
  device: str = None,
127
137
  runtime: str = None,
128
138
  ):
129
- if self.url is not None:
139
+ if self.task_id is None:
140
+ # TODO: proper check
130
141
  if os.path.exists(model):
131
142
  self._load_local_custom_model(model, device, runtime)
132
143
  else:
@@ -209,12 +220,15 @@ class ModelAPI:
209
220
  project_id: int = None,
210
221
  batch_size: int = None,
211
222
  conf: float = None,
223
+ img_size: int = None,
212
224
  classes: List[str] = None,
213
225
  upload_mode: str = None,
226
+ recursive: bool = False,
227
+ tracking: bool = None,
228
+ tracking_config: dict = None,
214
229
  **kwargs,
215
230
  ) -> PredictionSession:
216
- if upload_mode is not None:
217
- kwargs["upload_mode"] = upload_mode
231
+
218
232
  return PredictionSession(
219
233
  self.url,
220
234
  input=input,
@@ -225,7 +239,12 @@ class ModelAPI:
225
239
  api=self.api,
226
240
  batch_size=batch_size,
227
241
  conf=conf,
242
+ img_size=img_size,
228
243
  classes=classes,
244
+ upload_mode=upload_mode,
245
+ recursive=recursive,
246
+ tracking=tracking,
247
+ tracking_config=tracking_config,
229
248
  **kwargs,
230
249
  )
231
250
 
@@ -241,28 +260,31 @@ class ModelAPI:
241
260
  img_size: int = None,
242
261
  classes: List[str] = None,
243
262
  upload_mode: str = None,
244
- recursive: bool = None,
263
+ recursive: bool = False,
264
+ tracking: bool = None,
265
+ tracking_config: dict = None,
245
266
  **kwargs,
246
267
  ) -> List[Prediction]:
247
268
  if "show_progress" not in kwargs:
248
269
  kwargs["show_progress"] = True
249
- if recursive is not None:
250
- kwargs["recursive"] = recursive
251
- if img_size is not None:
252
- kwargs["img_size"] = img_size
253
- return list(
254
- self.predict_detached(
255
- input,
256
- image_id,
257
- video_id,
258
- dataset_id,
259
- project_id,
260
- batch_size,
261
- conf,
262
- classes,
263
- upload_mode,
264
- **kwargs,
265
- )
270
+ session = PredictionSession(
271
+ self.url,
272
+ input=input,
273
+ image_id=image_id,
274
+ video_id=video_id,
275
+ dataset_id=dataset_id,
276
+ project_id=project_id,
277
+ api=self.api,
278
+ batch_size=batch_size,
279
+ conf=conf,
280
+ img_size=img_size,
281
+ classes=classes,
282
+ upload_mode=upload_mode,
283
+ recursive=recursive,
284
+ tracking=tracking,
285
+ tracking_config=tracking_config,
286
+ **kwargs,
266
287
  )
288
+ return list(session)
267
289
 
268
290
  # ------------------------------------ #
@@ -59,6 +59,7 @@ class Prediction:
59
59
  self.source = source
60
60
  if isinstance(annotation_json, Annotation):
61
61
  annotation_json = annotation_json.to_json()
62
+
62
63
  self.annotation_json = annotation_json
63
64
  self.model_meta = model_meta
64
65
  if isinstance(self.model_meta, dict):
@@ -82,6 +83,7 @@ class Prediction:
82
83
  self._masks = None
83
84
  self._classes = None
84
85
  self._scores = None
86
+ self._track_ids = None
85
87
 
86
88
  if self.path is None and isinstance(self.source, (str, PathLike)):
87
89
  self.path = str(self.source)
@@ -125,6 +127,10 @@ class Prediction:
125
127
  )
126
128
  self._boxes = np.array(self._boxes)
127
129
  self._masks = np.array(self._masks)
130
+
131
+ custom_data = self.annotation.custom_data
132
+ if custom_data and isinstance(custom_data, list) and len(custom_data) == len(self.annotation.labels):
133
+ self._track_ids = np.array(custom_data)
128
134
 
129
135
  @property
130
136
  def boxes(self):
@@ -152,7 +158,7 @@ class Prediction:
152
158
 
153
159
  @property
154
160
  def annotation(self) -> Annotation:
155
- if self._annotation is None:
161
+ if self._annotation is None and self.annotation_json is not None:
156
162
  if self.model_meta is None:
157
163
  raise ValueError("Model meta is not provided. Cannot create annotation.")
158
164
  model_meta = get_meta_from_annotation(self.annotation_json, self.model_meta)
@@ -178,6 +184,12 @@ class Prediction:
178
184
  obj_class.name: i for i, obj_class in enumerate(self.model_meta.obj_classes)
179
185
  }
180
186
  return np.array([cls_name_to_idx[class_name] for class_name in self.classes])
187
+ @property
188
+ def track_ids(self):
189
+ """Get track IDs for each detection. Returns None for detections without tracking."""
190
+ if self._track_ids is None:
191
+ self._init_geometries()
192
+ return self._track_ids
181
193
 
182
194
  @classmethod
183
195
  def from_json(cls, json_data: Dict, **kwargs) -> "Prediction":
@@ -229,6 +241,8 @@ class Prediction:
229
241
  if self.image_id is not None:
230
242
  try:
231
243
  if api is None:
244
+ # TODO: raise more clarifying error in case of failing of api init
245
+ # what a user should do to fix it?
232
246
  api = Api()
233
247
  return api.image.download_np(self.image_id)
234
248
  except Exception as e:
@@ -67,8 +67,11 @@ class PredictionSession:
67
67
  dataset_id: Union[List[int], int] = None,
68
68
  project_id: Union[List[int], int] = None,
69
69
  api: "Api" = None,
70
+ tracking: bool = None,
71
+ tracking_config: dict = None,
70
72
  **kwargs: dict,
71
- ):
73
+ ):
74
+
72
75
  extra_input_args = ["image_ids", "video_ids", "dataset_ids", "project_ids"]
73
76
  assert (
74
77
  sum(
@@ -112,6 +115,30 @@ class PredictionSession:
112
115
  k: v for k, v in kwargs.items() if isinstance(v, (str, int, float))
113
116
  }
114
117
 
118
+ if tracking is True:
119
+ model_info = self._get_session_info()
120
+ if not model_info.get("tracking_on_videos_support", False):
121
+ raise ValueError("Tracking is not supported by this model")
122
+
123
+ if tracking_config is None:
124
+ self.tracker = "botsort"
125
+ self.tracker_settings = {}
126
+ else:
127
+ cfg = dict(tracking_config)
128
+ self.tracker = cfg.pop("tracker", "botsort")
129
+ self.tracker_settings = cfg
130
+ else:
131
+ self.tracker = None
132
+ self.tracker_settings = None
133
+
134
+ if "classes" in kwargs:
135
+ self.inference_settings["classes"] = kwargs["classes"]
136
+ # TODO: remove "settings", it is the same as inference_settings
137
+ if "settings" in kwargs:
138
+ self.inference_settings.update(kwargs["settings"])
139
+ if "inference_settings" in kwargs:
140
+ self.inference_settings.update(kwargs["inference_settings"])
141
+
115
142
  # extra input args
116
143
  image_ids = self._set_var_from_kwargs("image_ids", kwargs, image_id)
117
144
  video_ids = self._set_var_from_kwargs("video_ids", kwargs, video_id)
@@ -139,7 +166,6 @@ class PredictionSession:
139
166
  input = [input]
140
167
  if isinstance(input[0], np.ndarray):
141
168
  # input is numpy array
142
- kwargs = get_valid_kwargs(kwargs, self._predict_images, exclude=["images"])
143
169
  self._predict_images(input, **kwargs)
144
170
  elif isinstance(input[0], (str, PathLike)):
145
171
  if len(input) > 1:
@@ -180,7 +206,7 @@ class PredictionSession:
180
206
  self._iterator = self._predict_images(input, **kwargs)
181
207
  elif ext.lower() in ALLOWED_VIDEO_EXTENSIONS:
182
208
  kwargs = get_valid_kwargs(kwargs, self._predict_videos, exclude=["videos"])
183
- self._iterator = self._predict_videos(input, **kwargs)
209
+ self._iterator = self._predict_videos(input, tracker=self.tracker, tracker_settings=self.tracker_settings, **kwargs)
184
210
  else:
185
211
  raise ValueError(
186
212
  f"Unsupported file extension: {ext}. Supported extensions are: {SUPPORTED_IMG_EXTS + ALLOWED_VIDEO_EXTENSIONS}"
@@ -193,7 +219,7 @@ class PredictionSession:
193
219
  if len(video_ids) > 1:
194
220
  raise ValueError("Only one video id can be provided.")
195
221
  kwargs = get_valid_kwargs(kwargs, self._predict_videos, exclude=["videos"])
196
- self._iterator = self._predict_videos(video_ids, **kwargs)
222
+ self._iterator = self._predict_videos(video_ids, tracker=self.tracker, tracker_settings=self.tracker_settings, **kwargs)
197
223
  elif dataset_ids is not None:
198
224
  kwargs = get_valid_kwargs(
199
225
  kwargs,
@@ -268,6 +294,8 @@ class PredictionSession:
268
294
  body["state"]["settings"] = self.inference_settings
269
295
  if self.api_token is not None:
270
296
  body["api_token"] = self.api_token
297
+ if "model_prediction_suffix" in self.kwargs:
298
+ body["state"]["model_prediction_suffix"] = self.kwargs["model_prediction_suffix"]
271
299
  return body
272
300
 
273
301
  def _post(self, method, *args, retries=5, **kwargs) -> requests.Response:
@@ -303,6 +331,11 @@ class PredictionSession:
303
331
  if retry_idx + 1 == retries:
304
332
  raise exc
305
333
 
334
+ def _get_session_info(self) -> Dict[str, Any]:
335
+ method = "get_session_info"
336
+ r = self._post(method, json=self._get_json_body())
337
+ return r.json()
338
+
306
339
  def _get_inference_progress(self):
307
340
  method = "get_inference_progress"
308
341
  r = self._post(method, json=self._get_json_body())
@@ -331,9 +364,21 @@ class PredictionSession:
331
364
  logger.info("Inference request will be cleared on the server")
332
365
  return r.json()
333
366
 
367
+ def _get_final_result(self):
368
+ method = "get_inference_result"
369
+ r = self._post(
370
+ method,
371
+ json=self._get_json_body(),
372
+ )
373
+ return r.json()
374
+
334
375
  def _on_infernce_end(self):
335
376
  if self.inference_request_uuid is None:
336
377
  return
378
+ try:
379
+ self.final_result = self._get_final_result()
380
+ except Exception as e:
381
+ logger.debug("Failed to get final result:", exc_info=True)
337
382
  self._clear_inference_request()
338
383
 
339
384
  @property
@@ -478,18 +523,16 @@ class PredictionSession:
478
523
  "Inference is already running. Please stop it before starting a new one."
479
524
  )
480
525
  resp = self._post(method, **kwargs).json()
481
-
482
526
  self.inference_request_uuid = resp["inference_request_uuid"]
483
-
484
- logger.info(
485
- "Inference has started:",
486
- extra={"inference_request_uuid": resp.get("inference_request_uuid")},
487
- )
488
527
  try:
489
528
  resp, has_started = self._wait_for_inference_start(tqdm=self.tqdm)
490
529
  except:
491
530
  self.stop()
492
531
  raise
532
+ logger.info(
533
+ "Inference has started:",
534
+ extra={"inference_request_uuid": resp.get("inference_request_uuid")},
535
+ )
493
536
  frame_iterator = self.Iterator(resp["progress"]["total"], self, tqdm=self.tqdm)
494
537
  return frame_iterator
495
538
 
@@ -537,7 +580,11 @@ class PredictionSession:
537
580
  return self._predict_images_bytes(images, batch_size=batch_size)
538
581
 
539
582
  def _predict_images_ids(
540
- self, images: List[int], batch_size: int = None, upload_mode: str = None
583
+ self,
584
+ images: List[int],
585
+ batch_size: int = None,
586
+ upload_mode: str = None,
587
+ output_project_id: int = None,
541
588
  ):
542
589
  method = "inference_batch_ids_async"
543
590
  json_body = self._get_json_body()
@@ -547,6 +594,8 @@ class PredictionSession:
547
594
  state["batch_size"] = batch_size
548
595
  if upload_mode is not None:
549
596
  state["upload_mode"] = upload_mode
597
+ if output_project_id is not None:
598
+ state["output_project_id"] = output_project_id
550
599
  return self._start_inference(method, json=json_body)
551
600
 
552
601
  def _predict_videos(
@@ -558,7 +607,8 @@ class PredictionSession:
558
607
  end_frame=None,
559
608
  duration=None,
560
609
  direction: Literal["forward", "backward"] = None,
561
- tracker: Literal["bot", "deepsort"] = None,
610
+ tracker: Literal["botsort"] = None,
611
+ tracker_settings: dict = None,
562
612
  batch_size: int = None,
563
613
  ):
564
614
  if len(videos) != 1:
@@ -573,6 +623,7 @@ class PredictionSession:
573
623
  ("duration", duration),
574
624
  ("direction", direction),
575
625
  ("tracker", tracker),
626
+ ("tracker_settings", tracker_settings),
576
627
  ("batch_size", batch_size),
577
628
  ):
578
629
  if value is not None:
@@ -594,8 +645,11 @@ class PredictionSession:
594
645
  encoder = MultipartEncoder(fields)
595
646
  if self.tqdm is not None:
596
647
 
648
+ bytes_read = 0
597
649
  def _callback(monitor):
598
- self.tqdm.update(monitor.bytes_read)
650
+ nonlocal bytes_read
651
+ self.tqdm.update(monitor.bytes_read - bytes_read)
652
+ bytes_read = monitor.bytes_read
599
653
 
600
654
  video_size = get_file_size(video_path)
601
655
  self._update_progress(self.tqdm, "Uploading video", 0, video_size, is_size=True)
@@ -620,6 +674,7 @@ class PredictionSession:
620
674
  upload_mode: str = None,
621
675
  iou_merge_threshold: float = None,
622
676
  cache_project_on_model: bool = None,
677
+ output_project_id: int = None,
623
678
  ):
624
679
  if len(project_ids) != 1:
625
680
  raise ValueError("Only one project can be processed at a time.")
@@ -637,7 +692,8 @@ class PredictionSession:
637
692
  state["iou_merge_threshold"] = iou_merge_threshold
638
693
  if cache_project_on_model is not None:
639
694
  state["cache_project_on_model"] = cache_project_on_model
640
-
695
+ if output_project_id is not None:
696
+ state["output_project_id"] = output_project_id
641
697
  return self._start_inference(method, json=json_body)
642
698
 
643
699
  def _predict_datasets(
@@ -3,6 +3,7 @@ from typing import List, Optional
3
3
  import numpy as np
4
4
 
5
5
  from supervisely.geometry.cuboid_3d import Cuboid3d
6
+ from supervisely.geometry.polyline_3d import Polyline3D
6
7
 
7
8
 
8
9
  class Prediction:
@@ -81,3 +82,9 @@ class PredictionCuboid3d(Prediction):
81
82
  super(PredictionCuboid3d, self).__init__(class_name=class_name)
82
83
  self.cuboid_3d = cuboid_3d
83
84
  self.score = score
85
+
86
+
87
+ class PredictionPolyline3D(Prediction):
88
+ def __init__(self, class_name: str, polyline_3d: Polyline3D):
89
+ super(PredictionPolyline3D, self).__init__(class_name=class_name)
90
+ self.polyline_3d = polyline_3d
@@ -1,10 +1,8 @@
1
- from supervisely.sly_logger import logger
2
-
3
1
  try:
4
- from supervisely.nn.tracker.bot_sort import BoTTracker
5
- from supervisely.nn.tracker.deep_sort import DeepSortTracker
2
+ from supervisely.nn.tracker.botsort_tracker import BotSortTracker
3
+ from supervisely.nn.tracker.calculate_metrics import TrackingEvaluator, evaluate
4
+ TRACKING_LIBS_INSTALLED = True
6
5
  except ImportError:
7
- logger.error(
8
- "Failed to import tracker modules. Please try install extras with 'pip install supervisely[tracking]'"
9
- )
10
- raise
6
+ TRACKING_LIBS_INSTALLED = False
7
+
8
+ from supervisely.nn.tracker.visualize import TrackingVisualizer, visualize