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,60 +1,401 @@
1
- import functools
2
- import json
3
1
  import time
2
+ import uuid
4
3
  from queue import Queue
5
4
  from threading import Event, Thread
6
- from typing import Any, Dict, List, Optional, Union
5
+ from typing import BinaryIO, Dict, List, Tuple
7
6
 
8
7
  import numpy as np
9
- from fastapi import BackgroundTasks, Form, Request, UploadFile
8
+ from pydantic import ValidationError
10
9
 
11
- import supervisely as sly
12
- import supervisely.nn.inference.tracking.functional as F
10
+ from supervisely._utils import find_value_by_keys
13
11
  from supervisely.annotation.label import Geometry, Label
14
- from supervisely.nn.inference import Inference
15
- from supervisely.nn.inference.tracking.tracker_interface import TrackerInterface
16
- from supervisely.nn.prediction_dto import PredictionSegmentation
12
+ from supervisely.annotation.obj_class import ObjClass
13
+ from supervisely.api.api import Api
14
+ from supervisely.api.module_api import ApiField
15
+ from supervisely.api.video.video_figure_api import FigureInfo
16
+ from supervisely.geometry.bitmap import Bitmap
17
+ from supervisely.geometry.helpers import deserialize_geometry
18
+ from supervisely.geometry.polygon import Polygon
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
+ )
25
+ from supervisely.sly_logger import logger
26
+ from supervisely.task.progress import Progress
27
+
28
+
29
+ class MaskTracking(BaseTracking):
30
+ def _deserialize_geometry(self, data: dict):
31
+ geometry_type_str = data["type"]
32
+ geometry_json = data["data"]
33
+ return deserialize_geometry(geometry_type_str, geometry_json)
17
34
 
35
+ def _inference(self, frames: List[np.ndarray], geometries: List[Geometry]):
36
+ results = [[] for _ in range(len(frames) - 1)]
37
+ for geometry in geometries:
38
+ if not isinstance(geometry, Bitmap) and not isinstance(geometry, Polygon):
39
+ raise TypeError(f"This app does not support {geometry.geometry_name()} tracking")
18
40
 
