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.

@@ -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, Union
6
+ from typing import Any, BinaryIO, Dict, List, Optional
9
7
 
10
8
  import numpy as np
11
- from fastapi import BackgroundTasks, Form, Request, UploadFile
9
+ from pydantic import ValidationError
12
10
 
13
- import supervisely as sly
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.nn.inference import Inference
17
- from supervisely.nn.inference.tracking.tracker_interface import TrackerInterface
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(Inference):
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 sly.deserialize_geometry(geometry_type_str, geometry_json)
34
+ return deserialize_geometry(geometry_type_str, geometry_json)
55
35
 
56
- def _track(self, api: sly.Api, context: dict, notify_annotation_tool: bool):
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, sly.Rectangle):
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: sly.Api, context: dict, request_uuid: str = None):
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 = sly.Rectangle.from_json(input_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, sly.Rectangle):
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": sly.Rectangle.geometry_name(), "data": sly_pred_geometry.to_json()}
281
+ {"type": Rectangle.geometry_name(), "data": sly_pred_geometry.to_json()}
302
282
  )
303
283
  return results
304
284
 
305
- def _track_api_files(
306
- self, request: Request, files: List[UploadFile], settings: str = Form("{}")
307
- ):
308
- state = json.loads(settings)
309
- sly.logger.info(f"Start tracking with settings: {state}.")
310
- video_id = state["video_id"]
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(state["frame_index"], state["frame_index"] + state["frames"] + 1)
410
+ range(settings["frame_index"], settings["frame_index"] + settings["frames"] + 1)
313
411
  )
314
- geometries = map(self._deserialize_geometry, state["input_geometries"])
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.file.read()
318
- frame = sly.image.read_bytes(img_bytes)
415
+ img_bytes = file.read()
416
+ frame = sly_image.read_bytes(img_bytes)
319
417
  frames.append(frame)
320
- sly.logger.info("Start tracking.")
321
- return self._inference(frames, geometries, state)
322
-
323
- def serve(self):
324
- super().serve()
325
- server = self._app.get_server()
326
- self.cache.add_cache_endpoint(server)
327
- self.cache.add_cache_files_endpoint(server)
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
- @server.post("/track")
363
- def start_track(request: Request, task: BackgroundTasks):
364
- task.add_task(track, request)
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
- @server.post("/track-api-files")
384
- def track_api_files(
385
- request: Request,
386
- files: List[UploadFile],
387
- settings: str = Form("{}"),
388
- ):
389
- return self._track_api_files(request, files, settings)
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) -> sly.Rectangle:
496
+ def _to_sly_geometry(self, dto: PredictionBBox) -> Rectangle:
447
497
  top, left, bottom, right = dto.bbox_tlbr
448
- geometry = sly.Rectangle(top=top, left=left, bottom=bottom, right=right)
498
+ geometry = Rectangle(top=top, left=left, bottom=bottom, right=right)
449
499
  return geometry
450
500
 
451
- def _create_label(self, dto: PredictionBBox) -> sly.Rectangle:
501
+ def _create_label(self, dto: PredictionBBox) -> Rectangle:
452
502
  geometry = self._to_sly_geometry(dto)
453
- return Label(geometry, sly.ObjClass("", sly.Rectangle))
503
+ return Label(geometry, ObjClass("", Rectangle))
454
504
 
455
505
  def _get_obj_class_shape(self):
456
- return sly.Rectangle
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
- ) -> sly.Annotation:
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 = sly.Annotation(img_size=image.shape[:2])
531
+ ann = Annotation(img_size=image.shape[:2])
482
532
  ann = ann.add_labels(labels)
483
533
  return ann