supervisely 6.73.410__py3-none-any.whl → 6.73.470__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of supervisely might be problematic. Click here for more details.
- supervisely/__init__.py +136 -1
- supervisely/_utils.py +81 -0
- supervisely/annotation/json_geometries_map.py +2 -0
- supervisely/annotation/label.py +80 -3
- supervisely/api/annotation_api.py +9 -9
- supervisely/api/api.py +67 -43
- supervisely/api/app_api.py +72 -5
- supervisely/api/dataset_api.py +108 -33
- supervisely/api/entity_annotation/figure_api.py +113 -49
- supervisely/api/image_api.py +82 -0
- supervisely/api/module_api.py +10 -0
- supervisely/api/nn/deploy_api.py +15 -9
- supervisely/api/nn/ecosystem_models_api.py +201 -0
- supervisely/api/nn/neural_network_api.py +12 -3
- supervisely/api/pointcloud/pointcloud_api.py +38 -0
- supervisely/api/pointcloud/pointcloud_episode_annotation_api.py +3 -0
- supervisely/api/project_api.py +213 -6
- supervisely/api/task_api.py +11 -1
- supervisely/api/video/video_annotation_api.py +4 -2
- supervisely/api/video/video_api.py +79 -1
- supervisely/api/video/video_figure_api.py +24 -11
- supervisely/api/volume/volume_api.py +38 -0
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +14 -6
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +88 -0
- supervisely/app/fastapi/subapp.py +175 -42
- supervisely/app/fastapi/templating.py +1 -1
- supervisely/app/fastapi/websocket.py +77 -9
- supervisely/app/singleton.py +21 -0
- supervisely/app/v1/app_service.py +18 -2
- supervisely/app/v1/constants.py +7 -1
- supervisely/app/widgets/__init__.py +11 -1
- supervisely/app/widgets/agent_selector/template.html +1 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/dataset_thumbnail/dataset_thumbnail.py +11 -2
- supervisely/app/widgets/dataset_thumbnail/template.html +3 -1
- supervisely/app/widgets/deploy_model/deploy_model.py +750 -0
- supervisely/app/widgets/dialog/dialog.py +12 -0
- supervisely/app/widgets/dialog/template.html +2 -1
- supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
- supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
- supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
- supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +195 -0
- supervisely/app/widgets/experiment_selector/experiment_selector.py +454 -263
- supervisely/app/widgets/fast_table/fast_table.py +713 -126
- supervisely/app/widgets/fast_table/script.js +492 -95
- supervisely/app/widgets/fast_table/style.css +54 -0
- supervisely/app/widgets/fast_table/template.html +45 -5
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +523 -0
- supervisely/app/widgets/heatmap/script.js +378 -0
- supervisely/app/widgets/heatmap/style.css +227 -0
- supervisely/app/widgets/heatmap/template.html +21 -0
- supervisely/app/widgets/input_tag/input_tag.py +102 -15
- supervisely/app/widgets/input_tag_list/__init__.py +0 -0
- supervisely/app/widgets/input_tag_list/input_tag_list.py +274 -0
- supervisely/app/widgets/input_tag_list/template.html +70 -0
- supervisely/app/widgets/radio_table/radio_table.py +10 -2
- supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
- supervisely/app/widgets/radio_tabs/template.html +1 -0
- supervisely/app/widgets/select/select.py +6 -4
- supervisely/app/widgets/select_dataset/select_dataset.py +6 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +83 -7
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tabs/tabs.py +22 -6
- supervisely/app/widgets/tabs/template.html +5 -1
- supervisely/app/widgets/transfer/style.css +3 -0
- supervisely/app/widgets/transfer/template.html +3 -1
- supervisely/app/widgets/transfer/transfer.py +48 -45
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/convert/image/csv/csv_converter.py +24 -15
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +43 -41
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +75 -51
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +137 -124
- supervisely/convert/video/video_converter.py +2 -2
- supervisely/geometry/polyline_3d.py +110 -0
- supervisely/io/env.py +161 -1
- supervisely/nn/artifacts/__init__.py +1 -1
- supervisely/nn/artifacts/artifacts.py +10 -2
- supervisely/nn/artifacts/detectron2.py +1 -0
- supervisely/nn/artifacts/hrda.py +1 -0
- supervisely/nn/artifacts/mmclassification.py +20 -0
- supervisely/nn/artifacts/mmdetection.py +5 -3
- supervisely/nn/artifacts/mmsegmentation.py +1 -0
- supervisely/nn/artifacts/ritm.py +1 -0
- supervisely/nn/artifacts/rtdetr.py +1 -0
- supervisely/nn/artifacts/unet.py +1 -0
- supervisely/nn/artifacts/utils.py +3 -0
- supervisely/nn/artifacts/yolov5.py +2 -0
- supervisely/nn/artifacts/yolov8.py +1 -0
- supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
- supervisely/nn/experiments.py +9 -0
- supervisely/nn/inference/cache.py +37 -17
- supervisely/nn/inference/gui/serving_gui_template.py +39 -13
- supervisely/nn/inference/inference.py +953 -211
- supervisely/nn/inference/inference_request.py +15 -8
- supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
- supervisely/nn/inference/object_detection/object_detection.py +1 -0
- supervisely/nn/inference/predict_app/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/classes_selector.py +160 -0
- supervisely/nn/inference/predict_app/gui/gui.py +915 -0
- supervisely/nn/inference/predict_app/gui/input_selector.py +344 -0
- supervisely/nn/inference/predict_app/gui/model_selector.py +77 -0
- supervisely/nn/inference/predict_app/gui/output_selector.py +179 -0
- supervisely/nn/inference/predict_app/gui/preview.py +93 -0
- supervisely/nn/inference/predict_app/gui/settings_selector.py +881 -0
- supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
- supervisely/nn/inference/predict_app/gui/utils.py +399 -0
- supervisely/nn/inference/predict_app/predict_app.py +176 -0
- supervisely/nn/inference/session.py +47 -39
- supervisely/nn/inference/tracking/bbox_tracking.py +5 -1
- supervisely/nn/inference/tracking/point_tracking.py +5 -1
- supervisely/nn/inference/tracking/tracker_interface.py +4 -0
- supervisely/nn/inference/uploader.py +9 -5
- supervisely/nn/model/model_api.py +44 -22
- supervisely/nn/model/prediction.py +15 -1
- supervisely/nn/model/prediction_session.py +70 -14
- supervisely/nn/prediction_dto.py +7 -0
- supervisely/nn/tracker/__init__.py +6 -8
- supervisely/nn/tracker/base_tracker.py +54 -0
- supervisely/nn/tracker/botsort/__init__.py +1 -0
- supervisely/nn/tracker/botsort/botsort_config.yaml +30 -0
- supervisely/nn/tracker/botsort/osnet_reid/__init__.py +0 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
- supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
- supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
- supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
- supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
- supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
- supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
- supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
- supervisely/nn/tracker/botsort_tracker.py +273 -0
- supervisely/nn/tracker/calculate_metrics.py +264 -0
- supervisely/nn/tracker/utils.py +273 -0
- supervisely/nn/tracker/visualize.py +520 -0
- supervisely/nn/training/gui/gui.py +152 -49
- supervisely/nn/training/gui/hyperparameters_selector.py +1 -1
- supervisely/nn/training/gui/model_selector.py +8 -6
- supervisely/nn/training/gui/train_val_splits_selector.py +144 -71
- supervisely/nn/training/gui/training_artifacts.py +3 -1
- supervisely/nn/training/train_app.py +225 -46
- supervisely/project/pointcloud_episode_project.py +12 -8
- supervisely/project/pointcloud_project.py +12 -8
- supervisely/project/project.py +221 -75
- supervisely/template/experiment/experiment.html.jinja +105 -55
- supervisely/template/experiment/experiment_generator.py +258 -112
- supervisely/template/experiment/header.html.jinja +31 -13
- supervisely/template/experiment/sly-style.css +7 -2
- supervisely/versions.json +3 -1
- supervisely/video/sampling.py +42 -20
- supervisely/video/video.py +41 -12
- supervisely/video_annotation/video_figure.py +38 -4
- supervisely/volume/stl_converter.py +2 -0
- supervisely/worker_api/agent_rpc.py +24 -1
- supervisely/worker_api/rpc_servicer.py +31 -7
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/METADATA +22 -14
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/RECORD +167 -148
- supervisely_lib/__init__.py +6 -1
- supervisely/app/widgets/experiment_selector/style.css +0 -27
- supervisely/app/widgets/experiment_selector/template.html +0 -61
- supervisely/nn/tracker/bot_sort/__init__.py +0 -21
- supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
- supervisely/nn/tracker/bot_sort/matching.py +0 -127
- supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
- supervisely/nn/tracker/deep_sort/__init__.py +0 -6
- supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
- supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
- supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
- supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
- supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
- supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
- supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
- supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
- supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
- supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
- supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
- supervisely/nn/tracker/tracker.py +0 -285
- supervisely/nn/tracker/utils/kalman_filter.py +0 -492
- supervisely/nn/tracking/__init__.py +0 -1
- supervisely/nn/tracking/boxmot.py +0 -114
- supervisely/nn/tracking/tracking.py +0 -24
- /supervisely/{nn/tracker/utils → app/widgets/deploy_model}/__init__.py +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/LICENSE +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/WHEEL +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/top_level.txt +0 -0
|
@@ -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["
|
|
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["
|
|
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
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if resp
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if resp
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
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["
|
|
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["
|
|
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=
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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,
|
|
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["
|
|
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
|
-
|
|
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(
|
supervisely/nn/prediction_dto.py
CHANGED
|
@@ -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.
|
|
5
|
-
from supervisely.nn.tracker.
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
raise
|
|
6
|
+
TRACKING_LIBS_INSTALLED = False
|
|
7
|
+
|
|
8
|
+
from supervisely.nn.tracker.visualize import TrackingVisualizer, visualize
|