from qtpy import QtCore
from QVideo.lib import QCameraTree
from QVideo.cameras.OpenCV import QOpenCVCamera
__all__ = ['QOpenCVTree']
[docs]
class QOpenCVTree(QCameraTree):
'''Camera control tree for :class:`~QVideo.cameras.OpenCV.QOpenCVCamera`.
Extends :class:`~QVideo.lib.QCameraTree.QCameraTree` with a
``resolution`` dropdown populated from the formats reported by
:class:`~QVideo.cameras.OpenCV._devices.QOpenCVDevices`. Selecting an
entry atomically updates width, height, and frame rate on the live
device without stopping the video source.
Width, height, and fps are displayed as read-only fields. All format
changes go through the ``resolution`` dropdown.
When no format information is available (e.g. when
:class:`~QtMultimedia.QMediaDevices` is absent and probing fails),
the dropdown is omitted and the tree falls back to the standard
read-only width/height display.
Parameters
----------
camera : QOpenCVCamera or None
Camera instance to use. If ``None``, a new
:class:`~QVideo.cameras.OpenCV.QOpenCVCamera` is created from the
keyword arguments below.
cameraID : int
Index of the camera device to open. Used only when *camera* is
``None``. Default: ``0``.
mirrored : bool
Flip the image horizontally. Used only when *camera* is ``None``.
Default: ``False``.
flipped : bool
Flip the image vertically. Used only when *camera* is ``None``.
Default: ``False``.
gray : bool
Open in grayscale mode. Used only when *camera* is ``None``.
Default: ``False``.
*args :
Positional arguments forwarded to
:class:`~QVideo.lib.QCameraTree.QCameraTree`.
**kwargs :
Keyword arguments forwarded to
:class:`~QVideo.lib.QCameraTree.QCameraTree`.
'''
def __init__(self, *args,
camera: QOpenCVCamera | None = None,
cameraID: int = 0,
mirrored: bool = False,
flipped: bool = False,
gray: bool = False,
**kwargs) -> None:
if camera is None:
camera = QOpenCVCamera(cameraID=cameraID,
mirrored=mirrored,
flipped=flipped,
gray=gray)
super().__init__(camera, *args, **kwargs)
for key in ('width', 'height'):
if key in self._parameters:
self._parameters[key].setOpts(enabled=False)
# fps is controlled by the resolution dropdown when formats are known
if self._hasFormats and 'fps' in self._parameters:
self._parameters['fps'].setOpts(enabled=False)
def _createTree(self, description=None) -> None:
'''Build the parameter tree, prepending a resolution enum when formats
are available.'''
if description is None:
description = self._defaultDescription(self.camera)
formats = getattr(self.camera, '_formats', [])
self._hasFormats = bool(formats)
if formats:
description = [self._formatEntry(formats)] + [
d for d in description if d.get('name') != 'resolution'
]
super()._createTree(description)
def _formatEntry(self, formats: list) -> dict:
'''Build the ``resolution`` list-parameter description dict.'''
current_w = self.camera.width
current_h = self.camera.height
values: dict[str, tuple] = {}
current_value = None
for w, h, _min_fps, max_fps in formats:
label = f'{w}\u00d7{h} @ {max_fps:.0f} Hz'
val = (w, h, float(max_fps))
values[label] = val
if w == current_w and h == current_h and current_value is None:
current_value = val
if current_value is None:
current_value = next(iter(values.values()))
return {'name': 'resolution',
'type': 'list',
'limits': values,
'value': current_value,
'default': current_value}
@QtCore.Slot(object, object)
def _sync(self, root, changes) -> None:
'''Handle parameter changes, routing ``resolution`` entries here.
A ``resolution`` change is applied directly to the camera (width,
height, fps) without going through the base-class ``camera.set``
dispatch, since ``resolution`` is not a registered camera property.
All other changes are forwarded to the base class.
'''
if self._ignoreSync:
return
fmt_value = None
other = []
for param, change, value in changes:
if change == 'value' and param.name() == 'resolution':
fmt_value = value
else:
other.append((param, change, value))
if fmt_value is not None:
w, h, fps = fmt_value
self.camera.set('width', w)
self.camera.set('height', h)
self.camera.set('fps', fps)
self._ignoreSync = True
for key, val in self.camera.settings.items():
if key != 'resolution':
self.set(key, val)
self._ignoreSync = False
if other:
super()._sync(root, other)
if __name__ == '__main__': # pragma: no cover
QOpenCVTree.example()