QVideo 3.2.3__tar.gz → 3.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. {qvideo-3.2.3 → qvideo-3.4.0}/PKG-INFO +2 -2
  2. {qvideo-3.2.3 → qvideo-3.4.0}/QVideo.egg-info/PKG-INFO +2 -2
  3. {qvideo-3.2.3 → qvideo-3.4.0}/QVideo.egg-info/SOURCES.txt +7 -1
  4. {qvideo-3.2.3 → qvideo-3.4.0}/README.md +1 -1
  5. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/OpenCV/_resolution_tree.py +4 -0
  6. {qvideo-3.2.3 → qvideo-3.4.0}/demos/__init__.py +7 -0
  7. {qvideo-3.2.3 → qvideo-3.4.0}/demos/demo.py +4 -2
  8. {qvideo-3.2.3 → qvideo-3.4.0}/demos/filterdemo.py +1 -11
  9. qvideo-3.4.0/demos/trackpydemo.py +53 -0
  10. {qvideo-3.2.3 → qvideo-3.4.0}/dvr/QDVRWidget.py +3 -2
  11. {qvideo-3.2.3 → qvideo-3.4.0}/filters/QBlurFilter.py +2 -2
  12. {qvideo-3.2.3 → qvideo-3.4.0}/filters/QEdgeFilter.py +6 -6
  13. {qvideo-3.2.3 → qvideo-3.4.0}/filters/QRGBFilter.py +2 -2
  14. {qvideo-3.2.3 → qvideo-3.4.0}/filters/QSampleHold.py +7 -5
  15. qvideo-3.4.0/filters/QYOLOFilter.py +89 -0
  16. {qvideo-3.2.3 → qvideo-3.4.0}/filters/_MedianBase.py +1 -1
  17. {qvideo-3.2.3 → qvideo-3.4.0}/filters/__init__.py +2 -0
  18. {qvideo-3.2.3 → qvideo-3.4.0}/lib/QFilterBank.py +10 -3
  19. qvideo-3.2.3/lib/VideoFilter.py → qvideo-3.4.0/lib/QVideoFilter.py +14 -14
  20. {qvideo-3.2.3 → qvideo-3.4.0}/lib/QVideoScreen.py +14 -8
  21. {qvideo-3.2.3 → qvideo-3.4.0}/lib/__init__.py +3 -3
  22. {qvideo-3.2.3 → qvideo-3.4.0}/lib/resolutions.py +3 -1
  23. qvideo-3.4.0/overlays/__init__.py +5 -0
  24. qvideo-3.4.0/overlays/trackpy.py +244 -0
  25. qvideo-3.4.0/overlays/yolo.py +21 -0
  26. {qvideo-3.2.3 → qvideo-3.4.0}/pyproject.toml +3 -1
  27. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_demo.py +8 -3
  28. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qblurfilter.py +1 -1
  29. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qdvrwidget.py +2 -3
  30. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qedgefilter.py +1 -1
  31. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qfilterbank.py +3 -3
  32. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qopencvresolutiontree.py +15 -1
  33. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qrgbfilter.py +1 -1
  34. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qsamplehold.py +1 -1
  35. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_resolutions.py +20 -4
  36. qvideo-3.4.0/tests/test_trackpy_overlay.py +346 -0
  37. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_videofilter.py +6 -6
  38. {qvideo-3.2.3 → qvideo-3.4.0}/LICENSE.md +0 -0
  39. {qvideo-3.2.3 → qvideo-3.4.0}/QCamcorder.py +0 -0
  40. {qvideo-3.2.3 → qvideo-3.4.0}/QVideo.egg-info/dependency_links.txt +0 -0
  41. {qvideo-3.2.3 → qvideo-3.4.0}/QVideo.egg-info/requires.txt +0 -0
  42. {qvideo-3.2.3 → qvideo-3.4.0}/QVideo.egg-info/top_level.txt +0 -0
  43. {qvideo-3.2.3 → qvideo-3.4.0}/__init__.py +0 -0
  44. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Basler/__init__.py +0 -0
  45. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Basler/_camera.py +0 -0
  46. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Basler/_tree.py +0 -0
  47. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Flir/QListFlirCameras.py +0 -0
  48. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Flir/__init__.py +0 -0
  49. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Flir/_camera.py +0 -0
  50. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Flir/_tree.py +0 -0
  51. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Genicam/__init__.py +0 -0
  52. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Genicam/_camera.py +0 -0
  53. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Genicam/_tree.py +0 -0
  54. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/IDS/__init__.py +0 -0
  55. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/IDS/_camera.py +0 -0
  56. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/IDS/_tree.py +0 -0
  57. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/MV/__init__.py +0 -0
  58. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/MV/_camera.py +0 -0
  59. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/MV/_tree.py +0 -0
  60. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Noise/__init__.py +0 -0
  61. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Noise/_camera.py +0 -0
  62. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Noise/_tree.py +0 -0
  63. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/OpenCV/QListCVCameras.py +0 -0
  64. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/OpenCV/__init__.py +0 -0
  65. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/OpenCV/_camera.py +0 -0
  66. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/OpenCV/_tree.py +0 -0
  67. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Picamera/__init__.py +0 -0
  68. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Picamera/_camera.py +0 -0
  69. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Picamera/_tree.py +0 -0
  70. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Vimbax/__init__.py +0 -0
  71. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Vimbax/_camera.py +0 -0
  72. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/Vimbax/_tree.py +0 -0
  73. {qvideo-3.2.3 → qvideo-3.4.0}/cameras/__init__.py +0 -0
  74. {qvideo-3.2.3 → qvideo-3.4.0}/demos/ROIdemo.py +0 -0
  75. {qvideo-3.2.3 → qvideo-3.4.0}/dvr/QHDF5Reader.py +0 -0
  76. {qvideo-3.2.3 → qvideo-3.4.0}/dvr/QHDF5Writer.py +0 -0
  77. {qvideo-3.2.3 → qvideo-3.4.0}/dvr/QOpenCVReader.py +0 -0
  78. {qvideo-3.2.3 → qvideo-3.4.0}/dvr/QOpenCVWriter.py +0 -0
  79. {qvideo-3.2.3 → qvideo-3.4.0}/dvr/__init__.py +0 -0
  80. {qvideo-3.2.3 → qvideo-3.4.0}/dvr/icons_rc.py +0 -0
  81. {qvideo-3.2.3 → qvideo-3.4.0}/dvr/icons_rc_qt6.py +0 -0
  82. {qvideo-3.2.3 → qvideo-3.4.0}/filters/Median.py +0 -0
  83. {qvideo-3.2.3 → qvideo-3.4.0}/filters/MoMedian.py +0 -0
  84. {qvideo-3.2.3 → qvideo-3.4.0}/filters/Normalize.py +0 -0
  85. {qvideo-3.2.3 → qvideo-3.4.0}/lib/QCamera.py +0 -0
  86. {qvideo-3.2.3 → qvideo-3.4.0}/lib/QCameraTree.py +0 -0
  87. {qvideo-3.2.3 → qvideo-3.4.0}/lib/QFPSMeter.py +0 -0
  88. {qvideo-3.2.3 → qvideo-3.4.0}/lib/QListCameras.py +0 -0
  89. {qvideo-3.2.3 → qvideo-3.4.0}/lib/QVideoReader.py +0 -0
  90. {qvideo-3.2.3 → qvideo-3.4.0}/lib/QVideoSource.py +0 -0
  91. {qvideo-3.2.3 → qvideo-3.4.0}/lib/QVideoWriter.py +0 -0
  92. {qvideo-3.2.3 → qvideo-3.4.0}/lib/chooser.py +0 -0
  93. {qvideo-3.2.3 → qvideo-3.4.0}/lib/clickable.py +0 -0
  94. {qvideo-3.2.3 → qvideo-3.4.0}/lib/types.py +0 -0
  95. {qvideo-3.2.3 → qvideo-3.4.0}/setup.cfg +0 -0
  96. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_chooser.py +0 -0
  97. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_clickable.py +0 -0
  98. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_filterdemo.py +0 -0
  99. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_icons_rc.py +0 -0
  100. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_median.py +0 -0
  101. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_momedian.py +0 -0
  102. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_normalize.py +0 -0
  103. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qbaslercamera.py +0 -0
  104. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qcamcorder.py +0 -0
  105. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qcamera.py +0 -0
  106. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qcameratree.py +0 -0
  107. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qflircamera.py +0 -0
  108. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qfpsmeter.py +0 -0
  109. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qgenicamcamera.py +0 -0
  110. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qgenicamtree.py +0 -0
  111. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qhdf5reader.py +0 -0
  112. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qhdf5writer.py +0 -0
  113. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qidscamera.py +0 -0
  114. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qlistcameras.py +0 -0
  115. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qlistcvcameras.py +0 -0
  116. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qmvcamera.py +0 -0
  117. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qnoisecamera.py +0 -0
  118. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qnoisetree.py +0 -0
  119. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qopencvcamera.py +0 -0
  120. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qopencvreader.py +0 -0
  121. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qopencvtree.py +0 -0
  122. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qopencvwriter.py +0 -0
  123. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qpicamera.py +0 -0
  124. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qvideoreader.py +0 -0
  125. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qvideoscreen.py +0 -0
  126. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qvideosource.py +0 -0
  127. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qvideowriter.py +0 -0
  128. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_qvimbaxcamera.py +0 -0
  129. {qvideo-3.2.3 → qvideo-3.4.0}/tests/test_roidemo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QVideo
