|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
| 3 | +import time |
| 4 | +from collections import deque |
3 | 5 | from dataclasses import dataclass |
4 | 6 | from typing import Callable, Generator, Optional, Tuple |
5 | 7 |
|
@@ -71,13 +73,11 @@ class VideoSink: |
71 | 73 | ```python |
72 | 74 | >>> import supervision as sv |
73 | 75 |
|
74 | | - >>> video_info = sv.VideoInfo.from_video_path(video_path='source_video.mp4') |
| 76 | + >>> video_info = sv.VideoInfo.from_video_path('source.mp4') |
| 77 | + >>> frames_generator = get_video_frames_generator('source.mp4') |
75 | 78 |
|
76 | | - >>> with sv.VideoSink(target_path='target_video.mp4', |
77 | | - ... video_info=video_info, |
78 | | - codec='H264') as sink: |
79 | | - ... for frame in get_video_frames_generator(source_path='source_video.mp4', |
80 | | - ... stride=2): |
| 79 | + >>> with sv.VideoSink(target_path='target.mp4', video_info=video_info) as sink: |
| 80 | + ... for frame in frames_generator: |
81 | 81 | ... sink.write_frame(frame=frame) |
82 | 82 | ``` |
83 | 83 | """ |
@@ -202,3 +202,54 @@ def process_video( |
202 | 202 | ): |
203 | 203 | result_frame = callback(frame, index) |
204 | 204 | sink.write_frame(frame=result_frame) |
| 205 | + |
| 206 | + |
| 207 | +class FPSMonitor: |
| 208 | + """ |
| 209 | + A class for monitoring frames per second (FPS) to benchmark latency. |
| 210 | + """ |
| 211 | + def __init__(self, sample_size: int = 30): |
| 212 | + """ |
| 213 | + Args: |
| 214 | + sample_size (int): The maximum number of observations for latency |
| 215 | + benchmarking. |
| 216 | +
|
| 217 | + Examples: |
| 218 | + ```python |
| 219 | + >>> import supervision as sv |
| 220 | +
|
| 221 | + >>> frames_generator = sv.get_video_frames_generator('source.mp4') |
| 222 | + >>> fps_monitor = sv.FPSMonitor() |
| 223 | +
|
| 224 | + >>> for frame in frames_generator: |
| 225 | + ... # your processing code here |
| 226 | + ... fps_monitor.tick() |
| 227 | + ... fps = fps_monitor() |
| 228 | + ``` |
| 229 | + """ |
| 230 | + self.all_timestamps = deque(maxlen=sample_size) |
| 231 | + |
| 232 | + def __call__(self) -> float: |
| 233 | + """ |
| 234 | + Computes and returns the average FPS based on the stored time stamps. |
| 235 | +
|
| 236 | + Returns: |
| 237 | + float: The average FPS. Returns 0.0 if no time stamps are stored. |
| 238 | + """ |
| 239 | + |
| 240 | + if not self.all_timestamps: |
| 241 | + return 0.0 |
| 242 | + taken_time = self.all_timestamps[-1] - self.all_timestamps[0] |
| 243 | + return (len(self.all_timestamps)) / taken_time if taken_time != 0 else 0.0 |
| 244 | + |
| 245 | + def tick(self) -> None: |
| 246 | + """ |
| 247 | + Adds a new time stamp to the deque for FPS calculation. |
| 248 | + """ |
| 249 | + self.all_timestamps.append(time.monotonic()) |
| 250 | + |
| 251 | + def reset(self) -> None: |
| 252 | + """ |
| 253 | + Clears all the time stamps from the deque. |
| 254 | + """ |
| 255 | + self.all_timestamps.clear() |
0 commit comments