Provides a key-value store for frame metrics/calculations which can be used for two-pass detection algorithms, as well as saving stats to a CSV file. Analyzing a statistics CSV file is also very useful for finding the optimal algorithm parameters for certain detection methods. Additiona
| 83 | # TODO(v1.0): Relax restriction on metric types only being float or int when loading from disk |
| 84 | # is fully deprecated. |
| 85 | class StatsManager: |
| 86 | """Provides a key-value store for frame metrics/calculations which can be used |
| 87 | for two-pass detection algorithms, as well as saving stats to a CSV file. |
| 88 | |
| 89 | Analyzing a statistics CSV file is also very useful for finding the optimal |
| 90 | algorithm parameters for certain detection methods. Additionally, the data |
| 91 | may be plotted by a graphing module (e.g. matplotlib) by obtaining the |
| 92 | metric of interest for a series of frames by iteratively calling get_metrics(), |
| 93 | after having called the detect_scenes(...) method on the SceneManager object |
| 94 | which owns the given StatsManager instance. |
| 95 | |
| 96 | Only metrics consisting of `float` or `int` should be used currently. |
| 97 | """ |
| 98 | |
| 99 | def __init__(self, base_timecode: int | FrameTimecode | None = None): |
| 100 | """Initialize a new StatsManager. |
| 101 | |
| 102 | Arguments: |
| 103 | base_timecode: Timecode associated with this object. Must not be None (default value |
| 104 | will be removed in a future release). |
| 105 | """ |
| 106 | # Frame metrics keyed by either an `int` frame number or a `FrameTimecode`. Both forms |
| 107 | # hash/compare to the same dict slot (`FrameTimecode.__hash__` returns `frame_num`), so |
| 108 | # public methods accept both interchangeably for the same frame. |
| 109 | self._frame_metrics: dict[int | FrameTimecode, dict[str, float]] = dict() |
| 110 | self._metric_keys: set[str] = set() |
| 111 | self._metrics_updated: bool = False # Flag indicating if metrics require saving. |
| 112 | self._base_timecode: int | FrameTimecode | None = ( |
| 113 | base_timecode # Used for timing calculations. |
| 114 | ) |
| 115 | |
| 116 | @property |
| 117 | def metric_keys(self) -> ty.Iterable[str]: |
| 118 | return self._metric_keys |
| 119 | |
| 120 | def register_metrics(self, metric_keys: ty.Iterable[str]) -> None: |
| 121 | """Register a list of metric keys that will be used by the detector.""" |
| 122 | self._metric_keys = self._metric_keys.union(set(metric_keys)) |
| 123 | |
| 124 | # TODO(https://scenedetect.com/issues/507): We should support the dictionary protocol instead |
| 125 | # of using this bespoke interface. It would be useful for Pandas compatibility as well. |
| 126 | def get_metrics( |
| 127 | self, timecode: int | FrameTimecode, metric_keys: ty.Iterable[str] |
| 128 | ) -> list[ty.Any]: |
| 129 | """Return the requested statistics/metrics for a given timecode. |
| 130 | |
| 131 | Returns: |
| 132 | A list containing the requested frame metrics for the given frame number, ordered as |
| 133 | they are in `metric_keys`. |
| 134 | """ |
| 135 | return [self._get_metric(timecode, metric_key) for metric_key in metric_keys] |
| 136 | |
| 137 | def set_metrics(self, timecode: int | FrameTimecode, metric_kv_dict: dict[str, ty.Any]) -> None: |
| 138 | """Set Metrics: Sets the provided statistics/metrics for a given frame. |
| 139 | |
| 140 | Arguments: |
| 141 | timecode: Timecode to set metrics for. |
| 142 | metric_kv_dict: Key value mapping of metrics to their values for `timecode`. |
no outgoing calls