19
- class MaskTracking(Inference):
20
- def __init__(
21
- self,
22
- model_dir: Optional[str] = None,
23
- custom_inference_settings: Optional[Union[Dict[str, Any], str]] = None,
24
- ):
25
- Inference.__init__(
26
- self,
27
- model_dir,
28
- custom_inference_settings,
29
- sliding_window_mode=None,
30
- use_gui=False,
41
+ # combine several binary masks into one multilabel mask
42
+ i = 0
43
+ label2id = {}
44
+
45
+ original_geometry = geometry.clone()
46
+
47
+ # convert polygon to bitmap
48
+ if isinstance(geometry, Polygon):
49
+ polygon_obj_class = ObjClass("polygon", Polygon)
50
+ polygon_label = Label(geometry, polygon_obj_class)
51
+ bitmap_obj_class = ObjClass("bitmap", Bitmap)
52
+ bitmap_label = polygon_label.convert(bitmap_obj_class)[0]
53
+ geometry = bitmap_label.geometry
54
+ if i == 0:
55
+ multilabel_mask = geometry.data.astype(int)
56
+ multilabel_mask = np.zeros(frames[0].shape, dtype=np.uint8)
57
+ geometry.draw(bitmap=multilabel_mask, color=[1, 1, 1])
58
+ i += 1
59
+ else:
60
+ i += 1
61
+ geometry.draw(bitmap=multilabel_mask, color=[i, i, i])
62
+ label2id[i] = {
63
+ "original_geometry": original_geometry.geometry_name(),
64
+ }
65
+ # run tracker
66
+ tracked_multilabel_masks = self.predict(
67
+ frames=frames, input_mask=multilabel_mask[:, :, 0]
68
+ )
69
+ tracked_multilabel_masks = np.array(tracked_multilabel_masks)
70
+ # decompose multilabel masks into binary masks
71
+ for i in np.unique(tracked_multilabel_masks):
72
+ if i != 0:
73
+ binary_masks = tracked_multilabel_masks == i
74
+ geometry_type = label2id[i]["original_geometry"]
75
+ for j, mask in enumerate(binary_masks[1:]):
76
+ # check if mask is not empty
77
+ if np.any(mask):
78
+ if geometry_type == "polygon":
79
+ bitmap_geometry = Bitmap(mask)
80
+ bitmap_obj_class = ObjClass("bitmap", Bitmap)
81
+ bitmap_label = Label(bitmap_geometry, bitmap_obj_class)
82
+ polygon_obj_class = ObjClass("polygon", Polygon)
83
+ polygon_labels = bitmap_label.convert(polygon_obj_class)
84
+ geometries = [label.geometry for label in polygon_labels]
85
+ else:
86
+ geometries = [Bitmap(mask)]
87
+ results[j].extend(
88
+ [
89
+ {"type": geom.geometry_name(), "data": geom.to_json()}
90
+ for geom in geometries
91
+ ]
92
+ )
93
+ return results
94
+
95
+ def _track(self, api: Api, context: Dict):
96
+ self.video_interface = TrackerInterface(
97
+ context=context,
98
+ api=api,
99
+ load_all_frames=True,
100
+ notify_in_predict=True,
101
+ per_point_polygon_tracking=False,
102
+ frame_loader=self.cache.download_frame,
103
+ frames_loader=self.cache.download_frames,
31
104
  )
105
+ range_of_frames = [
106
+ self.video_interface.frames_indexes[0],
107
+ self.video_interface.frames_indexes[-1],
108
+ ]
109
+
110
+ if self.cache.is_persistent:
111
+ # if cache is persistent, run cache task for whole video
112
+ self.cache.run_cache_task_manually(
113
+ api,
114
+ None,
115
+ video_id=self.video_interface.video_id,
116
+ )
117
+ else:
118
+ # if cache is not persistent, run cache task for range of frames
119
+ self.cache.run_cache_task_manually(
120
+ api,
121
+ [range_of_frames],
122
+ video_id=self.video_interface.video_id,
123
+ )
124
+
125
+ api.logger.info("Starting tracking process")
126
+ # load frames
127
+ frames = self.video_interface.frames
128
+ # combine several binary masks into one multilabel mask
129
+ i = 0
130
+ label2id = {}
131
+
132
+ def _upload_loop(q: Queue, stop_event: Event, video_interface: TrackerInterface):
133
+ try:
134
+ while True:
135
+ items = []
136
+ while not q.empty():
137
+ items.append(q.get_nowait())
138
+ if len(items) > 0:
139
+ video_interface.add_object_geometries_on_frames(*list(zip(*items)))
140
+ continue
141
+ if stop_event.is_set():
142
+ video_interface._notify(True, task="stop tracking")
143
+ return
144
+ time.sleep(1)
145
+ except Exception as e:
146
+ api.logger.error("Error in upload loop: %s", str(e), exc_info=True)
147
+ video_interface._notify(True, task="stop tracking")
148
+ video_interface.global_stop_indicatior = True
149
+ raise
150
+
151
+ upload_queue = Queue()
152
+ stop_upload_event = Event()
153
+ Thread(
154
+ target=_upload_loop,
155
+ args=[upload_queue, stop_upload_event, self.video_interface],
156
+ daemon=True,
157
+ ).start()
32
158
 
33
159
  try:
34
- self.load_on_device(model_dir, "cuda")
35
- except RuntimeError:
36
- self.load_on_device(model_dir, "cpu")
37
- sly.logger.warn("Failed to load model on CUDA device.")
38
-
39
- sly.logger.debug(
40
- "Smart cache params",
41
- extra={
42
- "ttl": sly.env.smart_cache_ttl(),
43
- "maxsize": sly.env.smart_cache_size(),
44
- },
45
- )
160
+ for (fig_id, geometry), obj_id in zip(
161
+ self.video_interface.geometries.items(),
162
+ self.video_interface.object_ids,
163
+ ):
164
+ original_geometry = geometry.clone()
165
+ if not isinstance(geometry, Bitmap) and not isinstance(geometry, Polygon):
166
+ stop_upload_event.set()
167
+ raise TypeError(
168
+ f"This app does not support {geometry.geometry_name()} tracking"
169
+ )
170
+ # convert polygon to bitmap
171
+ if isinstance(geometry, Polygon):
172
+ polygon_obj_class = ObjClass("polygon", Polygon)
173
+ polygon_label = Label(geometry, polygon_obj_class)
174
+ bitmap_obj_class = ObjClass("bitmap", Bitmap)
175
+ bitmap_label = polygon_label.convert(bitmap_obj_class)[0]
176
+ geometry = bitmap_label.geometry
177
+ if i == 0:
178
+ multilabel_mask = geometry.data.astype(int)
179
+ multilabel_mask = np.zeros(frames[0].shape, dtype=np.uint8)
180
+ geometry.draw(bitmap=multilabel_mask, color=[1, 1, 1])
181
+ i += 1
182
+ else:
183
+ i += 1
184
+ geometry.draw(bitmap=multilabel_mask, color=[i, i, i])
185
+ label2id[i] = {
186
+ "fig_id": fig_id,
187
+ "obj_id": obj_id,
188
+ "original_geometry": original_geometry.geometry_name(),
189
+ }
190
+ # run tracker
191
+ tracked_multilabel_masks = self.predict(
192
+ frames=frames, input_mask=multilabel_mask[:, :, 0]
193
+ )
194
+ tracked_multilabel_masks = np.array(tracked_multilabel_masks)
195
+ # decompose multilabel masks into binary masks
196
+ for i in np.unique(tracked_multilabel_masks):
197
+ if i != 0:
198
+ binary_masks = tracked_multilabel_masks == i
199
+ fig_id = label2id[i]["fig_id"]
200
+ obj_id = label2id[i]["obj_id"]
201
+ geometry_type = label2id[i]["original_geometry"]
202
+ for j, mask in enumerate(binary_masks[1:]):
203
+ # check if mask is not empty
204
+ if not np.any(mask):
205
+ api.logger.info(
206
+ f"Skipping empty mask on frame {self.video_interface.frame_index + j + 1}"
207
+ )
208
+ # update progress bar anyway (otherwise it will not be finished)
209
+ self.video_interface._notify(task="add geometry on frame")
210
+ else:
211
+ if geometry_type == "polygon":
212
+ bitmap_geometry = Bitmap(mask)
213
+ bitmap_obj_class = ObjClass("bitmap", Bitmap)
214
+ bitmap_label = Label(bitmap_geometry, bitmap_obj_class)
215
+ polygon_obj_class = ObjClass("polygon", Polygon)
216
+ polygon_labels = bitmap_label.convert(polygon_obj_class)
217
+ geometries = [label.geometry for label in polygon_labels]
218
+ else:
219
+ geometries = [Bitmap(mask)]
220
+ for l, geometry in enumerate(geometries):
221
+ if l == len(geometries) - 1:
222
+ notify = True
223
+ else:
224
+ notify = False
225
+ upload_queue.put(
226
+ (
227
+ geometry,
228
+ obj_id,
229
+ self.video_interface.frames_indexes[j + 1],
230
+ notify,
231
+ )
232
+ )
233
+ if self.video_interface.global_stop_indicatior:
234
+ stop_upload_event.set()
235
+ return
236
+ api.logger.info(f"Figure with id {fig_id} was successfully tracked")
237
+ except Exception:
238
+ stop_upload_event.set()
239
+ raise
240
+ stop_upload_event.set()
241
+
242
+ def _track_async(self, api: Api, context: dict, inference_request_uuid: str = None):
243
+ inference_request = self._inference_requests[inference_request_uuid]
244
+ tracker_interface = TrackerInterfaceV2(api, context, self.cache)
245
+ progress: Progress = inference_request["progress"]
246
+ frames_count = tracker_interface.frames_count
247
+ figures = tracker_interface.figures
248
+ progress_total = frames_count * len(figures)
249
+ progress.total = progress_total
250
+
251
+ def _upload_f(items: List[Tuple[FigureInfo, bool]]):
252
+ with inference_request["lock"]:
253
+ inference_request["pending_results"].extend([item[0] for item in items])
254
+
255
+ def _notify_f(items: List[Tuple[FigureInfo, bool]]):
256
+ items_by_object_id: Dict[int, List[Tuple[FigureInfo, bool]]] = {}
257
+ for item in items:
258
+ items_by_object_id.setdefault(item[0].object_id, []).append(item)
259
+
260
+ for object_id, object_items in items_by_object_id.items():
261
+ frame_range = [
262
+ min(item[0].frame_index for item in object_items),
263
+ max(item[0].frame_index for item in object_items),
264
+ ]
265
+ progress.iters_done_report(sum(1 for item in object_items if item[1]))
266
+ tracker_interface.notify_progress(progress.current, progress.total, frame_range)
46
267
 
47
- def _deserialize_geometry(self, data: dict):
48
- geometry_type_str = data["type"]
49
- geometry_json = data["data"]
50
- return sly.deserialize_geometry(geometry_type_str, geometry_json)
268
+ # run tracker
269
+ frame_index = tracker_interface.frame_index
270
+ direction_n = tracker_interface.direction_n
271
+ api.logger.info("Start tracking.")
272
+ try:
273
+ with tracker_interface(_upload_f, _notify_f):
274
+ # combine several binary masks into one multilabel mask
275
+ i = 0
276
+ label2id = {}
277
+ # load frames
278
+ frames = tracker_interface.load_all_frames()
279
+ frames = [frame.image for frame in frames]
280
+ for figure in figures:
281
+ figure = api.video.figure._convert_json_info(figure)
282
+ fig_id = figure.id
283
+ obj_id = figure.object_id
284
+ geometry = deserialize_geometry(figure.geometry_type, figure.geometry)
285
+ original_geometry = geometry.clone()
286
+ if not isinstance(geometry, (Bitmap, Polygon)):
287
+ raise TypeError(
288
+ f"This app does not support {geometry.geometry_name()} tracking"
289
+ )
290
+ # convert polygon to bitmap
291
+ if isinstance(geometry, Polygon):
292
+ polygon_obj_class = ObjClass("polygon", Polygon)
293
+ polygon_label = Label(geometry, polygon_obj_class)
294
+ bitmap_obj_class = ObjClass("bitmap", Bitmap)
295
+ bitmap_label = polygon_label.convert(bitmap_obj_class)[0]
296
+ geometry = bitmap_label.geometry
297
+ if i == 0:
298
+ multilabel_mask = geometry.data.astype(int)
299
+ multilabel_mask = np.zeros(frames[0].shape, dtype=np.uint8)
300
+ geometry.draw(bitmap=multilabel_mask, color=[1, 1, 1])
301
+ i += 1
302
+ else:
303
+ i += 1
304
+ geometry.draw(bitmap=multilabel_mask, color=[i, i, i])
305
+ label2id[i] = {
306
+ "fig_id": fig_id,
307
+ "obj_id": obj_id,
308
+ "original_geometry": original_geometry.geometry_name(),
309
+ }
310
+ if inference_request["cancel_inference"]:
311
+ return
312
+ if tracker_interface.is_stopped():
313
+ reason = tracker_interface.stop_reason()
314
+ if isinstance(reason, Exception):
315
+ raise reason
316
+ return
317
+
318
+ # predict
319
+ tracked_multilabel_masks = self.predict(
320
+ frames=frames, input_mask=multilabel_mask[:, :, 0]
321
+ )
322
+ tracked_multilabel_masks = np.array(tracked_multilabel_masks)
51
323
 
52
- def get_info(self):
53
- info = super().get_info()
54
- info["task type"] = "tracking"
55
- return info
324
+ # decompose multilabel masks into binary masks
325
+ for i in np.unique(tracked_multilabel_masks):
326
+ if inference_request["cancel_inference"]:
327
+ return
328
+ if tracker_interface.is_stopped():
329
+ reason = tracker_interface.stop_reason()
330
+ if isinstance(reason, Exception):
331
+ raise reason
332
+ return
333
+ if i != 0:
334
+ binary_masks = tracked_multilabel_masks == i
335
+ fig_id = label2id[i]["fig_id"]
336
+ obj_id = label2id[i]["obj_id"]
337
+ geometry_type = label2id[i]["original_geometry"]
338
+ for j, mask in enumerate(binary_masks[1:], 1):
339
+ if inference_request["cancel_inference"]:
340
+ return
341
+ if tracker_interface.is_stopped():
342
+ reason = tracker_interface.stop_reason()
343
+ if isinstance(reason, Exception):
344
+ raise reason
345
+ return
346
+ this_figure_index = frame_index + j * direction_n
347
+ # check if mask is not empty
348
+ if not np.any(mask):
349
+ api.logger.info(f"Skipping empty mask on frame {this_figure_index}")
350
+ # update progress bar anyway (otherwise it will not be finished)
351
+ progress.iter_done_report()
352
+ else:
353
+ if geometry_type == "polygon":
354
+ bitmap_geometry = Bitmap(mask)
355
+ bitmap_obj_class = ObjClass("bitmap", Bitmap)
356
+ bitmap_label = Label(bitmap_geometry, bitmap_obj_class)
357
+ polygon_obj_class = ObjClass("polygon", Polygon)
358
+ polygon_labels = bitmap_label.convert(polygon_obj_class)
359
+ geometries = [label.geometry for label in polygon_labels]
360
+ else:
361
+ geometries = [Bitmap(mask)]
362
+ for l, geometry in enumerate(geometries):
363
+ figure_id = uuid.uuid5(
364
+ namespace=uuid.NAMESPACE_URL, name=f"{time.time()}"
365
+ ).hex
366
+ result_figure = api.video.figure._convert_json_info(
367
+ {
368
+ ApiField.ID: figure_id,
369
+ ApiField.OBJECT_ID: obj_id,
370
+ "meta": {"frame": this_figure_index},
371
+ ApiField.GEOMETRY_TYPE: geometry.geometry_name(),
372
+ ApiField.GEOMETRY: geometry.to_json(),
373
+ ApiField.TRACK_ID: tracker_interface.track_id,
374
+ }
375
+ )
376
+ should_notify = l == len(geometries) - 1
377
+ tracker_interface.add_prediction((result_figure, should_notify))
378
+ api.logger.info(
379
+ "Figure [%d, %d] tracked.",
380
+ i,
381
+ len(figures),
382
+ extra={"figure_id": figure.id},
383
+ )
384
+ except Exception:
385
+ progress.message = "Error occured during tracking"
386
+ raise
387
+ else:
388
+ progress.message = "Ready"
389
+ finally:
390
+ progress.set(current=0, total=1, report=True)
391
+
392
+ # Implement the following methods in the derived class
393
+ def track(self, api: Api, state: Dict, context: Dict):
394
+ fn = self.send_error_data(api, context)(self._track)
395
+ self.schedule_task(fn, api, context)
396
+ return {"message": "Tracking has started."}
56
397
 
57
- def _track_api(self, api: sly.Api, context: dict):
398
+ def track_api(self, api: Api, state: Dict, context: Dict):
58
399
  # unused fields:
59
400
  context["trackId"] = "auto"
60
401
  context["objectIds"] = []
@@ -104,13 +445,13 @@ class MaskTracking(Inference):
104
445
 
105
446
  for input_geom in input_geometries:
106
447
  geometry = self._deserialize_geometry(input_geom)
107
- if not isinstance(geometry, sly.Bitmap) and not isinstance(geometry, sly.Polygon):
448
+ if not isinstance(geometry, Bitmap) and not isinstance(geometry, Polygon):
108
449
  raise TypeError(f"This app does not support {geometry.geometry_name()} tracking")
109
450
  # convert polygon to bitmap
110
- if isinstance(geometry, sly.Polygon):
111
- polygon_obj_class = sly.ObjClass("polygon", sly.Polygon)
112
- polygon_label = sly.Label(geometry, polygon_obj_class)
113
- bitmap_obj_class = sly.ObjClass("bitmap", sly.Bitmap)
451
+ if isinstance(geometry, Polygon):
452
+ polygon_obj_class = ObjClass("polygon", Polygon)
453
+ polygon_label = Label(geometry, polygon_obj_class)
454
+ bitmap_obj_class = ObjClass("bitmap", Bitmap)
114
455
  bitmap_label = polygon_label.convert(bitmap_obj_class)[0]
115
456
  geometry = bitmap_label.geometry
116
457
  if i == 0:
@@ -143,7 +484,7 @@ class MaskTracking(Inference):
143
484
  predictions_for_label.append(None)
144
485
  else:
145
486
  # frame_idx = j + 1
146
- geometry = sly.Bitmap(mask)
487
+ geometry = Bitmap(mask)
147
488
  predictions_for_label.append(
148
489
  {"type": geometry.geometry_name(), "data": geometry.to_json()}
149
490
  )
@@ -153,295 +494,45 @@ class MaskTracking(Inference):
153
494
  predictions = list(map(list, zip(*predictions)))
154
495
  return predictions
155
496
 
156
- def _inference(self, frames: List[np.ndarray], geometries: List[Geometry]):
157
- results = [[] for _ in range(len(frames) - 1)]
158
- for geometry in geometries:
159
- if not isinstance(geometry, sly.Bitmap) and not isinstance(geometry, sly.Polygon):
160
- raise TypeError(f"This app does not support {geometry.geometry_name()} tracking")
161
-
162
- # combine several binary masks into one multilabel mask
163
- i = 0
164
- label2id = {}
165
-
166
- original_geometry = geometry.clone()
167
-
168
- # convert polygon to bitmap
169
- if isinstance(geometry, sly.Polygon):
170
- polygon_obj_class = sly.ObjClass("polygon", sly.Polygon)
171
- polygon_label = sly.Label(geometry, polygon_obj_class)
172
- bitmap_obj_class = sly.ObjClass("bitmap", sly.Bitmap)
173
- bitmap_label = polygon_label.convert(bitmap_obj_class)[0]
174
- geometry = bitmap_label.geometry
175
- if i == 0:
176
- multilabel_mask = geometry.data.astype(int)
177
- multilabel_mask = np.zeros(frames[0].shape, dtype=np.uint8)
178
- geometry.draw(bitmap=multilabel_mask, color=[1, 1, 1])
179
- i += 1
180
- else:
181
- i += 1
182
- geometry.draw(bitmap=multilabel_mask, color=[i, i, i])
183
- label2id[i] = {
184
- "original_geometry": original_geometry.geometry_name(),
185
- }
186
- # run tracker
187
- tracked_multilabel_masks = self.predict(
188
- frames=frames, input_mask=multilabel_mask[:, :, 0]
189
- )
190
- tracked_multilabel_masks = np.array(tracked_multilabel_masks)
191
- # decompose multilabel masks into binary masks
192
- for i in np.unique(tracked_multilabel_masks):
193
- if i != 0:
194
- binary_masks = tracked_multilabel_masks == i
195
- geometry_type = label2id[i]["original_geometry"]
196
- for j, mask in enumerate(binary_masks[1:]):
197
- # check if mask is not empty
198
- if np.any(mask):
199
- if geometry_type == "polygon":
200
- bitmap_geometry = sly.Bitmap(mask)
201
- bitmap_obj_class = sly.ObjClass("bitmap", sly.Bitmap)
202
- bitmap_label = sly.Label(bitmap_geometry, bitmap_obj_class)
203
- polygon_obj_class = sly.ObjClass("polygon", sly.Polygon)
204
- polygon_labels = bitmap_label.convert(polygon_obj_class)
205
- geometries = [label.geometry for label in polygon_labels]
206
- else:
207
- geometries = [sly.Bitmap(mask)]
208
- results[j].extend(
209
- [
210
- {"type": geom.geometry_name(), "data": geom.to_json()}
211
- for geom in geometries
212
- ]
213
- )
214
- return results
215
-
216
- def _track_api_cached(self, request: Request, context: dict):
217
- sly.logger.info(f"Start tracking with settings: {context}.")
218
- video_id = context["video_id"]
219
- frame_indexes = list(
220
- range(context["frame_index"], context["frame_index"] + context["frames"] + 1)
221
- )
222
- geometries = map(self._deserialize_geometry, context["input_geometries"])
223
- frames = self.cache.get_frames_from_cache(video_id, frame_indexes)
224
- return self._inference(frames, geometries)
225
-
226
- def _track_api_files(
227
- self, request: Request, files: List[UploadFile], settings: str = Form("{}")
497
+ def track_api_files(
498
+ self,
499
+ files: List[BinaryIO],
500
+ settings: Dict,
228
501
  ):
229
- state = json.loads(settings)
230
- sly.logger.info(f"Start tracking with settings: {state}.")
231
- video_id = state["video_id"]
502
+ logger.info(f"Start tracking with settings:", extra={"settings": settings})
503
+ frame_index = find_value_by_keys(settings, ["frame_index", "frameIndex"])
504
+ frames_count = find_value_by_keys(settings, ["frames", "framesCount"])
505
+ input_geometries = find_value_by_keys(settings, ["input_geometries", "inputGeometries"])
506
+ direction = settings.get("direction", "forward")
507
+ direction_n = 1 if direction == "forward" else -1
232
508
  frame_indexes = list(
233
- range(state["frame_index"], state["frame_index"] + state["frames"] + 1)
509
+ range(frame_index, frame_index + frames_count * direction_n + direction_n, direction_n)
234
510
  )
235
- geometries = map(self._deserialize_geometry, state["input_geometries"])
511
+ geometries = map(self._deserialize_geometry, input_geometries)
236
512
  frames = []
237
513
  for file, frame_idx in zip(files, frame_indexes):
238
- img_bytes = file.file.read()
239
- frame = sly.image.read_bytes(img_bytes)
514
+ img_bytes = file.read()
515
+ frame = sly_image.read_bytes(img_bytes)
240
516
  frames.append(frame)
241
- sly.logger.info("Start tracking.")
517
+ logger.info("Start tracking.")
242
518
  return self._inference(frames, geometries)
243
519
 
244
- def serve(self):
245
- super().serve()
246
- server = self._app.get_server()
247
- self.cache.add_cache_endpoint(server)
248
- self.cache.add_cache_files_endpoint(server)
249
-
250
- @server.post("/track")
251
- def start_track(request: Request, task: BackgroundTasks):
252
- task.add_task(track, request)
253
- return {"message": "Track task started."}
254
-
255
- @server.post("/track-api")
256
- def track_api(request: Request):
257
- return self._track_api(request.state.api, request.state.context)
258
-
259
- @server.post("/track-api-files")
260
- def track_api_files(
261
- request: Request,
262
- files: List[UploadFile],
263
- settings: str = Form("{}"),
264
- ):
265
- return self._track_api_files(request, files, settings)
266
-
267
- def send_error_data(func):
268
- @functools.wraps(func)
269
- def wrapper(*args, **kwargs):
270
- value = None
271
- try:
272
- value = func(*args, **kwargs)
273
- except Exception as exc:
274
- # print("An error occured:")
275
- # print(traceback.format_exc())
276
- request: Request = args[0]
277
- context = request.state.context
278
- api: sly.Api = request.state.api
279
- track_id = context["trackId"]
280
- api.logger.error(f"An error occured: {repr(exc)}")
281
-
282
- api.post(
283
- "videos.notify-annotation-tool",
284
- data={
285
- "type": "videos:tracking-error",
286
- "data": {
287
- "trackId": track_id,
288
- "error": {"message": repr(exc)},
289
- },
290
- },
291
- )
292
- return value
293
-
294
- return wrapper
295
-
296
- @send_error_data
297
- def track(request: Request):
298
- context = request.state.context
299
- api: sly.Api = request.state.api
300
-
301
- self.video_interface = TrackerInterface(
302
- context=context,
303
- api=api,
304
- load_all_frames=True,
305
- notify_in_predict=True,
306
- per_point_polygon_tracking=False,
307
- frame_loader=self.cache.download_frame,
308
- frames_loader=self.cache.download_frames,
520
+ def track_async(self, api: Api, state: Dict, context: Dict):
521
+ batch_size = context.get("batch_size", self.get_batch_size())
522
+ if self.max_batch_size is not None and batch_size > self.max_batch_size:
523
+ raise ValidationError(
524
+ f"Batch size should be less than or equal to {self.max_batch_size} for this model."
309
525
  )
310
- range_of_frames = [
311
- self.video_interface.frames_indexes[0],
312
- self.video_interface.frames_indexes[-1],
313
- ]
314
-
315
- if self.cache.is_persistent:
316
- # if cache is persistent, run cache task for whole video
317
- self.cache.run_cache_task_manually(
318
- api,
319
- None,
320
- video_id=self.video_interface.video_id,
321
- )
322
- else:
323
- # if cache is not persistent, run cache task for range of frames
324
- self.cache.run_cache_task_manually(
325
- api,
326
- [range_of_frames],
327
- video_id=self.video_interface.video_id,
328
- )
329
526
 
330
- api.logger.info("Starting tracking process")
331
- # load frames
332
- frames = self.video_interface.frames
333
- # combine several binary masks into one multilabel mask
334
- i = 0
335
- label2id = {}
527
+ inference_request_uuid = uuid.uuid5(namespace=uuid.NAMESPACE_URL, name=f"{time.time()}").hex
528
+ fn = self.send_error_data(api, context)(self._track_async)
529
+ self.schedule_task(fn, api, context, inference_request_uuid=inference_request_uuid)
336
530
 
337
- def _upload_loop(q: Queue, stop_event: Event, video_interface: TrackerInterface):
338
- try:
339
- while True:
340
- items = []
341
- while not q.empty():
342
- items.append(q.get_nowait())
343
- if len(items) > 0:
344
- video_interface.add_object_geometries_on_frames(*list(zip(*items)))
345
- continue
346
- if stop_event.is_set():
347
- video_interface._notify(True, task="stop tracking")
348
- return
349
- time.sleep(1)
350
- except Exception as e:
351
- api.logger.error("Error in upload loop: %s", str(e), exc_info=True)
352
- video_interface._notify(True, task="stop tracking")
353
- video_interface.global_stop_indicatior = True
354
- raise
355
-
356
- upload_queue = Queue()
357
- stop_upload_event = Event()
358
- Thread(
359
- target=_upload_loop,
360
- args=[upload_queue, stop_upload_event, self.video_interface],
361
- daemon=True,
362
- ).start()
363
-
364
- try:
365
- for (fig_id, geometry), obj_id in zip(
366
- self.video_interface.geometries.items(),
367
- self.video_interface.object_ids,
368
- ):
369
- original_geometry = geometry.clone()
370
- if not isinstance(geometry, sly.Bitmap) and not isinstance(
371
- geometry, sly.Polygon
372
- ):
373
- stop_upload_event.set()
374
- raise TypeError(
375
- f"This app does not support {geometry.geometry_name()} tracking"
376
- )
377
- # convert polygon to bitmap
378
- if isinstance(geometry, sly.Polygon):
379
- polygon_obj_class = sly.ObjClass("polygon", sly.Polygon)
380
- polygon_label = sly.Label(geometry, polygon_obj_class)
381
- bitmap_obj_class = sly.ObjClass("bitmap", sly.Bitmap)
382
- bitmap_label = polygon_label.convert(bitmap_obj_class)[0]
383
- geometry = bitmap_label.geometry
384
- if i == 0:
385
- multilabel_mask = geometry.data.astype(int)
386
- multilabel_mask = np.zeros(frames[0].shape, dtype=np.uint8)
387
- geometry.draw(bitmap=multilabel_mask, color=[1, 1, 1])
388
- i += 1
389
- else:
390
- i += 1
391
- geometry.draw(bitmap=multilabel_mask, color=[i, i, i])
392
- label2id[i] = {
393
- "fig_id": fig_id,
394
- "obj_id": obj_id,
395
- "original_geometry": original_geometry.geometry_name(),
396
- }
397
- # run tracker
398
- tracked_multilabel_masks = self.predict(
399
- frames=frames, input_mask=multilabel_mask[:, :, 0]
400
- )
401
- tracked_multilabel_masks = np.array(tracked_multilabel_masks)
402
- # decompose multilabel masks into binary masks
403
- for i in np.unique(tracked_multilabel_masks):
404
- if i != 0:
405
- binary_masks = tracked_multilabel_masks == i
406
- fig_id = label2id[i]["fig_id"]
407
- obj_id = label2id[i]["obj_id"]
408
- geometry_type = label2id[i]["original_geometry"]
409
- for j, mask in enumerate(binary_masks[1:]):
410
- # check if mask is not empty
411
- if not np.any(mask):
412
- api.logger.info(
413
- f"Skipping empty mask on frame {self.video_interface.frame_index + j + 1}"
414
- )
415
- # update progress bar anyway (otherwise it will not be finished)
416
- self.video_interface._notify(task="add geometry on frame")
417
- else:
418
- if geometry_type == "polygon":
419
- bitmap_geometry = sly.Bitmap(mask)
420
- bitmap_obj_class = sly.ObjClass("bitmap", sly.Bitmap)
421
- bitmap_label = sly.Label(bitmap_geometry, bitmap_obj_class)
422
- polygon_obj_class = sly.ObjClass("polygon", sly.Polygon)
423
- polygon_labels = bitmap_label.convert(polygon_obj_class)
424
- geometries = [label.geometry for label in polygon_labels]
425
- else:
426
- geometries = [sly.Bitmap(mask)]
427
- for l, geometry in enumerate(geometries):
428
- if l == len(geometries) - 1:
429
- notify = True
430
- else:
431
- notify = False
432
- upload_queue.put(
433
- (
434
- geometry,
435
- obj_id,
436
- self.video_interface.frames_indexes[j + 1],
437
- notify,
438
- )
439
- )
440
- if self.video_interface.global_stop_indicatior:
441
- stop_upload_event.set()
442
- return
443
- api.logger.info(f"Figure with id {fig_id} was successfully tracked")
444
- except Exception:
445
- stop_upload_event.set()
446
- raise
447
- stop_upload_event.set()
531
+ logger.debug(
532
+ "Inference has scheduled from 'track_async' endpoint",
533
+ extra={"inference_request_uuid": inference_request_uuid},
534
+ )
535
+ return {
536
+ "message": "Inference has started.",
537
+ "inference_request_uuid": inference_request_uuid,
538
+ }