3
- Version: 3.2.3
3
+ Version: 3.4.0
4
4
  Summary: PyQt-based framework for integrating video cameras into research applications
5
5
  Author-email: "David G. Grier" <david.grier@nyu.edu>
6
6
  License: GPL-3.0-or-later
@@ -157,5 +157,5 @@ additional code needed.
157
157
  ## Acknowledgements
158
158
 
159
159
  Work on this project at New York University is supported by the National
160
- Science Foundation of the United States under award number DMR-2104837 and
160
+ Science Foundation of the United States under award number DMR-2428983 and
161
161
  by an award from the TAC Program of New York University.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QVideo
3
- Version: 3.2.3
3
+ Version: 3.4.0
4
4
  Summary: PyQt-based framework for integrating video cameras into research applications
5
5
  Author-email: "David G. Grier" <david.grier@nyu.edu>
6
6
  License: GPL-3.0-or-later
@@ -157,5 +157,5 @@ additional code needed.
157
157
  ## Acknowledgements
158
158
 
159
159
  Work on this project at New York University is supported by the National
160
- Science Foundation of the United States under award number DMR-2104837 and
160
+ Science Foundation of the United States under award number DMR-2428983 and
161
161
  by an award from the TAC Program of New York University.
@@ -45,6 +45,7 @@ demos/ROIdemo.py
45
45
  demos/__init__.py
