vidformer 0.8.0__py3-none-any.whl → 0.10.0__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.
@@ -0,0 +1,529 @@
1
+ """
2
+ vidformer.supervision is the [supervision](https://supervision.roboflow.com/) frontend for [vidformer](https://github.com/ixlab/vidformer).
3
+ """
4
+
5
+ import vidformer.cv2 as vf_cv2
6
+
7
+ import supervision as _sv
8
+ import numpy as np
9
+ from supervision import Color, ColorPalette, ColorLookup, Detections
10
+ from supervision.annotators.utils import resolve_color, resolve_text_background_xyxy
11
+ from supervision.detection.utils import spread_out_boxes
12
+ from supervision.config import CLASS_NAME_DATA_FIELD
13
+ from math import sqrt
14
+ from supervision.geometry.core import Position
15
+
16
+ CV2_FONT = vf_cv2.FONT_HERSHEY_SIMPLEX
17
+
18
+
19
+ class BoxAnnotator:
20
+ def __init__(
21
+ self,
22
+ color=ColorPalette.DEFAULT,
23
+ thickness=2,
24
+ color_lookup=ColorLookup.CLASS,
25
+ ):
26
+ self.color = color
27
+ self.thickness = thickness
28
+ self.color_lookup = color_lookup
29
+
30
+ def annotate(
31
+ self,
32
+ scene: vf_cv2.Frame,
33
+ detections: Detections,
34
+ custom_color_lookup=None,
35
+ ):
36
+ for detection_idx in range(len(detections)):
37
+ x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
38
+ color = resolve_color(
39
+ color=self.color,
40
+ detections=detections,
41
+ detection_idx=detection_idx,
42
+ color_lookup=(
43
+ self.color_lookup
44
+ if custom_color_lookup is None
45
+ else custom_color_lookup
46
+ ),
47
+ )
48
+ vf_cv2.rectangle(
49
+ img=scene,
50
+ pt1=(x1, y1),
51
+ pt2=(x2, y2),
52
+ color=color.as_rgb(),
53
+ thickness=self.thickness,
54
+ )
55
+ return scene
56
+
57
+
58
+ class RoundBoxAnnotator:
59
+ def __init__(
60
+ self,
61
+ color=ColorPalette.DEFAULT,
62
+ thickness: int = 2,
63
+ color_lookup: ColorLookup = ColorLookup.CLASS,
64
+ roundness: float = 0.6,
65
+ ):
66
+ self.color = color
67
+ self.thickness = thickness
68
+ self.color_lookup = color_lookup
69
+ if not 0 < roundness <= 1.0:
70
+ raise ValueError("roundness attribute must be float between (0, 1.0]")
71
+ self.roundness = roundness
72
+
73
+ def annotate(
74
+ self,
75
+ scene: vf_cv2.Frame,
76
+ detections: _sv.Detections,
77
+ custom_color_lookup=None,
78
+ ):
79
+ for detection_idx in range(len(detections)):
80
+ x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
81
+ color = resolve_color(
82
+ color=self.color,
83
+ detections=detections,
84
+ detection_idx=detection_idx,
85
+ color_lookup=(
86
+ self.color_lookup
87
+ if custom_color_lookup is None
88
+ else custom_color_lookup
89
+ ),
90
+ )
91
+ radius = (
92
+ int((x2 - x1) // 2 * self.roundness)
93
+ if abs(x1 - x2) < abs(y1 - y2)
94
+ else int((y2 - y1) // 2 * self.roundness)
95
+ )
96
+ circle_coordinates = [
97
+ ((x1 + radius), (y1 + radius)),
98
+ ((x2 - radius), (y1 + radius)),
99
+ ((x2 - radius), (y2 - radius)),
100
+ ((x1 + radius), (y2 - radius)),
101
+ ]
102
+ line_coordinates = [
103
+ ((x1 + radius, y1), (x2 - radius, y1)),
104
+ ((x2, y1 + radius), (x2, y2 - radius)),
105
+ ((x1 + radius, y2), (x2 - radius, y2)),
106
+ ((x1, y1 + radius), (x1, y2 - radius)),
107
+ ]
108
+ start_angles = (180, 270, 0, 90)
109
+ end_angles = (270, 360, 90, 180)
110
+ for center_coordinates, line, start_angle, end_angle in zip(
111
+ circle_coordinates, line_coordinates, start_angles, end_angles
112
+ ):
113
+ vf_cv2.ellipse(
114
+ img=scene,
115
+ center=center_coordinates,
116
+ axes=(radius, radius),
117
+ angle=0,
118
+ startAngle=start_angle,
119
+ endAngle=end_angle,
120
+ color=color.as_rgb(),
121
+ thickness=self.thickness,
122
+ )
123
+ vf_cv2.line(
124
+ img=scene,
125
+ pt1=line[0],
126
+ pt2=line[1],
127
+ color=color.as_rgb(),
128
+ thickness=self.thickness,
129
+ )
130
+ return scene
131
+
132
+
133
+ class BoxCornerAnnotator:
134
+ def __init__(
135
+ self,
136
+ color=ColorPalette.DEFAULT,
137
+ thickness=4,
138
+ corner_length=15,
139
+ color_lookup=ColorLookup.CLASS,
140
+ ):
141
+ self.color = color
142
+ self.thickness: int = thickness
143
+ self.corner_length: int = corner_length
144
+ self.color_lookup: ColorLookup = color_lookup
145
+
146
+ def annotate(
147
+ self,
148
+ scene: vf_cv2.Frame,
149
+ detections: Detections,
150
+ custom_color_lookup=None,
151
+ ):
152
+ for detection_idx in range(len(detections)):
153
+ x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
154
+ color = resolve_color(
155
+ color=self.color,
156
+ detections=detections,
157
+ detection_idx=detection_idx,
158
+ color_lookup=(
159
+ self.color_lookup
160
+ if custom_color_lookup is None
161
+ else custom_color_lookup
162
+ ),
163
+ )
164
+ corners = [(x1, y1), (x2, y1), (x1, y2), (x2, y2)]
165
+ for x, y in corners:
166
+ x_end = x + self.corner_length if x == x1 else x - self.corner_length
167
+ vf_cv2.line(
168
+ scene, (x, y), (x_end, y), color.as_rgb(), thickness=self.thickness
169
+ )
170
+
171
+ y_end = y + self.corner_length if y == y1 else y - self.corner_length
172
+ vf_cv2.line(
173
+ scene, (x, y), (x, y_end), color.as_rgb(), thickness=self.thickness
174
+ )
175
+ return scene
176
+
177
+
178
+ class ColorAnnotator:
179
+ def __init__(
180
+ self,
181
+ color=ColorPalette.DEFAULT,
182
+ opacity: float = 0.5,
183
+ color_lookup: ColorLookup = ColorLookup.CLASS,
184
+ ):
185
+ self.color = color
186
+ self.color_lookup: ColorLookup = color_lookup
187
+ self.opacity = opacity
188
+
189
+ def annotate(
190
+ self,
191
+ scene: vf_cv2.Frame,
192
+ detections: Detections,
193
+ custom_color_lookup=None,
194
+ ):
195
+ scene_with_boxes = scene.copy()
196
+ for detection_idx in range(len(detections)):
197
+ x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
198
+ color = resolve_color(
199
+ color=self.color,
200
+ detections=detections,
201
+ detection_idx=detection_idx,
202
+ color_lookup=(
203
+ self.color_lookup
204
+ if custom_color_lookup is None
205
+ else custom_color_lookup
206
+ ),
207
+ )
208
+ vf_cv2.rectangle(
209
+ img=scene_with_boxes,
210
+ pt1=(x1, y1),
211
+ pt2=(x2, y2),
212
+ color=color.as_rgb(),
213
+ thickness=-1,
214
+ )
215
+
216
+ vf_cv2.addWeighted(
217
+ scene_with_boxes, self.opacity, scene, 1 - self.opacity, gamma=0, dst=scene
218
+ )
219
+ return scene
220
+
221
+
222
+ class CircleAnnotator:
223
+ def __init__(
224
+ self,
225
+ color=ColorPalette.DEFAULT,
226
+ thickness: int = 2,
227
+ color_lookup: ColorLookup = ColorLookup.CLASS,
228
+ ):
229
+ self.color = color
230
+ self.thickness: int = thickness
231
+ self.color_lookup: ColorLookup = color_lookup
232
+
233
+ def annotate(
234
+ self,
235
+ scene: vf_cv2.Frame,
236
+ detections: Detections,
237
+ custom_color_lookup=None,
238
+ ):
239
+ for detection_idx in range(len(detections)):
240
+ x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
241
+ center = ((x1 + x2) // 2, (y1 + y2) // 2)
242
+ distance = sqrt((x1 - center[0]) ** 2 + (y1 - center[1]) ** 2)
243
+ color = resolve_color(
244
+ color=self.color,
245
+ detections=detections,
246
+ detection_idx=detection_idx,
247
+ color_lookup=(
248
+ self.color_lookup
249
+ if custom_color_lookup is None
250
+ else custom_color_lookup
251
+ ),
252
+ )
253
+ vf_cv2.circle(
254
+ img=scene,
255
+ center=center,
256
+ radius=int(distance),
257
+ color=color.as_rgb(),
258
+ thickness=self.thickness,
259
+ )
260
+
261
+ return scene
262
+
263
+
264
+ class DotAnnotator:
265
+ def __init__(
266
+ self,
267
+ color=ColorPalette.DEFAULT,
268
+ radius: int = 4,
269
+ position: Position = Position.CENTER,
270
+ color_lookup: ColorLookup = ColorLookup.CLASS,
271
+ outline_thickness: int = 0,
272
+ outline_color=Color.BLACK,
273
+ ):
274
+
275
+ self.color = color
276
+ self.radius: int = radius
277
+ self.position: Position = position
278
+ self.color_lookup: ColorLookup = color_lookup
279
+ self.outline_thickness = outline_thickness
280
+ self.outline_color = outline_color
281
+
282
+ def annotate(
283
+ self,
284
+ scene: vf_cv2.Frame,
285
+ detections: Detections,
286
+ custom_color_lookup=None,
287
+ ):
288
+ xy = detections.get_anchors_coordinates(anchor=self.position)
289
+ for detection_idx in range(len(detections)):
290
+ color = resolve_color(
291
+ color=self.color,
292
+ detections=detections,
293
+ detection_idx=detection_idx,
294
+ color_lookup=(
295
+ self.color_lookup
296
+ if custom_color_lookup is None
297
+ else custom_color_lookup
298
+ ),
299
+ )
300
+ center = (int(xy[detection_idx, 0]), int(xy[detection_idx, 1]))
301
+
302
+ vf_cv2.circle(scene, center, self.radius, color.as_rgb(), -1)
303
+ if self.outline_thickness:
304
+ outline_color = resolve_color(
305
+ color=self.outline_color,
306
+ detections=detections,
307
+ detection_idx=detection_idx,
308
+ color_lookup=(
309
+ self.color_lookup
310
+ if custom_color_lookup is None
311
+ else custom_color_lookup
312
+ ),
313
+ )
314
+ vf_cv2.circle(
315
+ scene,
316
+ center,
317
+ self.radius,
318
+ outline_color.as_rgb(),
319
+ self.outline_thickness,
320
+ )
321
+ return scene
322
+
323
+
324
+ class LabelAnnotator:
325
+ def __init__(
326
+ self,
327
+ color=ColorPalette.DEFAULT,
328
+ text_color=Color.WHITE,
329
+ text_scale: float = 0.5,
330
+ text_thickness: int = 1,
331
+ text_padding: int = 10,
332
+ text_position: Position = Position.TOP_LEFT,
333
+ color_lookup: ColorLookup = ColorLookup.CLASS,
334
+ border_radius: int = 0,
335
+ smart_position: bool = False,
336
+ ):
337
+ self.border_radius: int = border_radius
338
+ self.color = color
339
+ self.text_color = text_color
340
+ self.text_scale: float = text_scale
341
+ self.text_thickness: int = text_thickness
342
+ self.text_padding: int = text_padding
343
+ self.text_anchor: Position = text_position
344
+ self.color_lookup: ColorLookup = color_lookup
345
+ self.smart_position = smart_position
346
+
347
+ def annotate(
348
+ self,
349
+ scene,
350
+ detections: Detections,
351
+ labels,
352
+ custom_color_lookup=None,
353
+ ):
354
+ self._validate_labels(labels, detections)
355
+
356
+ labels = self._get_labels_text(detections, labels)
357
+ label_properties = self._get_label_properties(detections, labels)
358
+
359
+ if self.smart_position:
360
+ xyxy = label_properties[:, :4]
361
+ xyxy = spread_out_boxes(xyxy)
362
+ label_properties[:, :4] = xyxy
363
+
364
+ self._draw_labels(
365
+ scene=scene,
366
+ labels=labels,
367
+ label_properties=label_properties,
368
+ detections=detections,
369
+ custom_color_lookup=custom_color_lookup,
370
+ )
371
+
372
+ return scene
373
+
374
+ def _validate_labels(self, labels, detections: Detections):
375
+ if labels is not None and len(labels) != len(detections):
376
+ raise ValueError(
377
+ f"The number of labels ({len(labels)}) does not match the "
378
+ f"number of detections ({len(detections)}). Each detection "
379
+ f"should have exactly 1 label."
380
+ )
381
+
382
+ def _get_label_properties(
383
+ self,
384
+ detections: Detections,
385
+ labels,
386
+ ):
387
+ label_properties = []
388
+ anchors_coordinates = detections.get_anchors_coordinates(
389
+ anchor=self.text_anchor
390
+ ).astype(int)
391
+
392
+ for label, center_coords in zip(labels, anchors_coordinates):
393
+ (text_w, text_h) = vf_cv2.getTextSize(
394
+ text=label,
395
+ fontFace=CV2_FONT,
396
+ fontScale=self.text_scale,
397
+ thickness=self.text_thickness,
398
+ )[0]
399
+
400
+ width_padded = text_w + 2 * self.text_padding
401
+ height_padded = text_h + 2 * self.text_padding
402
+
403
+ text_background_xyxy = resolve_text_background_xyxy(
404
+ center_coordinates=tuple(center_coords),
405
+ text_wh=(width_padded, height_padded),
406
+ position=self.text_anchor,
407
+ )
408
+
409
+ label_properties.append(
410
+ [
411
+ *text_background_xyxy,
412
+ text_h,
413
+ ]
414
+ )
415
+
416
+ return np.array(label_properties).reshape(-1, 5)
417
+
418
+ @staticmethod
419
+ def _get_labels_text(detections: Detections, custom_labels):
420
+ if custom_labels is not None:
421
+ return custom_labels
422
+
423
+ labels = []
424
+ for idx in range(len(detections)):
425
+ if CLASS_NAME_DATA_FIELD in detections.data:
426
+ labels.append(detections.data[CLASS_NAME_DATA_FIELD][idx])
427
+ elif detections.class_id is not None:
428
+ labels.append(str(detections.class_id[idx]))
429
+ else:
430
+ labels.append(str(idx))
431
+ return labels
432
+
433
+ def _draw_labels(
434
+ self,
435
+ scene,
436
+ labels,
437
+ label_properties,
438
+ detections,
439
+ custom_color_lookup,
440
+ ) -> None:
441
+ assert len(labels) == len(label_properties) == len(detections), (
442
+ f"Number of label properties ({len(label_properties)}), "
443
+ f"labels ({len(labels)}) and detections ({len(detections)}) "
444
+ "do not match."
445
+ )
446
+
447
+ color_lookup = (
448
+ custom_color_lookup
449
+ if custom_color_lookup is not None
450
+ else self.color_lookup
451
+ )
452
+
453
+ for idx, label_property in enumerate(label_properties):
454
+ background_color = resolve_color(
455
+ color=self.color,
456
+ detections=detections,
457
+ detection_idx=idx,
458
+ color_lookup=color_lookup,
459
+ )
460
+ text_color = resolve_color(
461
+ color=self.text_color,
462
+ detections=detections,
463
+ detection_idx=idx,
464
+ color_lookup=color_lookup,
465
+ )
466
+
467
+ box_xyxy = label_property[:4]
468
+ text_height_padded = label_property[4]
469
+ self.draw_rounded_rectangle(
470
+ scene=scene,
471
+ xyxy=box_xyxy,
472
+ color=background_color.as_rgb(),
473
+ border_radius=self.border_radius,
474
+ )
475
+
476
+ text_x = box_xyxy[0] + self.text_padding
477
+ text_y = box_xyxy[1] + self.text_padding + text_height_padded
478
+ vf_cv2.putText(
479
+ img=scene,
480
+ text=labels[idx],
481
+ org=(text_x, text_y),
482
+ fontFace=CV2_FONT,
483
+ fontScale=self.text_scale,
484
+ color=text_color.as_rgb(),
485
+ thickness=self.text_thickness,
486
+ lineType=vf_cv2.LINE_AA,
487
+ )
488
+
489
+ @staticmethod
490
+ def draw_rounded_rectangle(
491
+ scene: np.ndarray,
492
+ xyxy,
493
+ color,
494
+ border_radius: int,
495
+ ) -> np.ndarray:
496
+ x1, y1, x2, y2 = xyxy
497
+ width = x2 - x1
498
+ height = y2 - y1
499
+
500
+ border_radius = min(border_radius, min(width, height) // 2)
501
+
502
+ rectangle_coordinates = [
503
+ ((x1 + border_radius, y1), (x2 - border_radius, y2)),
504
+ ((x1, y1 + border_radius), (x2, y2 - border_radius)),
505
+ ]
506
+ circle_centers = [
507
+ (x1 + border_radius, y1 + border_radius),
508
+ (x2 - border_radius, y1 + border_radius),
509
+ (x1 + border_radius, y2 - border_radius),
510
+ (x2 - border_radius, y2 - border_radius),
511
+ ]
512
+
513
+ for coordinates in rectangle_coordinates:
514
+ vf_cv2.rectangle(
515
+ img=scene,
516
+ pt1=coordinates[0],
517
+ pt2=coordinates[1],
518
+ color=color,
519
+ thickness=-1,
520
+ )
521
+ for center in circle_centers:
522
+ vf_cv2.circle(
523
+ img=scene,
524
+ center=center,
525
+ radius=border_radius,
526
+ color=color,
527
+ thickness=-1,
528
+ )
529
+ return scene
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: vidformer
3
- Version: 0.8.0
4
- Summary: A Python library for creating and viewing videos with vidformer.
3
+ Version: 0.10.0
4
+ Summary: vidformer-py is a Python 🐍 interface for [vidformer](https://github.com/ixlab/vidformer).
5
5
  Author-email: Dominik Winecki <dominikwinecki@gmail.com>
6
6
  Requires-Python: >=3.8
7
7
  Description-Content-Type: text/markdown
@@ -19,13 +19,15 @@ Project-URL: Issues, https://github.com/ixlab/vidformer/issues
19
19
  [![PyPI version](https://img.shields.io/pypi/v/vidformer.svg)](https://pypi.org/project/vidformer/)
20
20
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/ixlab/vidformer/blob/main/LICENSE)
21
21
 
22
- vidformer-py is a Python 🐍 interface for [vidformer](https://github.com/ixlab/vidformer).
22
+ vidformer-py is a Python 🐍 frontend for [vidformer](https://github.com/ixlab/vidformer).
23
+ It has an API compatability layer with OpenCV cv2, as well as some [supervision](https://github.com/roboflow/supervision) annotators.
23
24
  Our [getting started guide](https://ixlab.github.io/vidformer/getting-started.html) explains how to use it.
24
25
 
25
26
  **Quick links:**
26
27
  * [📦 PyPI](https://pypi.org/project/vidformer/)
27
- * [📘 Documentation - vidformer-py](https://ixlab.github.io/vidformer/vidformer-py/)
28
- * [📘 Documentation - vidformer.cv2](https://ixlab.github.io/vidformer/vidformer-py-cv2/)
28
+ * [📘 Documentation - vidformer-py](https://ixlab.github.io/vidformer/vidformer-py/pdoc/)
29
+ * [📘 Documentation - vidformer.cv2](https://ixlab.github.io/vidformer/vidformer-py/pdoc/vidformer/cv2.html)
30
+ * [📘 Documentation - vidformer.supervision](https://ixlab.github.io/vidformer/vidformer-py/pdoc/vidformer/supervision.html)
29
31
  * [🧑‍💻 Source Code](https://github.com/ixlab/vidformer/tree/main/vidformer-py/)
30
32
 
31
33
  **Publish:**
@@ -0,0 +1,6 @@
1
+ vidformer/__init__.py,sha256=qpWcttHsW1wnaGx5__qJqaT-m5VF7yMiHCxdm10Fjek,44916
2
+ vidformer/cv2/__init__.py,sha256=DGm5NB4FGCHxPVez-yO748DjocKruxn4QBqqThgskWI,25555
3
+ vidformer/supervision/__init__.py,sha256=T2QJ3gKtUSoKOlxAf06TG4fD9IgIDuBpiVOBKVk3qAw,17150
4
+ vidformer-0.10.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
5
+ vidformer-0.10.0.dist-info/METADATA,sha256=f_aUIFbQUoVJuYTKtqexwyN5uzWRoS2Fvd2dHZ_EGbo,1800
6
+ vidformer-0.10.0.dist-info/RECORD,,