supervisely 6.73.410__py3-none-any.whl → 6.73.470__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of supervisely might be problematic. Click here for more details.

Files changed (190) hide show
  1. supervisely/__init__.py +136 -1
  2. supervisely/_utils.py +81 -0
  3. supervisely/annotation/json_geometries_map.py +2 -0
  4. supervisely/annotation/label.py +80 -3
  5. supervisely/api/annotation_api.py +9 -9
  6. supervisely/api/api.py +67 -43
  7. supervisely/api/app_api.py +72 -5
  8. supervisely/api/dataset_api.py +108 -33
  9. supervisely/api/entity_annotation/figure_api.py +113 -49
  10. supervisely/api/image_api.py +82 -0
  11. supervisely/api/module_api.py +10 -0
  12. supervisely/api/nn/deploy_api.py +15 -9
  13. supervisely/api/nn/ecosystem_models_api.py +201 -0
  14. supervisely/api/nn/neural_network_api.py +12 -3
  15. supervisely/api/pointcloud/pointcloud_api.py +38 -0
  16. supervisely/api/pointcloud/pointcloud_episode_annotation_api.py +3 -0
  17. supervisely/api/project_api.py +213 -6
  18. supervisely/api/task_api.py +11 -1
  19. supervisely/api/video/video_annotation_api.py +4 -2
  20. supervisely/api/video/video_api.py +79 -1
  21. supervisely/api/video/video_figure_api.py +24 -11
  22. supervisely/api/volume/volume_api.py +38 -0
  23. supervisely/app/__init__.py +1 -1
  24. supervisely/app/content.py +14 -6
  25. supervisely/app/fastapi/__init__.py +1 -0
  26. supervisely/app/fastapi/custom_static_files.py +1 -1
  27. supervisely/app/fastapi/multi_user.py +88 -0
  28. supervisely/app/fastapi/subapp.py +175 -42
  29. supervisely/app/fastapi/templating.py +1 -1
  30. supervisely/app/fastapi/websocket.py +77 -9
  31. supervisely/app/singleton.py +21 -0
  32. supervisely/app/v1/app_service.py +18 -2
  33. supervisely/app/v1/constants.py +7 -1
  34. supervisely/app/widgets/__init__.py +11 -1
  35. supervisely/app/widgets/agent_selector/template.html +1 -0
  36. supervisely/app/widgets/card/card.py +20 -0
  37. supervisely/app/widgets/dataset_thumbnail/dataset_thumbnail.py +11 -2
  38. supervisely/app/widgets/dataset_thumbnail/template.html +3 -1
  39. supervisely/app/widgets/deploy_model/deploy_model.py +750 -0
  40. supervisely/app/widgets/dialog/dialog.py +12 -0
  41. supervisely/app/widgets/dialog/template.html +2 -1
  42. supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
  43. supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
  44. supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
  45. supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
  46. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +195 -0
  47. supervisely/app/widgets/experiment_selector/experiment_selector.py +454 -263
  48. supervisely/app/widgets/fast_table/fast_table.py +713 -126
  49. supervisely/app/widgets/fast_table/script.js +492 -95
  50. supervisely/app/widgets/fast_table/style.css +54 -0
  51. supervisely/app/widgets/fast_table/template.html +45 -5
  52. supervisely/app/widgets/heatmap/__init__.py +0 -0
  53. supervisely/app/widgets/heatmap/heatmap.py +523 -0
  54. supervisely/app/widgets/heatmap/script.js +378 -0
  55. supervisely/app/widgets/heatmap/style.css +227 -0
  56. supervisely/app/widgets/heatmap/template.html +21 -0
  57. supervisely/app/widgets/input_tag/input_tag.py +102 -15
  58. supervisely/app/widgets/input_tag_list/__init__.py +0 -0
  59. supervisely/app/widgets/input_tag_list/input_tag_list.py +274 -0
  60. supervisely/app/widgets/input_tag_list/template.html +70 -0
  61. supervisely/app/widgets/radio_table/radio_table.py +10 -2
  62. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  63. supervisely/app/widgets/radio_tabs/template.html +1 -0
  64. supervisely/app/widgets/select/select.py +6 -4
  65. supervisely/app/widgets/select_dataset/select_dataset.py +6 -0
  66. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +83 -7
  67. supervisely/app/widgets/table/table.py +68 -13
  68. supervisely/app/widgets/tabs/tabs.py +22 -6
  69. supervisely/app/widgets/tabs/template.html +5 -1
  70. supervisely/app/widgets/transfer/style.css +3 -0
  71. supervisely/app/widgets/transfer/template.html +3 -1
  72. supervisely/app/widgets/transfer/transfer.py +48 -45
  73. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  74. supervisely/convert/image/csv/csv_converter.py +24 -15
  75. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +43 -41
  76. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +75 -51
  77. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +137 -124
  78. supervisely/convert/video/video_converter.py +2 -2
  79. supervisely/geometry/polyline_3d.py +110 -0
  80. supervisely/io/env.py +161 -1
  81. supervisely/nn/artifacts/__init__.py +1 -1
  82. supervisely/nn/artifacts/artifacts.py +10 -2
  83. supervisely/nn/artifacts/detectron2.py +1 -0
  84. supervisely/nn/artifacts/hrda.py +1 -0
  85. supervisely/nn/artifacts/mmclassification.py +20 -0
  86. supervisely/nn/artifacts/mmdetection.py +5 -3
  87. supervisely/nn/artifacts/mmsegmentation.py +1 -0
  88. supervisely/nn/artifacts/ritm.py +1 -0
  89. supervisely/nn/artifacts/rtdetr.py +1 -0
  90. supervisely/nn/artifacts/unet.py +1 -0
  91. supervisely/nn/artifacts/utils.py +3 -0
  92. supervisely/nn/artifacts/yolov5.py +2 -0
  93. supervisely/nn/artifacts/yolov8.py +1 -0
  94. supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
  95. supervisely/nn/experiments.py +9 -0
  96. supervisely/nn/inference/cache.py +37 -17
  97. supervisely/nn/inference/gui/serving_gui_template.py +39 -13
  98. supervisely/nn/inference/inference.py +953 -211
  99. supervisely/nn/inference/inference_request.py +15 -8
  100. supervisely/nn/inference/instance_segmentation/instance_segmentation.py +1 -0
  101. supervisely/nn/inference/object_detection/object_detection.py +1 -0
  102. supervisely/nn/inference/predict_app/__init__.py +0 -0
  103. supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
  104. supervisely/nn/inference/predict_app/gui/classes_selector.py +160 -0
  105. supervisely/nn/inference/predict_app/gui/gui.py +915 -0
  106. supervisely/nn/inference/predict_app/gui/input_selector.py +344 -0
  107. supervisely/nn/inference/predict_app/gui/model_selector.py +77 -0
  108. supervisely/nn/inference/predict_app/gui/output_selector.py +179 -0
  109. supervisely/nn/inference/predict_app/gui/preview.py +93 -0
  110. supervisely/nn/inference/predict_app/gui/settings_selector.py +881 -0
  111. supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
  112. supervisely/nn/inference/predict_app/gui/utils.py +399 -0
  113. supervisely/nn/inference/predict_app/predict_app.py +176 -0
  114. supervisely/nn/inference/session.py +47 -39
  115. supervisely/nn/inference/tracking/bbox_tracking.py +5 -1
  116. supervisely/nn/inference/tracking/point_tracking.py +5 -1
  117. supervisely/nn/inference/tracking/tracker_interface.py +4 -0
  118. supervisely/nn/inference/uploader.py +9 -5
  119. supervisely/nn/model/model_api.py +44 -22
  120. supervisely/nn/model/prediction.py +15 -1
  121. supervisely/nn/model/prediction_session.py +70 -14
  122. supervisely/nn/prediction_dto.py +7 -0
  123. supervisely/nn/tracker/__init__.py +6 -8
  124. supervisely/nn/tracker/base_tracker.py +54 -0
  125. supervisely/nn/tracker/botsort/__init__.py +1 -0
  126. supervisely/nn/tracker/botsort/botsort_config.yaml +30 -0
  127. supervisely/nn/tracker/botsort/osnet_reid/__init__.py +0 -0
  128. supervisely/nn/tracker/botsort/osnet_reid/osnet.py +566 -0
  129. supervisely/nn/tracker/botsort/osnet_reid/osnet_reid_interface.py +88 -0
  130. supervisely/nn/tracker/botsort/tracker/__init__.py +0 -0
  131. supervisely/nn/tracker/{bot_sort → botsort/tracker}/basetrack.py +1 -2
  132. supervisely/nn/tracker/{utils → botsort/tracker}/gmc.py +51 -59
  133. supervisely/nn/tracker/{deep_sort/deep_sort → botsort/tracker}/kalman_filter.py +71 -33
  134. supervisely/nn/tracker/botsort/tracker/matching.py +202 -0
  135. supervisely/nn/tracker/{bot_sort/bot_sort.py → botsort/tracker/mc_bot_sort.py} +68 -81
  136. supervisely/nn/tracker/botsort_tracker.py +273 -0
  137. supervisely/nn/tracker/calculate_metrics.py +264 -0
  138. supervisely/nn/tracker/utils.py +273 -0
  139. supervisely/nn/tracker/visualize.py +520 -0
  140. supervisely/nn/training/gui/gui.py +152 -49
  141. supervisely/nn/training/gui/hyperparameters_selector.py +1 -1
  142. supervisely/nn/training/gui/model_selector.py +8 -6
  143. supervisely/nn/training/gui/train_val_splits_selector.py +144 -71
  144. supervisely/nn/training/gui/training_artifacts.py +3 -1
  145. supervisely/nn/training/train_app.py +225 -46
  146. supervisely/project/pointcloud_episode_project.py +12 -8
  147. supervisely/project/pointcloud_project.py +12 -8
  148. supervisely/project/project.py +221 -75
  149. supervisely/template/experiment/experiment.html.jinja +105 -55
  150. supervisely/template/experiment/experiment_generator.py +258 -112
  151. supervisely/template/experiment/header.html.jinja +31 -13
  152. supervisely/template/experiment/sly-style.css +7 -2
  153. supervisely/versions.json +3 -1
  154. supervisely/video/sampling.py +42 -20
  155. supervisely/video/video.py +41 -12
  156. supervisely/video_annotation/video_figure.py +38 -4
  157. supervisely/volume/stl_converter.py +2 -0
  158. supervisely/worker_api/agent_rpc.py +24 -1
  159. supervisely/worker_api/rpc_servicer.py +31 -7
  160. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/METADATA +22 -14
  161. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/RECORD +167 -148
  162. supervisely_lib/__init__.py +6 -1
  163. supervisely/app/widgets/experiment_selector/style.css +0 -27
  164. supervisely/app/widgets/experiment_selector/template.html +0 -61
  165. supervisely/nn/tracker/bot_sort/__init__.py +0 -21
  166. supervisely/nn/tracker/bot_sort/fast_reid_interface.py +0 -152
  167. supervisely/nn/tracker/bot_sort/matching.py +0 -127
  168. supervisely/nn/tracker/bot_sort/sly_tracker.py +0 -401
  169. supervisely/nn/tracker/deep_sort/__init__.py +0 -6
  170. supervisely/nn/tracker/deep_sort/deep_sort/__init__.py +0 -1
  171. supervisely/nn/tracker/deep_sort/deep_sort/detection.py +0 -49
  172. supervisely/nn/tracker/deep_sort/deep_sort/iou_matching.py +0 -81
  173. supervisely/nn/tracker/deep_sort/deep_sort/linear_assignment.py +0 -202
  174. supervisely/nn/tracker/deep_sort/deep_sort/nn_matching.py +0 -176
  175. supervisely/nn/tracker/deep_sort/deep_sort/track.py +0 -166
  176. supervisely/nn/tracker/deep_sort/deep_sort/tracker.py +0 -145
  177. supervisely/nn/tracker/deep_sort/deep_sort.py +0 -301
  178. supervisely/nn/tracker/deep_sort/generate_clip_detections.py +0 -90
  179. supervisely/nn/tracker/deep_sort/preprocessing.py +0 -70
  180. supervisely/nn/tracker/deep_sort/sly_tracker.py +0 -273
  181. supervisely/nn/tracker/tracker.py +0 -285
  182. supervisely/nn/tracker/utils/kalman_filter.py +0 -492
  183. supervisely/nn/tracking/__init__.py +0 -1
  184. supervisely/nn/tracking/boxmot.py +0 -114
  185. supervisely/nn/tracking/tracking.py +0 -24
  186. /supervisely/{nn/tracker/utils → app/widgets/deploy_model}/__init__.py +0 -0
  187. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/LICENSE +0 -0
  188. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/WHEEL +0 -0
  189. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/entry_points.txt +0 -0
  190. {supervisely-6.73.410.dist-info → supervisely-6.73.470.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,273 @@
1
+ from typing import List, Union, Dict, Tuple
2
+ from pathlib import Path
3
+ from collections import defaultdict
4
+ import numpy as np
5
+
6
+ import supervisely as sly
7
+ from supervisely.nn.model.prediction import Prediction
8
+ from supervisely.annotation.label import LabelingStatus
9
+ from supervisely import VideoAnnotation
10
+ from supervisely import logger
11
+
12
+
13
+ def predictions_to_video_annotation(
14
+ predictions: List[Prediction],
15
+ ) -> VideoAnnotation:
16
+ """
17
+ Convert list of Prediction objects to VideoAnnotation.
18
+
19
+ Args:
20
+ predictions: List of Prediction objects, one per frame
21
+
22
+ Returns:
23
+ VideoAnnotation object with tracked objects
24
+
25
+ """
26
+
27
+ if not predictions:
28
+ raise ValueError("Empty predictions list provided")
29
+
30
+ frame_shape = predictions[0].annotation.img_size
31
+ img_h, img_w = frame_shape
32
+ video_objects = {}
33
+ frames = []
34
+
35
+ for pred in predictions:
36
+ frame_figures = []
37
+ frame_idx = pred.frame_index
38
+
39
+ # Get data using public properties
40
+ boxes = pred.boxes # Public property - np.array (N, 4) in tlbr format
41
+ classes = pred.classes # Public property - list of class names
42
+ track_ids = pred.track_ids # Public property - can be None
43
+
44
+ # Skip frame if no detections
45
+ if len(boxes) == 0:
46
+ frames.append(sly.Frame(frame_idx, []))
47
+ continue
48
+
49
+ for bbox, class_name, track_id in zip(boxes, classes, track_ids):
50
+ # Clip bbox to image boundaries
51
+ # Note: pred.boxes returns tlbr format (top, left, bottom, right)
52
+ top, left, bottom, right = bbox
53
+ dims = np.array([img_h, img_w, img_h, img_w]) - 1
54
+ top, left, bottom, right = np.clip([top, left, bottom, right], 0, dims)
55
+
56
+ # Convert to integer coordinates
57
+ top, left, bottom, right = int(top), int(left), int(bottom), int(right)
58
+
59
+ # Get or create VideoObject
60
+ if track_id not in video_objects:
61
+ # Find obj_class from prediction annotation
62
+ obj_class = None
63
+ for label in pred.annotation.labels:
64
+ if label.obj_class.name == class_name:
65
+ obj_class = label.obj_class
66
+ break
67
+
68
+ if obj_class is None:
69
+ # Create obj_class if not found (fallback)
70
+ obj_class = sly.ObjClass(class_name, sly.Rectangle)
71
+
72
+ video_objects[track_id] = sly.VideoObject(obj_class)
73
+
74
+ video_object = video_objects[track_id]
75
+ rect = sly.Rectangle(top=top, left=left, bottom=bottom, right=right)
76
+ frame_figures.append(sly.VideoFigure(video_object, rect, frame_idx, track_id=str(track_id), status=LabelingStatus.AUTO))
77
+
78
+ frames.append(sly.Frame(frame_idx, frame_figures))
79
+
80
+ objects = list(video_objects.values())
81
+ return VideoAnnotation(
82
+ img_size=frame_shape,
83
+ frames_count=len(predictions),
84
+ objects=sly.VideoObjectCollection(objects),
85
+ frames=sly.FrameCollection(frames)
86
+ )
87
+
88
+ def video_annotation_to_mot(
89
+ annotation: VideoAnnotation,
90
+ output_path: Union[str, Path] = None,
91
+ class_to_id_mapping: Dict[str, int] = None
92
+ ) -> Union[str, List[str]]:
93
+ """
94
+ Convert Supervisely VideoAnnotation to MOT format.
95
+ MOT format: frame_id,track_id,left,top,width,height,confidence,class_id,visibility
96
+ """
97
+ mot_lines = []
98
+
99
+ # Create default class mapping if not provided
100
+ if class_to_id_mapping is None:
101
+ unique_classes = set()
102
+ for frame in annotation.frames:
103
+ for figure in frame.figures:
104
+ unique_classes.add(figure.video_object.obj_class.name)
105
+ class_to_id_mapping = {cls_name: idx + 1 for idx, cls_name in enumerate(sorted(unique_classes))}
106
+
107
+ # Extract tracks
108
+ for frame in annotation.frames:
109
+ frame_id = frame.index + 1 # MOT uses 1-based frame indexing
110
+
111
+ for figure in frame.figures:
112
+ # Get track ID from VideoFigure.track_id (official API)
113
+ if figure.track_id is not None:
114
+ track_id = int(figure.track_id)
115
+ else:
116
+ track_id = figure.video_object.key().int
117
+
118
+ # Get bounding box
119
+ if isinstance(figure.geometry, sly.Rectangle):
120
+ bbox = figure.geometry
121
+ else:
122
+ bbox = figure.geometry.to_bbox()
123
+
124
+ left = bbox.left
125
+ top = bbox.top
126
+ width = bbox.width
127
+ height = bbox.height
128
+
129
+ # Get class ID
130
+ class_name = figure.video_object.obj_class.name
131
+ class_id = class_to_id_mapping.get(class_name, 1)
132
+
133
+ # Get confidence (default)
134
+ confidence = 1.0
135
+
136
+ # Visibility (assume visible)
137
+ visibility = 1
138
+
139
+ # Create MOT line
140
+ mot_line = f"{frame_id},{track_id},{left:.2f},{top:.2f},{width:.2f},{height:.2f},{confidence:.3f},{class_id},{visibility}"
141
+ mot_lines.append(mot_line)
142
+
143
+ # Save to file if path provided
144
+ if output_path:
145
+ output_path = Path(output_path)
146
+ output_path.parent.mkdir(parents=True, exist_ok=True)
147
+
148
+ with open(output_path, 'w') as f:
149
+ for line in mot_lines:
150
+ f.write(line + '\n')
151
+
152
+ logger.info(f"Saved MOT format to: {output_path} ({len(mot_lines)} detections)")
153
+ return str(output_path)
154
+
155
+ return mot_lines
156
+
157
+ def mot_to_video_annotation(
158
+ mot_file_path: Union[str, Path],
159
+ img_size: Tuple[int, int] = (1080, 1920),
160
+ class_mapping: Dict[int, str] = None,
161
+ default_class_name: str = "person"
162
+ ) -> VideoAnnotation:
163
+ """
164
+ Convert MOT format tracking data to Supervisely VideoAnnotation.
165
+ MOT format: frame_id,track_id,left,top,width,height,confidence,class_id,visibility
166
+ """
167
+ mot_file_path = Path(mot_file_path)
168
+
169
+ if not mot_file_path.exists():
170
+ raise FileNotFoundError(f"MOT file not found: {mot_file_path}")
171
+
172
+ logger.info(f"Loading MOT data from: {mot_file_path}")
173
+ logger.info(f"Image size: {img_size} (height, width)")
174
+
175
+ # Default class mapping
176
+ if class_mapping is None:
177
+ class_mapping = {1: default_class_name}
178
+
179
+ # Parse MOT file
180
+ video_objects = {} # track_id -> VideoObject
181
+ frames_data = defaultdict(list) # frame_idx -> list of figures
182
+ max_frame_idx = 0
183
+ img_h, img_w = img_size
184
+
185
+ with open(mot_file_path, 'r') as f:
186
+ for line_num, line in enumerate(f, 1):
187
+ line = line.strip()
188
+ if not line or line.startswith('#'):
189
+ continue
190
+
191
+ try:
192
+ parts = line.split(',')
193
+ if len(parts) < 6: # Minimum required fields
194
+ continue
195
+
196
+ frame_id = int(parts[0])
197
+ track_id = int(parts[1])
198
+ left = float(parts[2])
199
+ top = float(parts[3])
200
+ width = float(parts[4])
201
+ height = float(parts[5])
202
+
203
+ # Optional fields
204
+ confidence = float(parts[6]) if len(parts) > 6 and parts[6] != '-1' else 1.0
205
+ class_id = int(parts[7]) if len(parts) > 7 and parts[7] != '-1' else 1
206
+ visibility = float(parts[8]) if len(parts) > 8 and parts[8] != '-1' else 1.0
207
+
208
+ frame_idx = frame_id - 1 # Convert to 0-based indexing
209
+ max_frame_idx = max(max_frame_idx, frame_idx)
210
+
211
+ # Skip low confidence detections
212
+ if confidence < 0.1:
213
+ continue
214
+
215
+ # Calculate coordinates with safer clipping
216
+ right = left + width
217
+ bottom = top + height
218
+
219
+ # Clip to image boundaries
220
+ left = max(0, int(left))
221
+ top = max(0, int(top))
222
+ right = min(int(right), img_w - 1)
223
+ bottom = min(int(bottom), img_h - 1)
224
+
225
+ # Skip invalid boxes
226
+ if right <= left or bottom <= top:
227
+ continue
228
+
229
+ # Get class name
230
+ class_name = class_mapping.get(class_id, default_class_name)
231
+
232
+ # Create VideoObject if not exists
233
+ if track_id not in video_objects:
234
+ obj_class = sly.ObjClass(class_name, sly.Rectangle)
235
+ video_objects[track_id] = sly.VideoObject(obj_class)
236
+
237
+ video_object = video_objects[track_id]
238
+
239
+ # Create rectangle and figure with track_id
240
+ rect = sly.Rectangle(top=top, left=left, bottom=bottom, right=right)
241
+ figure = sly.VideoFigure(video_object, rect, frame_idx, track_id=str(track_id))
242
+
243
+ frames_data[frame_idx].append(figure)
244
+
245
+ except (ValueError, IndexError) as e:
246
+ logger.warning(f"Skipped invalid MOT line {line_num}: {line} - {e}")
247
+ continue
248
+
249
+ # Create frames
250
+ frames = []
251
+ if frames_data:
252
+ frames_count = max(frames_data.keys()) + 1
253
+
254
+ for frame_idx in range(frames_count):
255
+ figures = frames_data.get(frame_idx, [])
256
+ frames.append(sly.Frame(frame_idx, figures))
257
+ else:
258
+ frames_count = 1
259
+ frames = [sly.Frame(0, [])]
260
+
261
+ # Create VideoAnnotation
262
+ objects = list(video_objects.values())
263
+
264
+ annotation = VideoAnnotation(
265
+ img_size=img_size,
266
+ frames_count=frames_count,
267
+ objects=sly.VideoObjectCollection(objects),
268
+ frames=sly.FrameCollection(frames)
269
+ )
270
+
271
+ logger.info(f"Created VideoAnnotation with {len(objects)} tracks and {frames_count} frames")
272
+
273
+ return annotation