46
46
  demos/demo.py
47
47
  demos/filterdemo.py
48
+ demos/trackpydemo.py
48
49
  dvr/QDVRWidget.py
49
50
  dvr/QHDF5Reader.py
50
51
  dvr/QHDF5Writer.py
@@ -60,6 +61,7 @@ filters/QBlurFilter.py
60
61
  filters/QEdgeFilter.py
61
62
  filters/QRGBFilter.py
62
63
  filters/QSampleHold.py
64
+ filters/QYOLOFilter.py
63
65
  filters/_MedianBase.py
64
66
  filters/__init__.py
65
67
  lib/QCamera.py
@@ -67,16 +69,19 @@ lib/QCameraTree.py
67
69
  lib/QFPSMeter.py
68
70
  lib/QFilterBank.py
69
71
  lib/QListCameras.py
72
+ lib/QVideoFilter.py
70
73
  lib/QVideoReader.py
71
74
  lib/QVideoScreen.py
72
75
  lib/QVideoSource.py
73
76
  lib/QVideoWriter.py
74
- lib/VideoFilter.py
75
77
  lib/__init__.py
76
78
  lib/chooser.py
77
79
  lib/clickable.py
78
80
  lib/resolutions.py
79
81
  lib/types.py
82
+ overlays/__init__.py
83
+ overlays/trackpy.py
84
+ overlays/yolo.py
80
85
  tests/test_chooser.py
81
86
  tests/test_clickable.py
82
87
  tests/test_demo.py
@@ -120,4 +125,5 @@ tests/test_qvideowriter.py
120
125
  tests/test_qvimbaxcamera.py
121
126
  tests/test_resolutions.py
122
127
  tests/test_roidemo.py
128
+ tests/test_trackpy_overlay.py
123
129
  tests/test_videofilter.py
@@ -114,5 +114,5 @@ additional code needed.
114
114
  ## Acknowledgements
115
115
 
116
116
  Work on this project at New York University is supported by the National
117
- Science Foundation of the United States under award number DMR-2104837 and
117
+ Science Foundation of the United States under award number DMR-2428983 and
118
118
  by an award from the TAC Program of New York University.
@@ -117,8 +117,12 @@ class QOpenCVResolutionTree(QCameraTree):
117
117
  if change == 'value' and param.name() == 'resolution':
118
118
  if value in self._resolutionMap:
119
119
  w, h = self._resolutionMap[value]
120
+ fps = (self.camera.fps
121
+ if 'fps' in self.camera._properties else None)
120
122
  self.camera.set('width', w)
121
123
  self.camera.set('height', h)
124
+ if fps is not None:
125
+ self.camera.set('fps', fps)
122
126
  return
123
127
  super()._sync(root, changes)
124
128
 
@@ -21,6 +21,11 @@ ROIdemo
21
21
  rectangular ROI overlay. Only the cropped region is saved when
22
22
  the DVR records, making it easy to capture a sub-region of interest.
23
23
 
24
+ trackpydemo
25
+ Extends :mod:`demo` with a :class:`~QVideo.overlays.trackpy.QTrackpyWidget`
26
+ panel so that trackpy particle positions are overlaid on the live feed
27
+ in real time and locate results are available via a signal.
28
+
24
29
  Running
25
30
  -------
26
31
  Each demo can be launched directly::
@@ -28,6 +33,7 @@ Each demo can be launched directly::
28
33
  python -m QVideo.demos.demo
29
34
  python -m QVideo.demos.filterdemo
30
35
  python -m QVideo.demos.ROIdemo
36
+ python -m QVideo.demos.trackpydemo
31
37
 
