supervisely 6.73.284__py3-none-any.whl → 6.73.286__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 +1 -1
- supervisely/_utils.py +9 -0
- supervisely/api/entity_annotation/figure_api.py +3 -0
- supervisely/api/module_api.py +35 -1
- supervisely/api/video/video_api.py +1 -1
- supervisely/api/video_annotation_tool_api.py +58 -7
- supervisely/app/fastapi/subapp.py +12 -11
- supervisely/nn/inference/cache.py +19 -1
- supervisely/nn/inference/inference.py +23 -0
- supervisely/nn/inference/tracking/base_tracking.py +362 -0
- supervisely/nn/inference/tracking/bbox_tracking.py +179 -129
- supervisely/nn/inference/tracking/mask_tracking.py +420 -329
- supervisely/nn/inference/tracking/point_tracking.py +325 -288
- supervisely/nn/inference/tracking/tracker_interface.py +346 -13
- {supervisely-6.73.284.dist-info → supervisely-6.73.286.dist-info}/METADATA +1 -1
- {supervisely-6.73.284.dist-info → supervisely-6.73.286.dist-info}/RECORD +20 -19
- {supervisely-6.73.284.dist-info → supervisely-6.73.286.dist-info}/LICENSE +0 -0
- {supervisely-6.73.284.dist-info → supervisely-6.73.286.dist-info}/WHEEL +0 -0
- {supervisely-6.73.284.dist-info → supervisely-6.73.286.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.284.dist-info → supervisely-6.73.286.dist-info}/top_level.txt +0 -0
|
@@ -1,59 +1,39 @@
|
|
|
1
|
-
import functools
|
|
2
|
-
import json
|
|
3
1
|
import time
|
|
4
2
|
import uuid
|
|
5
3
|
from pathlib import Path
|
|
6
4
|
from queue import Queue
|
|
7
5
|
from threading import Event, Thread
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
|
6
|
+
from typing import Any, BinaryIO, Dict, List, Optional
|
|
9
7
|
|
|
10
8
|
import numpy as np
|
|
11
|
-
from
|
|
9
|
+
from pydantic import ValidationError
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
import supervisely.nn.inference.tracking.functional as F
|
|
11
|
+
from supervisely.annotation.annotation import Annotation
|
|
15
12
|
from supervisely.annotation.label import Geometry, Label
|
|
16
|
-
from supervisely.
|
|
17
|
-
from supervisely.
|
|
13
|
+
from supervisely.annotation.obj_class import ObjClass
|
|
14
|
+
from supervisely.api.api import Api
|
|
15
|
+
from supervisely.api.module_api import ApiField
|
|
16
|
+
from supervisely.api.video.video_figure_api import FigureInfo
|
|
17
|
+
from supervisely.geometry.helpers import deserialize_geometry
|
|
18
|
+
from supervisely.geometry.rectangle import Rectangle
|
|
19
|
+
from supervisely.imaging import image as sly_image
|
|
20
|
+
from supervisely.nn.inference.tracking.base_tracking import BaseTracking
|
|
21
|
+
from supervisely.nn.inference.tracking.tracker_interface import (
|
|
22
|
+
TrackerInterface,
|
|
23
|
+
TrackerInterfaceV2,
|
|
24
|
+
)
|
|
18
25
|
from supervisely.nn.prediction_dto import Prediction, PredictionBBox
|
|
26
|
+
from supervisely.sly_logger import logger
|
|
27
|
+
from supervisely.task.progress import Progress
|
|
19
28
|
|
|
20
29
|
|
|
21
|
-
class BBoxTracking(
|
|
22
|
-
def __init__(
|
|
23
|
-
self,
|
|
24
|
-
model_dir: Optional[str] = None,
|
|
25
|
-
custom_inference_settings: Optional[Union[Dict[str, Any], str]] = None,
|
|
26
|
-
):
|
|
27
|
-
Inference.__init__(
|
|
28
|
-
self,
|
|
29
|
-
model_dir,
|
|
30
|
-
custom_inference_settings,
|
|
31
|
-
sliding_window_mode=None,
|
|
32
|
-
use_gui=False,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
try:
|
|
36
|
-
self.load_on_device(model_dir, "cuda")
|
|
37
|
-
except RuntimeError:
|
|
38
|
-
self.load_on_device(model_dir, "cpu")
|
|
39
|
-
sly.logger.warn("Failed to load model on CUDA device.")
|
|
40
|
-
|
|
41
|
-
sly.logger.debug(
|
|
42
|
-
"Smart cache params",
|
|
43
|
-
extra={"ttl": sly.env.smart_cache_ttl(), "maxsize": sly.env.smart_cache_size()},
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
def get_info(self):
|
|
47
|
-
info = super().get_info()
|
|
48
|
-
info["task type"] = "tracking"
|
|
49
|
-
return info
|
|
50
|
-
|
|
30
|
+
class BBoxTracking(BaseTracking):
|
|
51
31
|
def _deserialize_geometry(self, data: dict):
|
|
52
32
|
geometry_type_str = data["type"]
|
|
53
33
|
geometry_json = data["data"]
|
|
54
|
-
return
|
|
34
|
+
return deserialize_geometry(geometry_type_str, geometry_json)
|
|
55
35
|
|
|
56
|
-
def _track(self, api:
|
|
36
|
+
def _track(self, api: Api, context: dict, notify_annotation_tool: bool):
|
|
57
37
|
video_interface = TrackerInterface(
|
|
58
38
|
context=context,
|
|
59
39
|
api=api,
|
|
@@ -119,7 +99,7 @@ class BBoxTracking(Inference):
|
|
|
119
99
|
init = False
|
|
120
100
|
for _ in video_interface.frames_loader_generator():
|
|
121
101
|
geom = video_interface.geometries[fig_id]
|
|
122
|
-
if not isinstance(geom,
|
|
102
|
+
if not isinstance(geom, Rectangle):
|
|
123
103
|
stop_upload_event.set()
|
|
124
104
|
raise TypeError(f"Tracking does not work with {geom.geometry_name()}.")
|
|
125
105
|
|
|
@@ -155,7 +135,7 @@ class BBoxTracking(Inference):
|
|
|
155
135
|
raise
|
|
156
136
|
stop_upload_event.set()
|
|
157
137
|
|
|
158
|
-
def _track_api(self, api:
|
|
138
|
+
def _track_api(self, api: Api, context: dict, request_uuid: str = None):
|
|
159
139
|
track_t = time.monotonic()
|
|
160
140
|
# unused fields:
|
|
161
141
|
context["trackId"] = "auto"
|
|
@@ -211,7 +191,7 @@ class BBoxTracking(Inference):
|
|
|
211
191
|
)
|
|
212
192
|
for box_i, input_geom in enumerate(input_bboxes, 1):
|
|
213
193
|
input_bbox = input_geom["data"]
|
|
214
|
-
bbox =
|
|
194
|
+
bbox = Rectangle.from_json(input_bbox)
|
|
215
195
|
predictions_for_object = []
|
|
216
196
|
init = False
|
|
217
197
|
frame_t = time.monotonic()
|
|
@@ -281,7 +261,7 @@ class BBoxTracking(Inference):
|
|
|
281
261
|
}
|
|
282
262
|
results = [[] for _ in range(len(frames) - 1)]
|
|
283
263
|
for geometry in geometries:
|
|
284
|
-
if not isinstance(geometry,
|
|
264
|
+
if not isinstance(geometry, Rectangle):
|
|
285
265
|
raise TypeError(f"Tracking does not work with {geometry.geometry_name()}.")
|
|
286
266
|
target = PredictionBBox(
|
|
287
267
|
"",
|
|
@@ -298,95 +278,165 @@ class BBoxTracking(Inference):
|
|
|
298
278
|
)
|
|
299
279
|
sly_pred_geometry = self._to_sly_geometry(pred_geometry)
|
|
300
280
|
results[i].append(
|
|
301
|
-
{"type":
|
|
281
|
+
{"type": Rectangle.geometry_name(), "data": sly_pred_geometry.to_json()}
|
|
302
282
|
)
|
|
303
283
|
return results
|
|
304
284
|
|
|
305
|
-
def
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
285
|
+
def _track_async(self, api: Api, context: dict, inference_request_uuid: str = None):
|
|
286
|
+
inference_request = self._inference_requests[inference_request_uuid]
|
|
287
|
+
tracker_interface = TrackerInterfaceV2(api, context, self.cache)
|
|
288
|
+
progress: Progress = inference_request["progress"]
|
|
289
|
+
frames_count = tracker_interface.frames_count
|
|
290
|
+
figures = tracker_interface.figures
|
|
291
|
+
progress_total = frames_count * len(figures)
|
|
292
|
+
progress.total = progress_total
|
|
293
|
+
|
|
294
|
+
def _upload_f(items: List[FigureInfo]):
|
|
295
|
+
with inference_request["lock"]:
|
|
296
|
+
inference_request["pending_results"].extend(items)
|
|
297
|
+
|
|
298
|
+
def _notify_f(items: List[FigureInfo]):
|
|
299
|
+
items_by_object_id: Dict[int, List[FigureInfo]] = {}
|
|
300
|
+
for item in items:
|
|
301
|
+
items_by_object_id.setdefault(item.object_id, []).append(item)
|
|
302
|
+
|
|
303
|
+
for object_id, object_items in items_by_object_id.items():
|
|
304
|
+
frame_range = [
|
|
305
|
+
min(item.frame_index for item in object_items),
|
|
306
|
+
max(item.frame_index for item in object_items),
|
|
307
|
+
]
|
|
308
|
+
progress.iters_done_report(len(object_items))
|
|
309
|
+
tracker_interface.notify_progress(progress.current, progress.total, frame_range)
|
|
310
|
+
|
|
311
|
+
api.logger.info("Start tracking.")
|
|
312
|
+
try:
|
|
313
|
+
with tracker_interface(_upload_f, _notify_f):
|
|
314
|
+
for fig_i, figure in enumerate(figures, 1):
|
|
315
|
+
figure = api.video.figure._convert_json_info(figure)
|
|
316
|
+
if not figure.geometry_type == Rectangle.geometry_name():
|
|
317
|
+
raise TypeError(f"Tracking does not work with {figure.geometry_type}.")
|
|
318
|
+
api.logger.info("figure:", extra={"figure": figure._asdict()})
|
|
319
|
+
sly_geometry: Rectangle = deserialize_geometry(
|
|
320
|
+
figure.geometry_type, figure.geometry
|
|
321
|
+
)
|
|
322
|
+
init = False
|
|
323
|
+
for frame_i, (frame, next_frame) in enumerate(
|
|
324
|
+
tracker_interface.frames_loader_generator(), 1
|
|
325
|
+
):
|
|
326
|
+
target = PredictionBBox(
|
|
327
|
+
"", # TODO: can this be useful?
|
|
328
|
+
[
|
|
329
|
+
sly_geometry.top,
|
|
330
|
+
sly_geometry.left,
|
|
331
|
+
sly_geometry.bottom,
|
|
332
|
+
sly_geometry.right,
|
|
333
|
+
],
|
|
334
|
+
None,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
if not init:
|
|
338
|
+
self.initialize(frame.image, target)
|
|
339
|
+
init = True
|
|
340
|
+
|
|
341
|
+
logger.debug("Start prediction")
|
|
342
|
+
t = time.time()
|
|
343
|
+
geometry = self.predict(
|
|
344
|
+
rgb_image=next_frame.image,
|
|
345
|
+
prev_rgb_image=frame.image,
|
|
346
|
+
target_bbox=target,
|
|
347
|
+
settings=self.custom_inference_settings_dict,
|
|
348
|
+
)
|
|
349
|
+
logger.debug("Prediction done. Time: %f sec", time.time() - t)
|
|
350
|
+
sly_geometry = self._to_sly_geometry(geometry)
|
|
351
|
+
|
|
352
|
+
figure_id = uuid.uuid5(
|
|
353
|
+
namespace=uuid.NAMESPACE_URL, name=f"{time.time()}"
|
|
354
|
+
).hex
|
|
355
|
+
result_figure = api.video.figure._convert_json_info(
|
|
356
|
+
{
|
|
357
|
+
ApiField.ID: figure_id,
|
|
358
|
+
ApiField.OBJECT_ID: figure.object_id,
|
|
359
|
+
"meta": {"frame": next_frame.frame_index},
|
|
360
|
+
ApiField.GEOMETRY_TYPE: sly_geometry.geometry_name(),
|
|
361
|
+
ApiField.GEOMETRY: sly_geometry.to_json(),
|
|
362
|
+
ApiField.TRACK_ID: tracker_interface.track_id,
|
|
363
|
+
}
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
tracker_interface.add_prediction(result_figure)
|
|
367
|
+
|
|
368
|
+
logger.debug(
|
|
369
|
+
"Frame [%d / %d] processed.",
|
|
370
|
+
frame_i,
|
|
371
|
+
tracker_interface.frames_count,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
if inference_request["cancel_inference"]:
|
|
375
|
+
return
|
|
376
|
+
if tracker_interface.is_stopped():
|
|
377
|
+
reason = tracker_interface.stop_reason()
|
|
378
|
+
if isinstance(reason, Exception):
|
|
379
|
+
raise reason
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
api.logger.info(
|
|
383
|
+
"Figure [%d, %d] tracked.",
|
|
384
|
+
fig_i,
|
|
385
|
+
len(figures),
|
|
386
|
+
extra={"figure_id": figure.id},
|
|
387
|
+
)
|
|
388
|
+
except Exception:
|
|
389
|
+
progress.message = "Error occured during tracking"
|
|
390
|
+
raise
|
|
391
|
+
else:
|
|
392
|
+
progress.message = "Ready"
|
|
393
|
+
finally:
|
|
394
|
+
progress.set(current=0, total=1, report=True)
|
|
395
|
+
|
|
396
|
+
def track(self, api: Api, state: Dict, context: Dict):
|
|
397
|
+
fn = self.send_error_data(api, context)(self._track)
|
|
398
|
+
self.schedule_task(fn, api, context, notify_annotation_tool=True)
|
|
399
|
+
return {"message": "Track task started."}
|
|
400
|
+
|
|
401
|
+
def track_api(self, api: Api, state: Dict, context: Dict):
|
|
402
|
+
request_uuid = uuid.uuid5(namespace=uuid.NAMESPACE_URL, name=f"{time.time()}").hex
|
|
403
|
+
result = self._track_api(api, context, request_uuid)
|
|
404
|
+
logger.info("Track-api request processed.", extra={"request_uuid": request_uuid})
|
|
405
|
+
return result
|
|
406
|
+
|
|
407
|
+
def track_api_files(self, files: List[BinaryIO], settings: Dict):
|
|
408
|
+
logger.info("Start tracking with settings:", extra={"settings": settings})
|
|
311
409
|
frame_indexes = list(
|
|
312
|
-
range(
|
|
410
|
+
range(settings["frame_index"], settings["frame_index"] + settings["frames"] + 1)
|
|
313
411
|
)
|
|
314
|
-
geometries = map(self._deserialize_geometry,
|
|
412
|
+
geometries = map(self._deserialize_geometry, settings["input_geometries"])
|
|
315
413
|
frames = []
|
|
316
414
|
for file, frame_idx in zip(files, frame_indexes):
|
|
317
|
-
img_bytes = file.
|
|
318
|
-
frame =
|
|
415
|
+
img_bytes = file.read()
|
|
416
|
+
frame = sly_image.read_bytes(img_bytes)
|
|
319
417
|
frames.append(frame)
|
|
320
|
-
|
|
321
|
-
return self._inference(frames, geometries,
|
|
322
|
-
|
|
323
|
-
def
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
def send_error_data(func):
|
|
330
|
-
@functools.wraps(func)
|
|
331
|
-
def wrapper(*args, **kwargs):
|
|
332
|
-
value = None
|
|
333
|
-
try:
|
|
334
|
-
value = func(*args, **kwargs)
|
|
335
|
-
except Exception as exc:
|
|
336
|
-
request: Request = args[0]
|
|
337
|
-
context = request.state.context
|
|
338
|
-
api: sly.Api = request.state.api
|
|
339
|
-
track_id = context["trackId"]
|
|
340
|
-
api.logger.error(f"An error occured: {repr(exc)}")
|
|
341
|
-
|
|
342
|
-
api.post(
|
|
343
|
-
"videos.notify-annotation-tool",
|
|
344
|
-
data={
|
|
345
|
-
"type": "videos:tracking-error",
|
|
346
|
-
"data": {
|
|
347
|
-
"trackId": track_id,
|
|
348
|
-
"error": {"message": repr(exc)},
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
)
|
|
352
|
-
return value
|
|
353
|
-
|
|
354
|
-
return wrapper
|
|
355
|
-
|
|
356
|
-
@send_error_data
|
|
357
|
-
def track(request: Request):
|
|
358
|
-
return self._track(
|
|
359
|
-
request.state.api, request.state.context, notify_annotation_tool=True
|
|
418
|
+
logger.info("Start tracking.")
|
|
419
|
+
return self._inference(frames, geometries, settings)
|
|
420
|
+
|
|
421
|
+
def track_async(self, api: Api, state: Dict, context: Dict):
|
|
422
|
+
batch_size = context.get("batch_size", self.get_batch_size())
|
|
423
|
+
if self.max_batch_size is not None and batch_size > self.max_batch_size:
|
|
424
|
+
raise ValidationError(
|
|
425
|
+
f"Batch size should be less than or equal to {self.max_batch_size} for this model."
|
|
360
426
|
)
|
|
361
427
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return {"message": "Track task started."}
|
|
366
|
-
|
|
367
|
-
@server.post("/track-api")
|
|
368
|
-
def track_api(request: Request):
|
|
369
|
-
inference_request_uuid = uuid.uuid5(
|
|
370
|
-
namespace=uuid.NAMESPACE_URL, name=f"{time.time()}"
|
|
371
|
-
).hex
|
|
372
|
-
sly.logger.info(
|
|
373
|
-
"Received track-api request.", extra={"request_uuid": inference_request_uuid}
|
|
374
|
-
)
|
|
375
|
-
result = self._track_api(
|
|
376
|
-
request.state.api, request.state.context, request_uuid=inference_request_uuid
|
|
377
|
-
)
|
|
378
|
-
sly.logger.info(
|
|
379
|
-
"Track-api request processed.", extra={"request_uuid": inference_request_uuid}
|
|
380
|
-
)
|
|
381
|
-
return result
|
|
428
|
+
inference_request_uuid = uuid.uuid5(namespace=uuid.NAMESPACE_URL, name=f"{time.time()}").hex
|
|
429
|
+
fn = self.send_error_data(api, context)(self._track_async)
|
|
430
|
+
self.schedule_task(fn, api, context, inference_request_uuid=inference_request_uuid)
|
|
382
431
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
432
|
+
logger.debug(
|
|
433
|
+
"Inference has scheduled from 'track_async' endpoint",
|
|
434
|
+
extra={"inference_request_uuid": inference_request_uuid},
|
|
435
|
+
)
|
|
436
|
+
return {
|
|
437
|
+
"message": "Inference has started.",
|
|
438
|
+
"inference_request_uuid": inference_request_uuid,
|
|
439
|
+
}
|
|
390
440
|
|
|
391
441
|
def initialize(self, init_rgb_image: np.ndarray, target_bbox: PredictionBBox) -> None:
|
|
392
442
|
"""
|
|
@@ -443,24 +493,24 @@ class BBoxTracking(Inference):
|
|
|
443
493
|
fill_rectangles=False,
|
|
444
494
|
)
|
|
445
495
|
|
|
446
|
-
def _to_sly_geometry(self, dto: PredictionBBox) ->
|
|
496
|
+
def _to_sly_geometry(self, dto: PredictionBBox) -> Rectangle:
|
|
447
497
|
top, left, bottom, right = dto.bbox_tlbr
|
|
448
|
-
geometry =
|
|
498
|
+
geometry = Rectangle(top=top, left=left, bottom=bottom, right=right)
|
|
449
499
|
return geometry
|
|
450
500
|
|
|
451
|
-
def _create_label(self, dto: PredictionBBox) ->
|
|
501
|
+
def _create_label(self, dto: PredictionBBox) -> Rectangle:
|
|
452
502
|
geometry = self._to_sly_geometry(dto)
|
|
453
|
-
return Label(geometry,
|
|
503
|
+
return Label(geometry, ObjClass("", Rectangle))
|
|
454
504
|
|
|
455
505
|
def _get_obj_class_shape(self):
|
|
456
|
-
return
|
|
506
|
+
return Rectangle
|
|
457
507
|
|
|
458
508
|
def _predictions_to_annotation(
|
|
459
509
|
self,
|
|
460
510
|
image: np.ndarray,
|
|
461
511
|
predictions: List[Prediction],
|
|
462
512
|
classes_whitelist: Optional[List[str]] = None,
|
|
463
|
-
) ->
|
|
513
|
+
) -> Annotation:
|
|
464
514
|
labels = []
|
|
465
515
|
for prediction in predictions:
|
|
466
516
|
if (
|
|
@@ -478,6 +528,6 @@ class BBoxTracking(Inference):
|
|
|
478
528
|
labels.append(label)
|
|
479
529
|
|
|
480
530
|
# create annotation with correct image resolution
|
|
481
|
-
ann =
|
|
531
|
+
ann = Annotation(img_size=image.shape[:2])
|
|
482
532
|
ann = ann.add_labels(labels)
|
|
483
533
|
return ann
|