Source code for QVideo.filters.edge

'''Canny edge-detection filter and companion Qt widget.'''
from qtpy import QtCore, QtWidgets
from pyqtgraph import SpinBox
from QVideo.lib.QVideoFilter import VideoFilter, QVideoFilter
from QVideo.lib.videotypes import Image
import cv2
import logging


logger = logging.getLogger(__name__)

__all__ = ['EdgeFilter', 'QEdgeFilter']


[docs] class EdgeFilter(VideoFilter): '''Canny edge detector. Converts color input to grayscale, then applies OpenCV's Canny edge-detection algorithm. Parameters ---------- low : int Lower hysteresis threshold. Must be at least 1 and strictly less than *high*. Default: ``50``. high : int Upper hysteresis threshold. Must be at least 2 and strictly greater than *low*. Default: ``150``. Notes ----- Both thresholds are passed directly to :func:`cv2.Canny`. OpenCV recommends a 2:1 or 3:1 high-to-low ratio for typical images. ''' def __init__(self, low: int = 50, high: int = 150) -> None: super().__init__() self._low = 1 self._high = 2 self.high = high self.low = low @property def low(self) -> int: '''Lower Canny threshold (≥ 1, < high).''' return self._low @low.setter def low(self, low: int) -> None: low = max(1, int(low)) if low >= self._high: logger.warning(f'low ({low}) must be less than ' f'high ({self._high}); ignoring') return self._low = low @property def high(self) -> int: '''Upper Canny threshold (≥ 2, > low).''' return self._high @high.setter def high(self, high: int) -> None: high = max(2, int(high)) if high <= self._low: logger.warning(f'high ({high}) must be greater than ' f'low ({self._low}); ignoring') return self._high = high
[docs] def add(self, image: Image) -> None: '''Convert color input to grayscale and store the result. Parameters ---------- image : Image Input frame. 3-D (color) arrays are converted to grayscale; 2-D arrays are stored unchanged. ''' if image.ndim == 3: self.data = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) else: self.data = image
[docs] def to_code(self) -> 'FilterCode': from QVideo.lib.QVideoFilter import FilterCode return FilterCode( imports=frozenset({'import cv2'}), lines=[ 'if image.ndim == 3:', ' image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)', f'image = cv2.Canny(image, {self._low}, {self._high})', ], comment=f'Canny edges, low={self._low}, high={self._high}', )
[docs] def get(self) -> Image | None: '''Return the Canny edge map of the stored frame. Returns ------- Image or None Edge map, or ``None`` if no frame has been added yet. ''' if self.data is None: return None return cv2.Canny(self.data, self.low, self.high)
[docs] class QEdgeFilter(QVideoFilter): '''Widget for :class:`EdgeFilter` with low- and high-threshold spinboxes. Parameters ---------- parent : QtWidgets.QWidget or None Parent widget. ''' display_name = 'Canny' display_category = 'Edge Detection' def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: super().__init__(parent, 'Canny', EdgeFilter()) def _setupUi(self) -> None: super()._setupUi() self._lowSpinbox = SpinBox(self, prefix='low: ', value=self.filter.low, int=True) self._lowSpinbox.setMinimum(1) self._layout.addWidget(self._lowSpinbox) self._highSpinbox = SpinBox(self, prefix='high: ', value=self.filter.high, int=True) self._highSpinbox.setMinimum(2) self._layout.addWidget(self._highSpinbox) def _connectSignals(self) -> None: super()._connectSignals() self._lowSpinbox.valueChanged.connect(self._setLow) self._highSpinbox.valueChanged.connect(self._setHigh) @QtCore.Slot(object) def _setLow(self, low: int) -> None: '''Set the lower Canny threshold. Passes *low* to :class:`EdgeFilter`, which enforces the constraint ``low < high``, then snaps the spinbox to the accepted value. Parameters ---------- low : int New lower threshold. ''' self.filter.low = low with QtCore.QSignalBlocker(self._lowSpinbox): self._lowSpinbox.setValue(self.filter.low) @QtCore.Slot(object) def _setHigh(self, high: int) -> None: '''Set the upper Canny threshold. Passes *high* to :class:`EdgeFilter`, which enforces the constraint ``high > low``, then snaps the spinbox to the accepted value. Parameters ---------- high : int New upper threshold. ''' self.filter.high = high with QtCore.QSignalBlocker(self._highSpinbox): self._highSpinbox.setValue(self.filter.high)
if __name__ == '__main__': # pragma: no cover QEdgeFilter.example()