32
38
  Camera selection
33
39
  ----------------
@@ -59,4 +65,5 @@ Example::
59
65
  from .demo import Demo
60
66
  from .filterdemo import FilterDemo
61
67
  from .ROIdemo import ROIFilter, ROIDemo
68
+ from .trackpydemo import TrackpyDemo
62
69
 
@@ -7,7 +7,7 @@ Run directly::
7
7
  '''
8
8
 
9
9
  from QVideo.lib import QVideoScreen, QCameraTree
10
- from pyqtgraph.Qt.QtWidgets import QWidget, QHBoxLayout
10
+ from pyqtgraph.Qt.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout
11
11
 
12
12
 
13
13
  __all__ = ['Demo']
@@ -40,9 +40,11 @@ class Demo(QWidget):
40
40
  def _setupUi(self) -> None:
41
41
  layout = QHBoxLayout(self)
42
42
  layout.addWidget(self.screen)
43
- layout.addWidget(self.cameraTree)
43
+ self._controls = QVBoxLayout()
44
+ layout.addLayout(self._controls)
44
45
  layout.setStretch(0, 1) # screen takes all surplus horizontal space
45
46
  layout.setStretch(1, 0) # controls stay at their natural width
47
+ self._controls.addWidget(self.cameraTree)
46
48
 
47
49
 
48
50
  def main() -> None: # pragma: no cover
@@ -8,7 +8,6 @@ Run directly::
8
8
 
9
9
  from QVideo.demos.demo import Demo
10
10
  from QVideo.lib import QCameraTree
11
- from pyqtgraph.Qt.QtWidgets import QHBoxLayout, QVBoxLayout
12
11
 
13
12
 
14
13
  __all__ = ['FilterDemo']
@@ -38,15 +37,6 @@ class FilterDemo(Demo):
38
37
  super().__init__(cameraTree, **kwargs)
39
38
  self.addFilters(filters)
40
39
 
41
- def _setupUi(self) -> None:
42
- layout = QHBoxLayout(self)
43
- layout.addWidget(self.screen)
44
- self._controls = QVBoxLayout()
45
- layout.addLayout(self._controls)
46
- layout.setStretch(0, 1) # screen takes all surplus horizontal space
47
- layout.setStretch(1, 0) # controls stay at their natural width
48
- self._controls.addWidget(self.cameraTree)
49
-
50
40
  def addFilters(self, filters: list[str]) -> None:
51
41
  '''Register filters by name and show the filter bank.
52
42
 
@@ -68,7 +58,7 @@ def main() -> None: # pragma: no cover
68
58
 
69
59
  pg.mkQApp()
70
60
  camera = choose_camera().start()
71
- filters = 'QRGBFilter QBlurFilter QSampleHold QEdgeFilter'.split()
61
+ filters = 'QYOLOFilter QRGBFilter QSampleHold QBlurFilter QEdgeFilter'.split()
72
62
  widget = FilterDemo(camera, filters)
73
63
  widget.show()
74
64
  pg.exec()
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env python3
2
+ '''Demo combining a live video feed, camera controls, and a trackpy overlay.
3
+
4
+ Run directly::
5
+
6
+ python -m QVideo.demos.trackpydemo
7
+ '''
8
+
9
+ from QVideo.demos.demo import Demo
10
+ from QVideo.lib import QCameraTree
11
+ from QVideo.overlays import QTrackpyWidget
12
+
13
+
14
+ __all__ = ['TrackpyDemo']
15
+
16
+
17
+ class TrackpyDemo(Demo):
18
+ '''Extends :class:`~QVideo.demos.demo.Demo` with a trackpy overlay.
19
+
20
+ Adds a :class:`~QVideo.overlays.trackpy.QTrackpyWidget` control panel
21
+ below the camera control tree. Detected particle positions are
22
+ rendered in real time as a scatter-plot overlay on the video screen.
23
+
24
+ Parameters
25
+ ----------
26
+ cameraTree : QCameraTree
27
+ The camera control tree widget to display alongside the video feed.
28
+ **kwargs :
29
+ Additional keyword arguments forwarded to :class:`~QVideo.demos.demo.Demo`.
30
+ '''
31
+
32
+ def __init__(self, cameraTree: QCameraTree, **kwargs) -> None:
33
+ super().__init__(cameraTree, **kwargs)
34
+ self.trackpy = QTrackpyWidget(self)
35
+ self.trackpy.source = self.screen.source
36
+ self.trackpy.attachTo(self.screen)
37
+ self._controls.addWidget(self.trackpy)
38
+
39
+
40
+ def main() -> None: # pragma: no cover
41
+ '''Launch the trackpy demo with an interactively chosen camera.'''
42
+ import pyqtgraph as pg
43
+ from QVideo.lib import choose_camera
44
+
45
+ pg.mkQApp()
46
+ camera = choose_camera().start()
47
+ widget = TrackpyDemo(camera)
48
+ widget.show()
49
+ pg.exec()
50
+
51
+
52
+ if __name__ == '__main__': # pragma: no cover
53
+ main()
@@ -120,7 +120,7 @@ class QDVRWidget(QtWidgets.QFrame):
120
120
  def __init__(self,
121
121
  *args,
122
122
  source: QVideoSource | None = None,
123
- filename: str | None = None,
123
+ filename: str = '',
124
124
  **kwargs) -> None:
