vidformer 1.2.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.
Potentially problematic release.
This version of vidformer might be problematic. Click here for more details.
- vidformer/__init__.py +900 -0
- vidformer/cv2/__init__.py +937 -0
- vidformer/supervision/__init__.py +635 -0
- vidformer-1.2.0.dist-info/METADATA +37 -0
- vidformer-1.2.0.dist-info/RECORD +6 -0
- vidformer-1.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"""
|
|
2
|
+
vidformer.supervision is the [supervision](https://supervision.roboflow.com/) frontend for [vidformer](https://github.com/ixlab/vidformer).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from math import sqrt
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import supervision as _sv
|
|
9
|
+
from supervision import Color, ColorLookup, ColorPalette, Detections
|
|
10
|
+
from supervision.annotators.utils import resolve_color, resolve_text_background_xyxy
|
|
11
|
+
from supervision.config import CLASS_NAME_DATA_FIELD
|
|
12
|
+
|
|
13
|
+
# supervision moved this between two versions, so we need to handle both cases
|
|
14
|
+
try:
|
|
15
|
+
from supervision.detection.utils import spread_out_boxes
|
|
16
|
+
except ImportError:
|
|
17
|
+
from supervision.detection.utils.boxes import spread_out_boxes
|
|
18
|
+
|
|
19
|
+
from supervision.geometry.core import Position
|
|
20
|
+
|
|
21
|
+
import vidformer.cv2 as vf_cv2
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
import cv2 as ocv_cv2
|
|
25
|
+
except ImportError:
|
|
26
|
+
ocv_cv2 = None
|
|
27
|
+
|
|
28
|
+
CV2_FONT = vf_cv2.FONT_HERSHEY_SIMPLEX
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BoxAnnotator:
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
color=ColorPalette.DEFAULT,
|
|
35
|
+
thickness=2,
|
|
36
|
+
color_lookup=ColorLookup.CLASS,
|
|
37
|
+
):
|
|
38
|
+
self.color = color
|
|
39
|
+
self.thickness = thickness
|
|
40
|
+
self.color_lookup = color_lookup
|
|
41
|
+
|
|
42
|
+
def annotate(
|
|
43
|
+
self,
|
|
44
|
+
scene: vf_cv2.Frame,
|
|
45
|
+
detections: Detections,
|
|
46
|
+
custom_color_lookup=None,
|
|
47
|
+
):
|
|
48
|
+
for detection_idx in range(len(detections)):
|
|
49
|
+
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
|
50
|
+
color = resolve_color(
|
|
51
|
+
color=self.color,
|
|
52
|
+
detections=detections,
|
|
53
|
+
detection_idx=detection_idx,
|
|
54
|
+
color_lookup=(
|
|
55
|
+
self.color_lookup
|
|
56
|
+
if custom_color_lookup is None
|
|
57
|
+
else custom_color_lookup
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
vf_cv2.rectangle(
|
|
61
|
+
img=scene,
|
|
62
|
+
pt1=(x1, y1),
|
|
63
|
+
pt2=(x2, y2),
|
|
64
|
+
color=color.as_rgb(),
|
|
65
|
+
thickness=self.thickness,
|
|
66
|
+
)
|
|
67
|
+
return scene
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RoundBoxAnnotator:
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
color=ColorPalette.DEFAULT,
|
|
74
|
+
thickness: int = 2,
|
|
75
|
+
color_lookup: ColorLookup = ColorLookup.CLASS,
|
|
76
|
+
roundness: float = 0.6,
|
|
77
|
+
):
|
|
78
|
+
self.color = color
|
|
79
|
+
self.thickness = thickness
|
|
80
|
+
self.color_lookup = color_lookup
|
|
81
|
+
if not 0 < roundness <= 1.0:
|
|
82
|
+
raise ValueError("roundness attribute must be float between (0, 1.0]")
|
|
83
|
+
self.roundness = roundness
|
|
84
|
+
|
|
85
|
+
def annotate(
|
|
86
|
+
self,
|
|
87
|
+
scene: vf_cv2.Frame,
|
|
88
|
+
detections: _sv.Detections,
|
|
89
|
+
custom_color_lookup=None,
|
|
90
|
+
):
|
|
91
|
+
for detection_idx in range(len(detections)):
|
|
92
|
+
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
|
93
|
+
color = resolve_color(
|
|
94
|
+
color=self.color,
|
|
95
|
+
detections=detections,
|
|
96
|
+
detection_idx=detection_idx,
|
|
97
|
+
color_lookup=(
|
|
98
|
+
self.color_lookup
|
|
99
|
+
if custom_color_lookup is None
|
|
100
|
+
else custom_color_lookup
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
radius = (
|
|
104
|
+
int((x2 - x1) // 2 * self.roundness)
|
|
105
|
+
if abs(x1 - x2) < abs(y1 - y2)
|
|
106
|
+
else int((y2 - y1) // 2 * self.roundness)
|
|
107
|
+
)
|
|
108
|
+
circle_coordinates = [
|
|
109
|
+
((x1 + radius), (y1 + radius)),
|
|
110
|
+
((x2 - radius), (y1 + radius)),
|
|
111
|
+
((x2 - radius), (y2 - radius)),
|
|
112
|
+
((x1 + radius), (y2 - radius)),
|
|
113
|
+
]
|
|
114
|
+
line_coordinates = [
|
|
115
|
+
((x1 + radius, y1), (x2 - radius, y1)),
|
|
116
|
+
((x2, y1 + radius), (x2, y2 - radius)),
|
|
117
|
+
((x1 + radius, y2), (x2 - radius, y2)),
|
|
118
|
+
((x1, y1 + radius), (x1, y2 - radius)),
|
|
119
|
+
]
|
|
120
|
+
start_angles = (180, 270, 0, 90)
|
|
121
|
+
end_angles = (270, 360, 90, 180)
|
|
122
|
+
for center_coordinates, line, start_angle, end_angle in zip(
|
|
123
|
+
circle_coordinates, line_coordinates, start_angles, end_angles
|
|
124
|
+
):
|
|
125
|
+
vf_cv2.ellipse(
|
|
126
|
+
img=scene,
|
|
127
|
+
center=center_coordinates,
|
|
128
|
+
axes=(radius, radius),
|
|
129
|
+
angle=0,
|
|
130
|
+
startAngle=start_angle,
|
|
131
|
+
endAngle=end_angle,
|
|
132
|
+
color=color.as_rgb(),
|
|
133
|
+
thickness=self.thickness,
|
|
134
|
+
)
|
|
135
|
+
vf_cv2.line(
|
|
136
|
+
img=scene,
|
|
137
|
+
pt1=line[0],
|
|
138
|
+
pt2=line[1],
|
|
139
|
+
color=color.as_rgb(),
|
|
140
|
+
thickness=self.thickness,
|
|
141
|
+
)
|
|
142
|
+
return scene
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class BoxCornerAnnotator:
|
|
146
|
+
def __init__(
|
|
147
|
+
self,
|
|
148
|
+
color=ColorPalette.DEFAULT,
|
|
149
|
+
thickness=4,
|
|
150
|
+
corner_length=15,
|
|
151
|
+
color_lookup=ColorLookup.CLASS,
|
|
152
|
+
):
|
|
153
|
+
self.color = color
|
|
154
|
+
self.thickness: int = thickness
|
|
155
|
+
self.corner_length: int = corner_length
|
|
156
|
+
self.color_lookup: ColorLookup = color_lookup
|
|
157
|
+
|
|
158
|
+
def annotate(
|
|
159
|
+
self,
|
|
160
|
+
scene: vf_cv2.Frame,
|
|
161
|
+
detections: Detections,
|
|
162
|
+
custom_color_lookup=None,
|
|
163
|
+
):
|
|
164
|
+
for detection_idx in range(len(detections)):
|
|
165
|
+
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
|
166
|
+
color = resolve_color(
|
|
167
|
+
color=self.color,
|
|
168
|
+
detections=detections,
|
|
169
|
+
detection_idx=detection_idx,
|
|
170
|
+
color_lookup=(
|
|
171
|
+
self.color_lookup
|
|
172
|
+
if custom_color_lookup is None
|
|
173
|
+
else custom_color_lookup
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
corners = [(x1, y1), (x2, y1), (x1, y2), (x2, y2)]
|
|
177
|
+
for x, y in corners:
|
|
178
|
+
x_end = x + self.corner_length if x == x1 else x - self.corner_length
|
|
179
|
+
vf_cv2.line(
|
|
180
|
+
scene, (x, y), (x_end, y), color.as_rgb(), thickness=self.thickness
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
y_end = y + self.corner_length if y == y1 else y - self.corner_length
|
|
184
|
+
vf_cv2.line(
|
|
185
|
+
scene, (x, y), (x, y_end), color.as_rgb(), thickness=self.thickness
|
|
186
|
+
)
|
|
187
|
+
return scene
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class ColorAnnotator:
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
color=ColorPalette.DEFAULT,
|
|
194
|
+
opacity: float = 0.5,
|
|
195
|
+
color_lookup: ColorLookup = ColorLookup.CLASS,
|
|
196
|
+
):
|
|
197
|
+
self.color = color
|
|
198
|
+
self.color_lookup: ColorLookup = color_lookup
|
|
199
|
+
self.opacity = opacity
|
|
200
|
+
|
|
201
|
+
def annotate(
|
|
202
|
+
self,
|
|
203
|
+
scene: vf_cv2.Frame,
|
|
204
|
+
detections: Detections,
|
|
205
|
+
custom_color_lookup=None,
|
|
206
|
+
):
|
|
207
|
+
scene_with_boxes = scene.copy()
|
|
208
|
+
for detection_idx in range(len(detections)):
|
|
209
|
+
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
|
210
|
+
color = resolve_color(
|
|
211
|
+
color=self.color,
|
|
212
|
+
detections=detections,
|
|
213
|
+
detection_idx=detection_idx,
|
|
214
|
+
color_lookup=(
|
|
215
|
+
self.color_lookup
|
|
216
|
+
if custom_color_lookup is None
|
|
217
|
+
else custom_color_lookup
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
vf_cv2.rectangle(
|
|
221
|
+
img=scene_with_boxes,
|
|
222
|
+
pt1=(x1, y1),
|
|
223
|
+
pt2=(x2, y2),
|
|
224
|
+
color=color.as_rgb(),
|
|
225
|
+
thickness=-1,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
vf_cv2.addWeighted(
|
|
229
|
+
scene_with_boxes, self.opacity, scene, 1 - self.opacity, gamma=0, dst=scene
|
|
230
|
+
)
|
|
231
|
+
return scene
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class CircleAnnotator:
|
|
235
|
+
def __init__(
|
|
236
|
+
self,
|
|
237
|
+
color=ColorPalette.DEFAULT,
|
|
238
|
+
thickness: int = 2,
|
|
239
|
+
color_lookup: ColorLookup = ColorLookup.CLASS,
|
|
240
|
+
):
|
|
241
|
+
self.color = color
|
|
242
|
+
self.thickness: int = thickness
|
|
243
|
+
self.color_lookup: ColorLookup = color_lookup
|
|
244
|
+
|
|
245
|
+
def annotate(
|
|
246
|
+
self,
|
|
247
|
+
scene: vf_cv2.Frame,
|
|
248
|
+
detections: Detections,
|
|
249
|
+
custom_color_lookup=None,
|
|
250
|
+
):
|
|
251
|
+
for detection_idx in range(len(detections)):
|
|
252
|
+
x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int)
|
|
253
|
+
center = ((x1 + x2) // 2, (y1 + y2) // 2)
|
|
254
|
+
distance = sqrt((x1 - center[0]) ** 2 + (y1 - center[1]) ** 2)
|
|
255
|
+
color = resolve_color(
|
|
256
|
+
color=self.color,
|
|
257
|
+
detections=detections,
|
|
258
|
+
detection_idx=detection_idx,
|
|
259
|
+
color_lookup=(
|
|
260
|
+
self.color_lookup
|
|
261
|
+
if custom_color_lookup is None
|
|
262
|
+
else custom_color_lookup
|
|
263
|
+
),
|
|
264
|
+
)
|
|
265
|
+
vf_cv2.circle(
|
|
266
|
+
img=scene,
|
|
267
|
+
center=center,
|
|
268
|
+
radius=int(distance),
|
|
269
|
+
color=color.as_rgb(),
|
|
270
|
+
thickness=self.thickness,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
return scene
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class DotAnnotator:
|
|
277
|
+
def __init__(
|
|
278
|
+
self,
|
|
279
|
+
color=ColorPalette.DEFAULT,
|
|
280
|
+
radius: int = 4,
|
|
281
|
+
position: Position = Position.CENTER,
|
|
282
|
+
color_lookup: ColorLookup = ColorLookup.CLASS,
|
|
283
|
+
outline_thickness: int = 0,
|
|
284
|
+
outline_color=Color.BLACK,
|
|
285
|
+
):
|
|
286
|
+
self.color = color
|
|
287
|
+
self.radius: int = radius
|
|
288
|
+
self.position: Position = position
|
|
289
|
+
self.color_lookup: ColorLookup = color_lookup
|
|
290
|
+
self.outline_thickness = outline_thickness
|
|
291
|
+
self.outline_color = outline_color
|
|
292
|
+
|
|
293
|
+
def annotate(
|
|
294
|
+
self,
|
|
295
|
+
scene: vf_cv2.Frame,
|
|
296
|
+
detections: Detections,
|
|
297
|
+
custom_color_lookup=None,
|
|
298
|
+
):
|
|
299
|
+
xy = detections.get_anchors_coordinates(anchor=self.position)
|
|
300
|
+
for detection_idx in range(len(detections)):
|
|
301
|
+
color = resolve_color(
|
|
302
|
+
color=self.color,
|
|
303
|
+
detections=detections,
|
|
304
|
+
detection_idx=detection_idx,
|
|
305
|
+
color_lookup=(
|
|
306
|
+
self.color_lookup
|
|
307
|
+
if custom_color_lookup is None
|
|
308
|
+
else custom_color_lookup
|
|
309
|
+
),
|
|
310
|
+
)
|
|
311
|
+
center = (int(xy[detection_idx, 0]), int(xy[detection_idx, 1]))
|
|
312
|
+
|
|
313
|
+
vf_cv2.circle(scene, center, self.radius, color.as_rgb(), -1)
|
|
314
|
+
if self.outline_thickness:
|
|
315
|
+
outline_color = resolve_color(
|
|
316
|
+
color=self.outline_color,
|
|
317
|
+
detections=detections,
|
|
318
|
+
detection_idx=detection_idx,
|
|
319
|
+
color_lookup=(
|
|
320
|
+
self.color_lookup
|
|
321
|
+
if custom_color_lookup is None
|
|
322
|
+
else custom_color_lookup
|
|
323
|
+
),
|
|
324
|
+
)
|
|
325
|
+
vf_cv2.circle(
|
|
326
|
+
scene,
|
|
327
|
+
center,
|
|
328
|
+
self.radius,
|
|
329
|
+
outline_color.as_rgb(),
|
|
330
|
+
self.outline_thickness,
|
|
331
|
+
)
|
|
332
|
+
return scene
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class LabelAnnotator:
|
|
336
|
+
def __init__(
|
|
337
|
+
self,
|
|
338
|
+
color=ColorPalette.DEFAULT,
|
|
339
|
+
text_color=Color.WHITE,
|
|
340
|
+
text_scale: float = 0.5,
|
|
341
|
+
text_thickness: int = 1,
|
|
342
|
+
text_padding: int = 10,
|
|
343
|
+
text_position: Position = Position.TOP_LEFT,
|
|
344
|
+
color_lookup: ColorLookup = ColorLookup.CLASS,
|
|
345
|
+
border_radius: int = 0,
|
|
346
|
+
smart_position: bool = False,
|
|
347
|
+
):
|
|
348
|
+
self.border_radius: int = border_radius
|
|
349
|
+
self.color = color
|
|
350
|
+
self.text_color = text_color
|
|
351
|
+
self.text_scale: float = text_scale
|
|
352
|
+
self.text_thickness: int = text_thickness
|
|
353
|
+
self.text_padding: int = text_padding
|
|
354
|
+
self.text_anchor: Position = text_position
|
|
355
|
+
self.color_lookup: ColorLookup = color_lookup
|
|
356
|
+
self.smart_position = smart_position
|
|
357
|
+
|
|
358
|
+
def annotate(
|
|
359
|
+
self,
|
|
360
|
+
scene,
|
|
361
|
+
detections: Detections,
|
|
362
|
+
labels,
|
|
363
|
+
custom_color_lookup=None,
|
|
364
|
+
):
|
|
365
|
+
self._validate_labels(labels, detections)
|
|
366
|
+
|
|
367
|
+
labels = self._get_labels_text(detections, labels)
|
|
368
|
+
label_properties = self._get_label_properties(detections, labels)
|
|
369
|
+
|
|
370
|
+
if self.smart_position:
|
|
371
|
+
xyxy = label_properties[:, :4]
|
|
372
|
+
xyxy = spread_out_boxes(xyxy)
|
|
373
|
+
label_properties[:, :4] = xyxy
|
|
374
|
+
|
|
375
|
+
self._draw_labels(
|
|
376
|
+
scene=scene,
|
|
377
|
+
labels=labels,
|
|
378
|
+
label_properties=label_properties,
|
|
379
|
+
detections=detections,
|
|
380
|
+
custom_color_lookup=custom_color_lookup,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return scene
|
|
384
|
+
|
|
385
|
+
def _validate_labels(self, labels, detections: Detections):
|
|
386
|
+
if labels is not None and len(labels) != len(detections):
|
|
387
|
+
raise ValueError(
|
|
388
|
+
f"The number of labels ({len(labels)}) does not match the "
|
|
389
|
+
f"number of detections ({len(detections)}). Each detection "
|
|
390
|
+
f"should have exactly 1 label."
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
def _get_label_properties(
|
|
394
|
+
self,
|
|
395
|
+
detections: Detections,
|
|
396
|
+
labels,
|
|
397
|
+
):
|
|
398
|
+
label_properties = []
|
|
399
|
+
anchors_coordinates = detections.get_anchors_coordinates(
|
|
400
|
+
anchor=self.text_anchor
|
|
401
|
+
).astype(int)
|
|
402
|
+
|
|
403
|
+
for label, center_coords in zip(labels, anchors_coordinates):
|
|
404
|
+
(text_w, text_h) = vf_cv2.getTextSize(
|
|
405
|
+
text=label,
|
|
406
|
+
fontFace=CV2_FONT,
|
|
407
|
+
fontScale=self.text_scale,
|
|
408
|
+
thickness=self.text_thickness,
|
|
409
|
+
)[0]
|
|
410
|
+
|
|
411
|
+
width_padded = text_w + 2 * self.text_padding
|
|
412
|
+
height_padded = text_h + 2 * self.text_padding
|
|
413
|
+
|
|
414
|
+
text_background_xyxy = resolve_text_background_xyxy(
|
|
415
|
+
center_coordinates=tuple(center_coords),
|
|
416
|
+
text_wh=(width_padded, height_padded),
|
|
417
|
+
position=self.text_anchor,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
label_properties.append(
|
|
421
|
+
[
|
|
422
|
+
*text_background_xyxy,
|
|
423
|
+
text_h,
|
|
424
|
+
]
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
return np.array(label_properties).reshape(-1, 5)
|
|
428
|
+
|
|
429
|
+
@staticmethod
|
|
430
|
+
def _get_labels_text(detections: Detections, custom_labels):
|
|
431
|
+
if custom_labels is not None:
|
|
432
|
+
return custom_labels
|
|
433
|
+
|
|
434
|
+
labels = []
|
|
435
|
+
for idx in range(len(detections)):
|
|
436
|
+
if CLASS_NAME_DATA_FIELD in detections.data:
|
|
437
|
+
labels.append(detections.data[CLASS_NAME_DATA_FIELD][idx])
|
|
438
|
+
elif detections.class_id is not None:
|
|
439
|
+
labels.append(str(detections.class_id[idx]))
|
|
440
|
+
else:
|
|
441
|
+
labels.append(str(idx))
|
|
442
|
+
return labels
|
|
443
|
+
|
|
444
|
+
def _draw_labels(
|
|
445
|
+
self,
|
|
446
|
+
scene,
|
|
447
|
+
labels,
|
|
448
|
+
label_properties,
|
|
449
|
+
detections,
|
|
450
|
+
custom_color_lookup,
|
|
451
|
+
) -> None:
|
|
452
|
+
assert len(labels) == len(label_properties) == len(detections), (
|
|
453
|
+
f"Number of label properties ({len(label_properties)}), "
|
|
454
|
+
f"labels ({len(labels)}) and detections ({len(detections)}) "
|
|
455
|
+
"do not match."
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
color_lookup = (
|
|
459
|
+
custom_color_lookup
|
|
460
|
+
if custom_color_lookup is not None
|
|
461
|
+
else self.color_lookup
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
for idx, label_property in enumerate(label_properties):
|
|
465
|
+
background_color = resolve_color(
|
|
466
|
+
color=self.color,
|
|
467
|
+
detections=detections,
|
|
468
|
+
detection_idx=idx,
|
|
469
|
+
color_lookup=color_lookup,
|
|
470
|
+
)
|
|
471
|
+
text_color = resolve_color(
|
|
472
|
+
color=self.text_color,
|
|
473
|
+
detections=detections,
|
|
474
|
+
detection_idx=idx,
|
|
475
|
+
color_lookup=color_lookup,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
box_xyxy = label_property[:4]
|
|
479
|
+
text_height_padded = label_property[4]
|
|
480
|
+
self.draw_rounded_rectangle(
|
|
481
|
+
scene=scene,
|
|
482
|
+
xyxy=box_xyxy,
|
|
483
|
+
color=background_color.as_rgb(),
|
|
484
|
+
border_radius=self.border_radius,
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
text_x = box_xyxy[0] + self.text_padding
|
|
488
|
+
text_y = box_xyxy[1] + self.text_padding + text_height_padded
|
|
489
|
+
vf_cv2.putText(
|
|
490
|
+
img=scene,
|
|
491
|
+
text=labels[idx],
|
|
492
|
+
org=(text_x, text_y),
|
|
493
|
+
fontFace=CV2_FONT,
|
|
494
|
+
fontScale=self.text_scale,
|
|
495
|
+
color=text_color.as_rgb(),
|
|
496
|
+
thickness=self.text_thickness,
|
|
497
|
+
lineType=vf_cv2.LINE_AA,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
@staticmethod
|
|
501
|
+
def draw_rounded_rectangle(
|
|
502
|
+
scene: np.ndarray,
|
|
503
|
+
xyxy,
|
|
504
|
+
color,
|
|
505
|
+
border_radius: int,
|
|
506
|
+
) -> np.ndarray:
|
|
507
|
+
x1, y1, x2, y2 = xyxy
|
|
508
|
+
width = x2 - x1
|
|
509
|
+
height = y2 - y1
|
|
510
|
+
|
|
511
|
+
border_radius = min(border_radius, min(width, height) // 2)
|
|
512
|
+
|
|
513
|
+
if border_radius <= 0:
|
|
514
|
+
vf_cv2.rectangle(
|
|
515
|
+
img=scene,
|
|
516
|
+
pt1=(x1, y1),
|
|
517
|
+
pt2=(x2, y2),
|
|
518
|
+
color=color,
|
|
519
|
+
thickness=-1,
|
|
520
|
+
)
|
|
521
|
+
else:
|
|
522
|
+
rectangle_coordinates = [
|
|
523
|
+
((x1 + border_radius, y1), (x2 - border_radius, y2)),
|
|
524
|
+
((x1, y1 + border_radius), (x2, y2 - border_radius)),
|
|
525
|
+
]
|
|
526
|
+
circle_centers = [
|
|
527
|
+
(x1 + border_radius, y1 + border_radius),
|
|
528
|
+
(x2 - border_radius, y1 + border_radius),
|
|
529
|
+
(x1 + border_radius, y2 - border_radius),
|
|
530
|
+
(x2 - border_radius, y2 - border_radius),
|
|
531
|
+
]
|
|
532
|
+
|
|
533
|
+
for coordinates in rectangle_coordinates:
|
|
534
|
+
vf_cv2.rectangle(
|
|
535
|
+
img=scene,
|
|
536
|
+
pt1=coordinates[0],
|
|
537
|
+
pt2=coordinates[1],
|
|
538
|
+
color=color,
|
|
539
|
+
thickness=-1,
|
|
540
|
+
)
|
|
541
|
+
for center in circle_centers:
|
|
542
|
+
vf_cv2.circle(
|
|
543
|
+
img=scene,
|
|
544
|
+
center=center,
|
|
545
|
+
radius=border_radius,
|
|
546
|
+
color=color,
|
|
547
|
+
thickness=-1,
|
|
548
|
+
)
|
|
549
|
+
return scene
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
class MaskAnnotator:
|
|
553
|
+
def __init__(
|
|
554
|
+
self,
|
|
555
|
+
color=ColorPalette.DEFAULT,
|
|
556
|
+
opacity: float = 0.5,
|
|
557
|
+
color_lookup: ColorLookup = ColorLookup.CLASS,
|
|
558
|
+
):
|
|
559
|
+
self.color = color
|
|
560
|
+
self.opacity = opacity
|
|
561
|
+
self.color_lookup: ColorLookup = color_lookup
|
|
562
|
+
|
|
563
|
+
def annotate(
|
|
564
|
+
self,
|
|
565
|
+
scene,
|
|
566
|
+
detections: Detections,
|
|
567
|
+
custom_color_lookup=None,
|
|
568
|
+
):
|
|
569
|
+
if detections.mask is None:
|
|
570
|
+
return scene
|
|
571
|
+
|
|
572
|
+
colored_mask = scene.copy()
|
|
573
|
+
|
|
574
|
+
for detection_idx in np.flip(np.argsort(detections.box_area)):
|
|
575
|
+
color = resolve_color(
|
|
576
|
+
color=self.color,
|
|
577
|
+
detections=detections,
|
|
578
|
+
detection_idx=detection_idx,
|
|
579
|
+
color_lookup=(
|
|
580
|
+
self.color_lookup
|
|
581
|
+
if custom_color_lookup is None
|
|
582
|
+
else custom_color_lookup
|
|
583
|
+
),
|
|
584
|
+
)
|
|
585
|
+
mask = detections.mask[detection_idx]
|
|
586
|
+
colored_mask[mask] = color.as_bgr()
|
|
587
|
+
|
|
588
|
+
vf_cv2.addWeighted(
|
|
589
|
+
colored_mask, self.opacity, scene, 1 - self.opacity, 0, dst=scene
|
|
590
|
+
)
|
|
591
|
+
return scene
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
class MaskStreamWriter:
|
|
595
|
+
def __init__(self, path: str, shape: tuple):
|
|
596
|
+
# Shape should be (width, height)
|
|
597
|
+
assert ocv_cv2 is not None, "OpenCV cv2 is required for ExternDetectionsBuilder"
|
|
598
|
+
assert type(shape) is tuple, "shape must be a tuple"
|
|
599
|
+
assert len(shape) == 2, "shape must be a tuple of length 2"
|
|
600
|
+
self._shape = (shape[1], shape[0])
|
|
601
|
+
self._writer = ocv_cv2.VideoWriter(
|
|
602
|
+
path, ocv_cv2.VideoWriter_fourcc(*"FFV1"), 1, shape, isColor=False
|
|
603
|
+
)
|
|
604
|
+
assert self._writer.isOpened(), f"Failed to open video writer at {path}"
|
|
605
|
+
self._i = 0
|
|
606
|
+
|
|
607
|
+
def write_detections(self, detections: Detections):
|
|
608
|
+
if len(detections) == 0:
|
|
609
|
+
return self._i
|
|
610
|
+
|
|
611
|
+
mask = detections.mask
|
|
612
|
+
assert (
|
|
613
|
+
mask.shape[1:] == self._shape
|
|
614
|
+
), f"mask shape ({mask.shape[:1]}) must match the shape of the video ({self._shape})"
|
|
615
|
+
for i in range(mask.shape[0]):
|
|
616
|
+
frame_uint8 = detections.mask[i].astype(np.uint8)
|
|
617
|
+
self._writer.write(frame_uint8)
|
|
618
|
+
self._i += 1
|
|
619
|
+
return self._i
|
|
620
|
+
|
|
621
|
+
def release(self):
|
|
622
|
+
self._writer.release()
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def populate_mask(
|
|
626
|
+
detections: Detections, mask_stream: vf_cv2.VideoCapture, frame_idx: int
|
|
627
|
+
):
|
|
628
|
+
assert type(detections) is Detections
|
|
629
|
+
assert detections.mask is None
|
|
630
|
+
detections.mask = []
|
|
631
|
+
assert len(detections) + frame_idx <= len(mask_stream)
|
|
632
|
+
for i in range(len(detections)):
|
|
633
|
+
mask = mask_stream[frame_idx + i]
|
|
634
|
+
assert mask.shape[2] == 1, "mask must be a single channel image"
|
|
635
|
+
detections.mask.append(mask)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vidformer
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: vidformer-py is a Python 🐍 interface for [vidformer](https://github.com/ixlab/vidformer).
|
|
5
|
+
Author-email: Dominik Winecki <dominikwinecki@gmail.com>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Dist: requests
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Project-URL: Documentation, https://ixlab.github.io/vidformer/vidformer-py/
|
|
13
|
+
Project-URL: Homepage, https://ixlab.github.io/vidformer/
|
|
14
|
+
Project-URL: Issues, https://github.com/ixlab/vidformer/issues
|
|
15
|
+
|
|
16
|
+
# vidformer-py
|
|
17
|
+
|
|
18
|
+
[](https://pypi.org/project/vidformer/)
|
|
19
|
+
[](https://github.com/ixlab/vidformer/blob/main/LICENSE)
|
|
20
|
+
|
|
21
|
+
vidformer-py is a Python 🐍 frontend for [vidformer](https://github.com/ixlab/vidformer).
|
|
22
|
+
It has an API compatability layer with OpenCV cv2, as well as some [supervision](https://github.com/roboflow/supervision) annotators.
|
|
23
|
+
Our [getting started guide](https://ixlab.github.io/vidformer/getting-started.html) explains how to use it.
|
|
24
|
+
|
|
25
|
+
**Quick links:**
|
|
26
|
+
* [📦 PyPI](https://pypi.org/project/vidformer/)
|
|
27
|
+
* [📘 Documentation - vidformer-py](https://ixlab.github.io/vidformer/vidformer-py/pdoc/)
|
|
28
|
+
* [📘 Documentation - vidformer.cv2](https://ixlab.github.io/vidformer/vidformer-py/pdoc/vidformer/cv2.html)
|
|
29
|
+
* [📘 Documentation - vidformer.supervision](https://ixlab.github.io/vidformer/vidformer-py/pdoc/vidformer/supervision.html)
|
|
30
|
+
* [🧑💻 Source Code](https://github.com/ixlab/vidformer/tree/main/vidformer-py/)
|
|
31
|
+
|
|
32
|
+
**Publish:**
|
|
33
|
+
```bash
|
|
34
|
+
export FLIT_USERNAME='__token__' FLIT_PASSWORD='<token>'
|
|
35
|
+
flit publish
|
|
36
|
+
```
|
|
37
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
vidformer/__init__.py,sha256=0l6e8yMCWfQmk5P80Z2JhKEUGIXhlza4SeS9p9jzSbc,29972
|
|
2
|
+
vidformer/cv2/__init__.py,sha256=QYB-rpkBURtE-q9MhqF1zgsq1qfbPLb0DDjtDpRxVJY,27978
|
|
3
|
+
vidformer/supervision/__init__.py,sha256=FzuFmMUtnnxlXoeSzLDYTWatQ5E-RsMF5Atwbv_RfHI,20523
|
|
4
|
+
vidformer-1.2.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
5
|
+
vidformer-1.2.0.dist-info/METADATA,sha256=oXwwyrPIMpMOzFUaiCkky-a76c0kpiWzUiX4YINN0WY,1776
|
|
6
|
+
vidformer-1.2.0.dist-info/RECORD,,
|