Source code for QVideo.dvr.QHDF5Reader
'''HDF5 video reader and threaded playback source.'''
from qtpy import QtCore
from QVideo.lib import QCamera, QVideoReader, QVideoSource
from pathlib import Path
try:
import h5py
except (ImportError, ModuleNotFoundError):
h5py = None
__all__ = ['QHDF5Reader', 'QHDF5Source']
[docs]
class QHDF5Reader(QVideoReader):
'''Video reader for HDF5 files.
Reads frames from an HDF5 file containing an ``images`` group of
timestamped datasets, as written by :class:`QHDF5Writer`.
Parameters
----------
filename : str
Path to the HDF5 file to read.
'''
def _initialize(self) -> bool:
try:
self._file = h5py.File(self.filename, 'r')
self._images = self._file['images']
except (OSError, KeyError):
return False
self._keys = sorted(self._images.keys(), key=float)
self._length = len(self._keys)
if not self._keys:
return False
self._framenumber = 0
self._height, self._width = (
self._images[self._keys[0]][()].shape[0:2])
return True
def _deinitialize(self) -> None:
self._file.close()
[docs]
def read(self) -> QCamera.CameraData:
'''Read the next frame from the HDF5 file.
Returns
-------
tuple[bool, ndarray or None]
``(True, frame)`` on success, ``(False, None)`` when past
the end.
'''
if self._framenumber >= len(self._keys):
return False, None
key = self._keys[self._framenumber]
frame = self._images[key][()]
self._framenumber += 1
return True, frame
[docs]
@QtCore.Slot(int)
def seek(self, framenumber: int) -> None:
'''Advance playback to specified frame number.'''
self._framenumber = framenumber
@property
def fps(self) -> float:
'''Estimated frame rate derived from the recorded timestamps [fps].
Computed as ``(n_frames - 1) / total_duration``. Falls back to
30 fps when fewer than two frames are present or the timestamps
span zero time.
'''
if len(self._keys) < 2:
return 30.
elapsed = float(self._keys[-1]) - float(self._keys[0])
if elapsed <= 0.:
return 30.
return (len(self._keys) - 1) / elapsed
@property
def length(self) -> int:
'''Total number of frames in the HDF5 file.'''
return self._length
@property
def framenumber(self) -> int:
'''Index of the next frame to be returned by :meth:`read`.'''
return self._framenumber
@property
def width(self) -> int:
'''Frame width in pixels.'''
return self._width
@property
def height(self) -> int:
'''Frame height in pixels.'''
return self._height
[docs]
class QHDF5Source(QVideoSource):
'''Video source for HDF5 files.
Parameters
----------
reader : str, Path, or QHDF5Reader
Path to the HDF5 file to read, or an existing
:class:`QHDF5Reader` instance.
'''
def __init__(self, reader: str | Path | QHDF5Reader) -> None:
if isinstance(reader, (str, Path)):
reader = QHDF5Reader(str(reader))
super().__init__(reader)