125
125
  super().__init__(*args, **kwargs)
126
126
  self._source = None
@@ -130,7 +130,7 @@ class QDVRWidget(QtWidgets.QFrame):
130
130
  self._setupUi()
131
131
  self._connectSignals()
132
132
  self.source = source
133
- self.filename = filename if filename is not None else str(
133
+ self.filename = filename if filename else str(
134
134
  Path.home() / self.FILENAME)
135
135
 
136
136
  def _setupUi(self) -> None:
@@ -335,6 +335,7 @@ class QDVRWidget(QtWidgets.QFrame):
335
335
  return
336
336
  if not (self.isRecording() or self.isPlaying()):
337
337
  self.saveEdit.setText(filename)
338
+ self.playname = filename
338
339
 
339
340
  @QtCore.pyqtProperty(str)
340
341
  def playname(self) -> str:
@@ -1,6 +1,6 @@
1
1
  from pyqtgraph.Qt import QtCore, QtWidgets
2
2
  from pyqtgraph import SpinBox
3
- from QVideo.lib.VideoFilter import QVideoFilter, VideoFilter
3
+ from QVideo.lib.QVideoFilter import VideoFilter, QVideoFilter
4
4
  from QVideo.lib.types import Image
5
5
  import cv2
6
6
 
@@ -67,7 +67,7 @@ class QBlurFilter(QVideoFilter):
67
67
  '''
68
68
 
69
69
  def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
70
- super().__init__('Gaussian Blur', parent, BlurFilter())
70
+ super().__init__(parent, 'Gaussian Blur', BlurFilter())
71
71
 
72
72
  def _setupUi(self) -> None:
73
73
  super()._setupUi()
@@ -1,6 +1,6 @@
1
1
  from pyqtgraph.Qt import QtCore, QtWidgets
2
2
  from pyqtgraph import SpinBox
3
- from QVideo.lib.VideoFilter import QVideoFilter, VideoFilter
3
+ from QVideo.lib.QVideoFilter import VideoFilter, QVideoFilter
4
4
  from QVideo.lib.types import Image
5
5
  import cv2
6
6
  import logging
@@ -49,8 +49,8 @@ class EdgeFilter(VideoFilter):
49
49
  def low(self, low: int) -> None:
50
50
  low = max(1, int(low))
51
51
  if low >= self._high:
52
- logger.warning('low (%d) must be less than high (%d); '
53
- 'ignoring', low, self._high)
52
+ logger.warning(f'low ({low}) must be less than '
53
+ f'high ({self._high}); ignoring')
54
54
  return
55
55
  self._low = low
56
56
 
@@ -63,8 +63,8 @@ class EdgeFilter(VideoFilter):
63
63
  def high(self, high: int) -> None:
64
64
  high = max(2, int(high))
65
65
  if high <= self._low:
66
- logger.warning('high (%d) must be greater than low (%d); '
67
- 'ignoring', high, self._low)
66
+ logger.warning(f'high ({high}) must be greater than '
67
+ f'low ({self._low}); ignoring')
68
68
  return
69
69
  self._high = high
70
70
 
@@ -106,7 +106,7 @@ class QEdgeFilter(QVideoFilter):
106
106
  '''
107
107
 
108
108
  def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
109
- super().__init__('Canny Edge Detection', parent, EdgeFilter())
109
+ super().__init__(parent, 'Canny Edge Detection', EdgeFilter())
110
110
 
111
111
  def _setupUi(self) -> None:
112
112
  super()._setupUi()
@@ -1,5 +1,5 @@
1
1
  from pyqtgraph.Qt import QtCore, QtWidgets
2
- from QVideo.lib.VideoFilter import QVideoFilter, VideoFilter
2
+ from QVideo.lib.QVideoFilter import VideoFilter, QVideoFilter
3
3
  from QVideo.lib.types import Image
4
4
 
5
5
 
@@ -61,7 +61,7 @@ class QRGBFilter(QVideoFilter):
61
61
  '''
62
62
 
63
63
  def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
64
- super().__init__('Color Channel', parent, RGBFilter())
64
+ super().__init__(parent, 'Color Channel', RGBFilter())
65
65
 
66
66
  def _setupUi(self) -> None:
67
67
  super()._setupUi()
@@ -1,7 +1,7 @@
1
1
  from pyqtgraph.Qt import QtCore, QtWidgets
2
- from QVideo.lib.VideoFilter import QVideoFilter
3
- from QVideo.lib.types import Image
4
2
  from QVideo.filters.Normalize import Normalize
3
+ from QVideo.lib.QVideoFilter import QVideoFilter
4
+ from QVideo.lib.types import Image
5
5
 
6
6
 
7
7
  __all__ = ['SampleHold', 'QSampleHold']
@@ -87,14 +87,16 @@ class QSampleHold(QVideoFilter):
87
87
  '''
88
88
 
89
89
  def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
90
- super().__init__('Sample and Hold', parent, SampleHold())
90
+ super().__init__(parent, 'Sample and Hold', SampleHold())
91
91
 
92
92
  def _setupUi(self) -> None:
93
93
  super()._setupUi()
94
94
  self._layout.addWidget(QtWidgets.QLabel('order'))
95
- self._orderButtons = [QtWidgets.QRadioButton(str(n)) for n in (1, 2, 3)]
95
+ self._orderButtons = [
96
+ QtWidgets.QRadioButton(str(n)) for n in (1, 2, 3)]
96
97
  for n, button in enumerate(self._orderButtons, start=1):
97
- button.toggled.connect(lambda checked, n=n: self.setOrder(checked, n))
98
+ button.toggled.connect(
99
+ lambda checked, n=n: self.setOrder(checked, n))
98
100
  self._layout.addWidget(button)
99
101
  self._orderButtons[self.filter.order - 1].setChecked(True)
100
102
  self._resetButton = QtWidgets.QPushButton('Reset', self)
@@ -0,0 +1,89 @@
1
+ '''Real-time object detection with YOLO.'''
2
+
3
+ from pyqtgraph.Qt import QtCore, QtWidgets
4
+ from QVideo.lib.QVideoFilter import VideoFilter, QVideoFilter
5
+ from QVideo.lib.types import Image
6
+ import numpy as np
7
+
8
+ try:
9
+ from ultralytics import YOLO
10
+ except ImportError:
11
+ YOLO = None
12
+
13
+
14
+ __all__ = ['YOLOFilter', 'QYOLOFilter']
15
+
16
+
17
+ class YOLOFilter(VideoFilter):
18
+
19
+ '''YOLO object detection filter.
20
+
21
+ Parameters
22
+ ----------
23
+ model_name: str
24
+ Name of the YOLO model weights file.
25
+ Default: ``'yolo11n.pt'``.
26
+ passthrough: bool
27
+ If True, return the input image.
28
+ If False, mark up the image with bounding box data
29
+ '''
30
+
31
+ #: Emitted with bounding boxes of detected features.
32
+ featuresReady = QtCore.pyqtSignal(np.ndarray)
33
+
34
+ def __init__(self,
35
+ model_name: str = 'yolo11n.pt',
36
+ passthrough: bool = False) -> None:
37
+ super().__init__()
38
+ if YOLO is None:
39
+ raise ImportError(
40
+ 'YOLO is required for QYOLOFilter.'
41
+ '\n\tInstall it with: pip install ultralytics'
42
+ '\n\tSee https://docs.ultralytics.com/ '
43
+ 'for more information.')
44
+ try:
45
+ self.model = YOLO(model_name)
46
+ except FileNotFoundError:
47
+ raise FileNotFoundError(
48
+ f'YOLO model "{model_name}" not found.'
49
+ '\n\tProvide the name of a pretrained ultralytics model'
50
+ '\n\tor the full path to a custom YOLO weights file.'
51
+ '\n\tSee https://docs.ultralytics.com/models/ '
52
+ 'for available pretrained models.')
53
+ self._passthrough = passthrough
54
+
55
+ def add(self, image: Image) -> None:
56
+ '''Performs YOLO feature detection on image
57
+
58
+ Emits featuresReady with bounding-box data
59
+ '''
60
+ results = self.model(image, verbose=False)
61
+ boxes = results[0].boxes.xyxy.numpy()
62
+ self.featuresReady.emit(boxes)
63
+ self.data = image if self._passthrough else results[0].plot()
64
+
65
+ @property
66
+ def passthrough(self) -> bool:
67
+ return self._passthrough
68
+
69
+ @passthrough.setter
70
+ def passthrough(self, value: bool) -> None:
71
+ self._passthrough = bool(value)
72
+
73
+
74
+ class QYOLOFilter(QVideoFilter):
75
+
76
+ '''QVideoFilter wrapper for YOLOFilter.'''
77
+
78
+ def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
79
+ super().__init__(parent, 'YOLO Filter', YOLOFilter())
80
+
81
+ def _setupUi(self) -> None:
82
+ super()._setupUi()
83
+ self._checkbox = QtWidgets.QCheckBox('Passthrough')
84
+ self._layout.addWidget(self._checkbox)
85
+ self._checkbox.stateChanged.connect(self._setPassthrough)
86
+
87
+ @QtCore.pyqtSlot(int)
88
+ def _setPassthrough(self, state: int) -> None:
89
+ self.filter.passthrough = state
@@ -1,4 +1,4 @@
1
- from QVideo.lib.VideoFilter import VideoFilter
1
+ from QVideo.lib.QVideoFilter import VideoFilter
2
2
  from QVideo.lib.types import Image
3
3
  import numpy as np
4
4
 
@@ -5,6 +5,7 @@ from .QBlurFilter import BlurFilter, QBlurFilter
5
5
  from .QEdgeFilter import EdgeFilter, QEdgeFilter
6
6
  from .QRGBFilter import RGBFilter, QRGBFilter
7
7
  from .QSampleHold import SampleHold, QSampleHold
8
+ from .QYOLOFilter import YOLOFilter, QYOLOFilter
8
9
 
9
10
 
10
11
  __all__ = '''
@@ -14,4 +15,5 @@ BlurFilter QBlurFilter
14
15
  EdgeFilter QEdgeFilter
15
16
  RGBFilter QRGBFilter
16
17
  SampleHold QSampleHold
18
+ YOLOFilter QYOLOFilter
17
19
  '''.split()
@@ -1,14 +1,17 @@
1
1
  '''Composable pipeline of VideoFilter stages between a source and a display.'''
2
+ import logging
2
3
  from typing import Iterator
3
4
 
4
5
  from pyqtgraph.Qt import QtWidgets
5
- from QVideo.lib.VideoFilter import QVideoFilter
6
+ from QVideo.lib.QVideoFilter import QVideoFilter
6
7
  from QVideo.lib.types import Image
7
8
  import QVideo.filters as videofilters
8
9
 
9
10
 
10
11
  __all__ = ['QFilterBank']
11
12
 
13
+ logger = logging.getLogger(__name__)
14
+
12
15
 
13
16
  class QFilterBank(QtWidgets.QGroupBox):
14
17
 
@@ -72,7 +75,8 @@ class QFilterBank(QtWidgets.QGroupBox):
72
75
  If *video_filter* is not a :class:`~QVideo.lib.VideoFilter.QVideoFilter`.
73
76
  '''
74
77
  if not isinstance(video_filter, QVideoFilter):
75
- raise TypeError(f'expected QVideoFilter, got {type(video_filter).__name__}')
78
+ raise TypeError('expected QVideoFilter, '
79
+ f'got {type(video_filter).__name__}')
76
80
  self._filters.append(video_filter)
77
81
  self._layout.addWidget(video_filter)
78
82
 
@@ -116,4 +120,7 @@ class QFilterBank(QtWidgets.QGroupBox):
116
120
  if cls is None or not (isinstance(cls, type) and
117
121
  issubclass(cls, QVideoFilter)):
118
122
  raise ValueError(f'{name!r} is not a known filter')
119
- self.register(cls())
123
+ try:
124
+ self.register(cls())
125
+ except Exception as e:
126
+ logger.warning(f'Failed to instantiate filter {name!r}: {e}')
@@ -1,12 +1,12 @@
1
1
  '''Base classes for image-processing filters in the QVideo filter pipeline.'''
2
- from pyqtgraph.Qt import QtWidgets
2
+ from pyqtgraph.Qt import QtCore, QtWidgets
3
3
  from QVideo.lib.types import Image
4
4
 
5
5
 
6
6
  __all__ = ['VideoFilter', 'QVideoFilter']
7
7
 
8
8
 
9
- class VideoFilter:
9
+ class VideoFilter(QtCore.QObject):
10
10
 
11
11
  '''Base class for video filters.
12
12
 
@@ -20,6 +20,7 @@ class VideoFilter:
20
20
  '''
21
21
 
22
22
  def __init__(self) -> None:
23
+ super().__init__()
23
24
  self.data: Image | None = None
24
25
 
25
26
  def __call__(self, data: Image) -> Image:
@@ -80,21 +81,20 @@ class QVideoFilter(QtWidgets.QGroupBox):
80
81
 
81
82
  Parameters
82
83
  ----------
84
+ parent : QtWidgets.QWidget
85
+ Parent widget.
83
86
  title : str
84
87
  Label displayed in the group box border.
85
- parent : QtWidgets.QWidget or None
86
- Parent widget.
87
- video_filter : VideoFilter or None
88
- The filter to apply when enabled. Defaults to a passthrough
89
- :class:`VideoFilter` if not provided.
88
+ videoFilter : VideoFilter
89
+ The filter to apply when enabled.
90
90
  '''
91
91
 
92
92
  def __init__(self,
93
+ parent: QtWidgets.QWidget,
93
94
  title: str,
94
- parent: QtWidgets.QWidget | None = None,
95
- video_filter: VideoFilter | None = None) -> None:
95
+ videoFilter: VideoFilter) -> None:
96
96
  super().__init__(title, parent)
