Source code for QVideo.filters.laplacian

'''Laplacian 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 numpy as np
import cv2

__all__ = ['LaplacianFilter', 'QLaplacianFilter']


[docs] class LaplacianFilter(VideoFilter): '''Laplacian (∇²) edge detector with optional Gaussian pre-blur. Converts each frame to grayscale, optionally applies a Gaussian blur to reduce noise sensitivity, then computes the discrete Laplacian. The absolute value is returned as a uint8 image; edges appear bright at intensity transitions. Setting *sigma* > 0 implements the Laplacian-of-Gaussian (LoG) operator. Parameters ---------- ksize : int Laplacian kernel size [pixels]. Must be odd and ≥ 1. Default: ``3``. sigma : float Standard deviation of the optional Gaussian pre-blur [pixels]. ``0`` skips blurring. Default: ``0``. ''' def __init__(self, ksize: int = 3, sigma: float = 0.0) -> None: super().__init__() self.ksize = ksize self.sigma = sigma @property def ksize(self) -> int: '''Laplacian kernel size [pixels], always odd and ≥ 1.''' return self._ksize @ksize.setter def ksize(self, value: int) -> None: value = max(1, int(value)) self._ksize = value + (1 - value % 2) @property def sigma(self) -> float: '''Gaussian pre-blur standard deviation [pixels]; 0 skips blur.''' return self._sigma @sigma.setter def sigma(self, value: float) -> None: self._sigma = max(0.0, float(value))
[docs] def to_code(self) -> 'FilterCode': from QVideo.lib.QVideoFilter import FilterCode lines = [ 'if image.ndim == 3:', ' image = image.mean(axis=2).astype(np.uint8)', ] imports = frozenset({'import cv2', 'import numpy as np'}) if self._sigma > 0: lines.append(f'image = cv2.GaussianBlur(image, (0, 0), {self._sigma})') lines.append( f'image = cv2.convertScaleAbs(cv2.Laplacian(image, cv2.CV_32F, ksize={self._ksize}))' ) suffix = f', σ={self._sigma}' if self._sigma > 0 else '' return FilterCode( imports=imports, lines=lines, comment=f'Laplacian edges, k={self._ksize}{suffix}', )
[docs] def get(self) -> Image | None: '''Return the Laplacian edge map of the stored frame. Returns ------- Image or None uint8 edge map, or ``None`` if no frame has been added. ''' if self.data is None: return None gray = (self.data.mean(axis=2).astype(np.uint8) if self.data.ndim == 3 else self.data) if self._sigma > 0: gray = cv2.GaussianBlur(gray, (0, 0), self._sigma) result = cv2.Laplacian(gray, cv2.CV_32F, ksize=self._ksize) return cv2.convertScaleAbs(result)
[docs] class QLaplacianFilter(QVideoFilter): '''Widget for :class:`LaplacianFilter` with kernel size and sigma spinboxes. Parameters ---------- parent : QtWidgets.QWidget or None Parent widget. ''' display_name = 'Laplacian' display_category = 'Edge Detection' def __init__(self, parent: QtWidgets.QWidget | None = None) -> None: super().__init__(parent, 'Laplacian Edge Detection', LaplacianFilter()) def _setupUi(self) -> None: super()._setupUi() self._ksizeBox = SpinBox(value=self.filter.ksize, bounds=(1, None), step=2, int=True, prefix='k ') self._layout.addWidget(self._ksizeBox) self._sigmaBox = SpinBox(value=self.filter.sigma, bounds=(0, None), step=0.5, prefix='σ ') self._layout.addWidget(self._sigmaBox) def _connectSignals(self) -> None: super()._connectSignals() self._ksizeBox.valueChanged.connect(self._setKsize) self._sigmaBox.valueChanged.connect(self._setSigma) @QtCore.Slot(object) def _setKsize(self, value: int) -> None: self.filter.ksize = value with QtCore.QSignalBlocker(self._ksizeBox): self._ksizeBox.setValue(self.filter.ksize) @QtCore.Slot(object) def _setSigma(self, value: float) -> None: self.filter.sigma = value
if __name__ == '__main__': # pragma: no cover QLaplacianFilter.example()