Source code for QVideo.lib.QFilterBank
'''Composable pipeline of VideoFilter stages between a source and a display.'''
import logging
from collections.abc import Iterator
from qtpy import QtWidgets
from QVideo.lib.QVideoFilter import QVideoFilter
from QVideo.lib.videotypes import Image
import QVideo.filters as videofilters
__all__ = ['QFilterBank']
logger = logging.getLogger(__name__)
[docs]
class QFilterBank(QtWidgets.QGroupBox):
'''A vertical stack of :class:`~QVideo.lib.VideoFilter.QVideoFilter` widgets.
Applies a sequence of video filters to each frame in order.
Filters are added and removed at runtime via :meth:`register` and
:meth:`deregister`, and may also be looked up by name from the
:mod:`QVideo.filters` package using :meth:`registerByName`.
Parameters
----------
parent : QtWidgets.QWidget or None
Parent widget.
'''
def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
super().__init__('Display Filters', parent)
self._filters: list[QVideoFilter] = []
self._setupUi()
def _setupUi(self) -> None:
self._layout = QtWidgets.QVBoxLayout(self)
def __iter__(self) -> Iterator[QVideoFilter]:
return iter(self._filters)
@property
def filters(self) -> list[QVideoFilter]:
'''Read-only view of the registered filters.'''
return list(self._filters)
def __call__(self, image: Image) -> Image | None:
'''Apply all registered filters to *image* in order.
Parameters
----------
image : Image
Input frame.
Returns
-------
Image or None
Frame after all enabled filters have been applied.
'''
for video_filter in self:
image = video_filter(image)
return image
[docs]
def register(self, video_filter: QVideoFilter) -> None:
'''Add a filter to the end of the pipeline.
Parameters
----------
video_filter : QVideoFilter
Filter widget to add.
Raises
------
TypeError
If *video_filter* is not a :class:`~QVideo.lib.VideoFilter.QVideoFilter`.
'''
if not isinstance(video_filter, QVideoFilter):
raise TypeError('expected QVideoFilter, '
f'got {type(video_filter).__name__}')
self._filters.append(video_filter)
self._layout.addWidget(video_filter)
[docs]
def deregister(self, video_filter: QVideoFilter) -> None:
'''Remove a filter from the pipeline.
Parameters
----------
video_filter : QVideoFilter
Filter widget to remove.
Raises
------
ValueError
If *video_filter* is not currently registered.
'''
self._filters.remove(video_filter)
self._layout.removeWidget(video_filter)
video_filter.setParent(None)
[docs]
def registerByName(self, name: str) -> None:
'''Instantiate a filter by class name and add it to the pipeline.
Looks up *name* in the :mod:`QVideo.filters` package and
registers an instance if found.
Parameters
----------
name : str
Name of a :class:`~QVideo.lib.VideoFilter.QVideoFilter`
subclass exported by :mod:`QVideo.filters`.
Raises
------
ValueError
If *name* is not found in :mod:`QVideo.filters` or does not
refer to a :class:`~QVideo.lib.VideoFilter.QVideoFilter`
subclass.
'''
cls = getattr(videofilters, name, None)
if cls is None or not (isinstance(cls, type) and
issubclass(cls, QVideoFilter)):
raise ValueError(f'{name!r} is not a known filter')
try:
self.register(cls())
except Exception as e:
logger.warning(f'Failed to instantiate filter {name!r}: {e}')