97
- self._filter = VideoFilter() if video_filter is None else video_filter
97
+ self._filter = videoFilter
98
98
  self._setupUi()
99
99
 
100
100
  @property
@@ -103,10 +103,10 @@ class QVideoFilter(QtWidgets.QGroupBox):
103
103
  return self._filter
104
104
 
105
105
  @filter.setter
106
- def filter(self, video_filter: VideoFilter) -> None:
107
- if not isinstance(video_filter, VideoFilter):
108
- raise TypeError(f'expected VideoFilter, got {type(video_filter).__name__}')
109
- self._filter = video_filter
106
+ def filter(self, videoFilter: VideoFilter) -> None:
107
+ if not isinstance(videoFilter, VideoFilter):
108
+ raise TypeError(f'expected VideoFilter, got {type(videoFilter).__name__}')
109
+ self._filter = videoFilter
110
110
 
111
111
  def __call__(self, image: Image) -> Image:
112
112
  '''Apply the filter if enabled, otherwise return *image* unchanged.
@@ -1,5 +1,6 @@
1
1
  '''Live video display widget with mouse-aware graphical overlay support.'''
2
- from QVideo.lib import QVideoSource, QFilterBank
2
+ from QVideo.lib.QVideoSource import QVideoSource
3
+ from QVideo.lib.QFilterBank import QFilterBank
3
4
  from QVideo.lib.types import Image
4
5
  from pyqtgraph.Qt import QtCore
5
6
  import numpy as np
@@ -71,13 +72,17 @@ class QVideoScreen(GraphicsLayoutWidget):
71
72
 
72
73
  @property
73
74
  def framerate(self) -> int | None:
74
- '''Maximum display frame rate [fps], or ``None`` for no throttling.'''
75
+ '''Maximum display frame rate [fps].
76
+
77
+ Accommodate high-speed cameras by throttling the rate at
78
+ which frames are displayed. ``None`` for no throttling.'''
75
79
  return self._framerate
76
80
 
77
81
  @framerate.setter
78
82
  def framerate(self, framerate: int | None) -> None:
79
83
  if framerate is not None and framerate <= 0:
80
- raise ValueError(f'framerate must be positive, got {framerate}')
84
+ raise ValueError('framerate must be positive, '
85
+ f'got {framerate}')
81
86
  self._framerate = framerate
82
87
  self._interval = 0 if framerate is None else int(1000 / framerate)
83
88
 
@@ -124,7 +129,7 @@ class QVideoScreen(GraphicsLayoutWidget):
124
129
  self._pending = image
125
130
 
126
131
  def sizeHint(self) -> QtCore.QSize:
127
- '''Return the source's natural frame size as the preferred widget size.'''
132
+ '''Return the source frame size as the preferred widget size.'''
128
133
  if self.source is not None:
129
134
  return self.source.shape
130
135
  return super().sizeHint()
@@ -164,11 +169,12 @@ class QVideoScreen(GraphicsLayoutWidget):
164
169
 
165
170
  @QtCore.pyqtSlot()
166
171
  def _fitToVideo(self) -> None:
167
- '''Resize the containing window so this widget matches the video aspect ratio.
172
+ '''Resize the containing window to match the video aspect ratio.
168
173
 
169
- Computes the height the widget should have given its current width and
170
- the source aspect ratio, then adjusts the top-level window height by
171
- the difference. Has no effect when no source is connected.
174
+ Computes the height the widget should have given its current
175
+ width and the source aspect ratio, then adjusts the top-level
176
+ window height by the difference. Only has effect when a source
177
+ is connected.
172
178
  '''
173
179
  if not self.hasHeightForWidth():
174
180
  return
@@ -3,18 +3,18 @@ from .clickable import clickable
3
3
  from .QCamera import QCamera
4
4
  from .QVideoSource import QVideoSource
5
5
  from .QCameraTree import QCameraTree
6
+ from .QVideoScreen import QVideoScreen
6
7
  from .QFilterBank import QFilterBank
8
+ from .QVideoFilter import VideoFilter, QVideoFilter
7
9
  from .QVideoReader import QVideoReader
8
10
  from .QVideoWriter import QVideoWriter
9
- from .QVideoScreen import QVideoScreen
10
11
  from .chooser import choose_camera
11
12
  from .QListCameras import QListCameras
12
13
  from .QFPSMeter import QFPSMeter
13
- from .VideoFilter import VideoFilter
14
14
 
15
15
 
16
16
  __all__ = '''Image
17
17
  clickable choose_camera QListCameras
18
18
  QCamera QVideoSource QCameraTree QFilterBank
19
19
  QVideoReader QVideoWriter QVideoScreen
20
- QFPSMeter VideoFilter'''.split()
20
+ QFPSMeter VideoFilter QVideoFilter'''.split()