QVideo 3.2.3__tar.gz → 3.4.1__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 (131) hide show
  1. {qvideo-3.2.3 → qvideo-3.4.1}/PKG-INFO +25 -10
  2. {qvideo-3.2.3 → qvideo-3.4.1}/QVideo.egg-info/PKG-INFO +25 -10
  3. {qvideo-3.2.3 → qvideo-3.4.1}/QVideo.egg-info/SOURCES.txt +10 -3
  4. {qvideo-3.2.3 → qvideo-3.4.1}/QVideo.egg-info/requires.txt +21 -2
  5. {qvideo-3.2.3 → qvideo-3.4.1}/README.md +6 -6
  6. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/OpenCV/_resolution_tree.py +4 -0
  7. {qvideo-3.2.3 → qvideo-3.4.1}/demos/__init__.py +14 -0
  8. {qvideo-3.2.3 → qvideo-3.4.1}/demos/demo.py +4 -2
  9. {qvideo-3.2.3 → qvideo-3.4.1}/demos/filterdemo.py +1 -11
  10. qvideo-3.4.1/demos/trackpydemo.py +55 -0
  11. qvideo-3.4.1/demos/yolodemo.py +61 -0
  12. {qvideo-3.2.3 → qvideo-3.4.1}/dvr/QDVRWidget.py +3 -2
  13. {qvideo-3.2.3 → qvideo-3.4.1}/filters/QBlurFilter.py +2 -2
  14. {qvideo-3.2.3 → qvideo-3.4.1}/filters/QEdgeFilter.py +6 -6
  15. {qvideo-3.2.3 → qvideo-3.4.1}/filters/QRGBFilter.py +2 -2
  16. {qvideo-3.2.3 → qvideo-3.4.1}/filters/QSampleHold.py +7 -5
  17. qvideo-3.4.1/filters/QYOLOFilter.py +100 -0
  18. {qvideo-3.2.3 → qvideo-3.4.1}/filters/_MedianBase.py +1 -1
  19. {qvideo-3.2.3 → qvideo-3.4.1}/filters/__init__.py +2 -0
  20. {qvideo-3.2.3 → qvideo-3.4.1}/lib/QFilterBank.py +10 -3
  21. qvideo-3.2.3/lib/VideoFilter.py → qvideo-3.4.1/lib/QVideoFilter.py +14 -14
  22. {qvideo-3.2.3 → qvideo-3.4.1}/lib/QVideoScreen.py +14 -8
  23. {qvideo-3.2.3 → qvideo-3.4.1}/lib/__init__.py +3 -3
  24. {qvideo-3.2.3 → qvideo-3.4.1}/lib/resolutions.py +3 -1
  25. qvideo-3.4.1/overlays/__init__.py +6 -0
  26. qvideo-3.4.1/overlays/trackpy.py +251 -0
  27. qvideo-3.4.1/overlays/yolo.py +259 -0
  28. {qvideo-3.2.3 → qvideo-3.4.1}/pyproject.toml +13 -8
  29. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_demo.py +8 -3
  30. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qblurfilter.py +1 -1
  31. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qdvrwidget.py +2 -3
  32. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qedgefilter.py +1 -1
  33. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qfilterbank.py +3 -3
  34. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qopencvresolutiontree.py +15 -1
  35. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qrgbfilter.py +1 -1
  36. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qsamplehold.py +1 -1
  37. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_resolutions.py +20 -4
  38. qvideo-3.4.1/tests/test_trackpy_overlay.py +346 -0
  39. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_videofilter.py +6 -6
  40. qvideo-3.4.1/tests/test_yolo_overlay.py +315 -0
  41. qvideo-3.2.3/dvr/icons_rc_qt6.py +0 -1106
  42. {qvideo-3.2.3 → qvideo-3.4.1}/LICENSE.md +0 -0
  43. {qvideo-3.2.3 → qvideo-3.4.1}/QCamcorder.py +0 -0
  44. {qvideo-3.2.3 → qvideo-3.4.1}/QVideo.egg-info/dependency_links.txt +0 -0
  45. {qvideo-3.2.3 → qvideo-3.4.1}/QVideo.egg-info/top_level.txt +0 -0
  46. {qvideo-3.2.3 → qvideo-3.4.1}/__init__.py +0 -0
  47. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Basler/__init__.py +0 -0
  48. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Basler/_camera.py +0 -0
  49. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Basler/_tree.py +0 -0
  50. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Flir/QListFlirCameras.py +0 -0
  51. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Flir/__init__.py +0 -0
  52. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Flir/_camera.py +0 -0
  53. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Flir/_tree.py +0 -0
  54. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Genicam/__init__.py +0 -0
  55. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Genicam/_camera.py +0 -0
  56. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Genicam/_tree.py +0 -0
  57. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/IDS/__init__.py +0 -0
  58. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/IDS/_camera.py +0 -0
  59. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/IDS/_tree.py +0 -0
  60. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/MV/__init__.py +0 -0
  61. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/MV/_camera.py +0 -0
  62. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/MV/_tree.py +0 -0
  63. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Noise/__init__.py +0 -0
  64. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Noise/_camera.py +0 -0
  65. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Noise/_tree.py +0 -0
  66. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/OpenCV/QListCVCameras.py +0 -0
  67. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/OpenCV/__init__.py +0 -0
  68. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/OpenCV/_camera.py +0 -0
  69. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/OpenCV/_tree.py +0 -0
  70. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Picamera/__init__.py +0 -0
  71. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Picamera/_camera.py +0 -0
  72. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Picamera/_tree.py +0 -0
  73. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Vimbax/__init__.py +0 -0
  74. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Vimbax/_camera.py +0 -0
  75. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/Vimbax/_tree.py +0 -0
  76. {qvideo-3.2.3 → qvideo-3.4.1}/cameras/__init__.py +0 -0
  77. {qvideo-3.2.3 → qvideo-3.4.1}/demos/ROIdemo.py +0 -0
  78. {qvideo-3.2.3 → qvideo-3.4.1}/dvr/QHDF5Reader.py +0 -0
  79. {qvideo-3.2.3 → qvideo-3.4.1}/dvr/QHDF5Writer.py +0 -0
  80. {qvideo-3.2.3 → qvideo-3.4.1}/dvr/QOpenCVReader.py +0 -0
  81. {qvideo-3.2.3 → qvideo-3.4.1}/dvr/QOpenCVWriter.py +0 -0
  82. {qvideo-3.2.3 → qvideo-3.4.1}/dvr/__init__.py +0 -0
  83. {qvideo-3.2.3 → qvideo-3.4.1}/dvr/icons_rc.py +0 -0
  84. {qvideo-3.2.3 → qvideo-3.4.1}/filters/Median.py +0 -0
  85. {qvideo-3.2.3 → qvideo-3.4.1}/filters/MoMedian.py +0 -0
  86. {qvideo-3.2.3 → qvideo-3.4.1}/filters/Normalize.py +0 -0
  87. {qvideo-3.2.3 → qvideo-3.4.1}/lib/QCamera.py +0 -0
  88. {qvideo-3.2.3 → qvideo-3.4.1}/lib/QCameraTree.py +0 -0
  89. {qvideo-3.2.3 → qvideo-3.4.1}/lib/QFPSMeter.py +0 -0
  90. {qvideo-3.2.3 → qvideo-3.4.1}/lib/QListCameras.py +0 -0
  91. {qvideo-3.2.3 → qvideo-3.4.1}/lib/QVideoReader.py +0 -0
  92. {qvideo-3.2.3 → qvideo-3.4.1}/lib/QVideoSource.py +0 -0
  93. {qvideo-3.2.3 → qvideo-3.4.1}/lib/QVideoWriter.py +0 -0
  94. {qvideo-3.2.3 → qvideo-3.4.1}/lib/chooser.py +0 -0
  95. {qvideo-3.2.3 → qvideo-3.4.1}/lib/clickable.py +0 -0
  96. {qvideo-3.2.3 → qvideo-3.4.1}/lib/types.py +0 -0
  97. {qvideo-3.2.3 → qvideo-3.4.1}/setup.cfg +0 -0
  98. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_chooser.py +0 -0
  99. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_clickable.py +0 -0
  100. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_filterdemo.py +0 -0
  101. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_icons_rc.py +0 -0
  102. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_median.py +0 -0
  103. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_momedian.py +0 -0
  104. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_normalize.py +0 -0
  105. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qbaslercamera.py +0 -0
  106. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qcamcorder.py +0 -0
  107. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qcamera.py +0 -0
  108. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qcameratree.py +0 -0
  109. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qflircamera.py +0 -0
  110. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qfpsmeter.py +0 -0
  111. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qgenicamcamera.py +0 -0
  112. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qgenicamtree.py +0 -0
  113. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qhdf5reader.py +0 -0
  114. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qhdf5writer.py +0 -0
  115. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qidscamera.py +0 -0
  116. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qlistcameras.py +0 -0
  117. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qlistcvcameras.py +0 -0
  118. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qmvcamera.py +0 -0
  119. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qnoisecamera.py +0 -0
  120. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qnoisetree.py +0 -0
  121. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qopencvcamera.py +0 -0
  122. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qopencvreader.py +0 -0
  123. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qopencvtree.py +0 -0
  124. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qopencvwriter.py +0 -0
  125. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qpicamera.py +0 -0
  126. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qvideoreader.py +0 -0
  127. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qvideoscreen.py +0 -0
  128. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qvideosource.py +0 -0
  129. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qvideowriter.py +0 -0
  130. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_qvimbaxcamera.py +0 -0
  131. {qvideo-3.2.3 → qvideo-3.4.1}/tests/test_roidemo.py +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QVideo
3
- Version: 3.2.3
3
+ Version: 3.4.1
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
7
7
  Project-URL: Homepage, https://github.com/davidgrier/QVideo
8
8
  Project-URL: Repository, https://github.com/davidgrier/QVideo
9
9
  Project-URL: Bug Tracker, https://github.com/davidgrier/QVideo/issues
10
- Keywords: camera,video,imaging,PyQt5,scientific,GenICam,GigE Vision,raspberry pi
10
+ Keywords: camera,video,imaging,PyQt5,PyQt6,scientific,GenICam,GigE Vision,raspberry pi
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Science/Research
13
13
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -24,15 +24,30 @@ License-File: LICENSE.md
24
24
  Requires-Dist: h5py
25
25
  Requires-Dist: numpy
26
26
  Requires-Dist: opencv-python
27
- Requires-Dist: PyQt5
28
- Requires-Dist: PyQt5-sip
27
+ Requires-Dist: pandas
29
28
  Requires-Dist: pyqtgraph
29
+ Provides-Extra: pyqt5
30
+ Requires-Dist: PyQt5; extra == "pyqt5"
31
+ Requires-Dist: PyQt5-sip; extra == "pyqt5"
32
+ Provides-Extra: pyqt6
33
+ Requires-Dist: PyQt6; extra == "pyqt6"
30
34
  Provides-Extra: genicam
31
35
  Requires-Dist: harvesters; extra == "genicam"
32
36
  Requires-Dist: genicam; extra == "genicam"
33
37
  Provides-Extra: picamera
34
38
  Requires-Dist: picamera2; extra == "picamera"
39
+ Provides-Extra: dev-pyqt5
40
+ Requires-Dist: PyQt5; extra == "dev-pyqt5"
41
+ Requires-Dist: PyQt5-sip; extra == "dev-pyqt5"
42
+ Requires-Dist: pytest; extra == "dev-pyqt5"
43
+ Requires-Dist: pytest-cov; extra == "dev-pyqt5"
44
+ Provides-Extra: dev-pyqt6
45
+ Requires-Dist: PyQt6; extra == "dev-pyqt6"
46
+ Requires-Dist: pytest; extra == "dev-pyqt6"
47
+ Requires-Dist: pytest-cov; extra == "dev-pyqt6"
35
48
  Provides-Extra: dev
49
+ Requires-Dist: PyQt5; extra == "dev"
50
+ Requires-Dist: PyQt5-sip; extra == "dev"
36
51
  Requires-Dist: pytest; extra == "dev"
37
52
  Requires-Dist: pytest-cov; extra == "dev"
38
53
  Provides-Extra: docs
@@ -48,8 +63,9 @@ Dynamic: license-file
48
63
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE.md)
49
64
  [![Tests](https://github.com/davidgrier/QVideo/actions/workflows/test.yml/badge.svg)](https://github.com/davidgrier/QVideo/actions/workflows/test.yml)
50
65
  [![Documentation](https://readthedocs.org/projects/qvideo/badge/?version=latest)](https://qvideo.readthedocs.io/en/latest/)
66
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19239402.svg)](https://doi.org/10.5281/zenodo.19239402)
51
67
 
52
- **QVideo** is a framework for integrating video cameras into PyQt5 projects
68
+ **QVideo** is a framework for integrating video cameras into PyQt projects
53
69
  for scientific research. It provides a unified, registration-based property
54
70
  system so that every camera backend — USB webcams, GenICam devices, FLIR
55
71
  cameras, Raspberry Pi cameras — is controlled through the same API. Property
@@ -87,16 +103,15 @@ pip install QVideo
87
103
  |---------|-------|-------|
88
104
  | GenICam cameras (Vimba, etc.) | `pip install QVideo[genicam]` | Requires a vendor-supplied `.cti` producer file |
89
105
  | Raspberry Pi camera | `pip install QVideo[picamera]` | Requires `picamera2` |
90
- | FLIR / Spinnaker cameras | — | Requires the proprietary PySpin SDK; install that separately |
91
106
 
92
107
  ## Quick start
93
108
 
94
109
  ```python
95
- from pyqtgraph.Qt import QtWidgets
110
+ import pyqtgraph as pg
96
111
  from QVideo.cameras.Noise import QNoiseSource
97
112
  from QVideo.lib import QVideoScreen
98
113
 
99
- app = QtWidgets.QApplication([])
114
+ pg.mkApp()
100
115
 
101
116
  source = QNoiseSource() # synthetic noise — no hardware needed
102
117
  screen = QVideoScreen()
@@ -104,7 +119,7 @@ source.newFrame.connect(screen.setImage)
104
119
 
105
120
  screen.show()
106
121
  source.start()
107
- app.exec()
122
+ pg.exec()
108
123
  ```
109
124
 
110
125
  Replace `QNoiseSource` with `QOpenCVSource`, `QGenicamSource`, etc. to switch
@@ -157,5 +172,5 @@ additional code needed.
157
172
  ## Acknowledgements
158
173
 
159
174
  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
175
+ Science Foundation of the United States under award number DMR-2428983 and
161
176
  by an award from the TAC Program of New York University.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QVideo
3
- Version: 3.2.3
3
+ Version: 3.4.1
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
7
7
  Project-URL: Homepage, https://github.com/davidgrier/QVideo
8
8
  Project-URL: Repository, https://github.com/davidgrier/QVideo
9
9
  Project-URL: Bug Tracker, https://github.com/davidgrier/QVideo/issues
10
- Keywords: camera,video,imaging,PyQt5,scientific,GenICam,GigE Vision,raspberry pi
10
+ Keywords: camera,video,imaging,PyQt5,PyQt6,scientific,GenICam,GigE Vision,raspberry pi
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Science/Research
13
13
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -24,15 +24,30 @@ License-File: LICENSE.md
24
24
  Requires-Dist: h5py
25
25
  Requires-Dist: numpy
26
26
  Requires-Dist: opencv-python
27
- Requires-Dist: PyQt5
28
- Requires-Dist: PyQt5-sip
27
+ Requires-Dist: pandas
29
28
  Requires-Dist: pyqtgraph
29
+ Provides-Extra: pyqt5
30
+ Requires-Dist: PyQt5; extra == "pyqt5"
31
+ Requires-Dist: PyQt5-sip; extra == "pyqt5"
32
+ Provides-Extra: pyqt6
33
+ Requires-Dist: PyQt6; extra == "pyqt6"
30
34
  Provides-Extra: genicam
31
35
  Requires-Dist: harvesters; extra == "genicam"
32
36
  Requires-Dist: genicam; extra == "genicam"
33
37
  Provides-Extra: picamera
34
38
  Requires-Dist: picamera2; extra == "picamera"
39
+ Provides-Extra: dev-pyqt5
40
+ Requires-Dist: PyQt5; extra == "dev-pyqt5"
41
+ Requires-Dist: PyQt5-sip; extra == "dev-pyqt5"
42
+ Requires-Dist: pytest; extra == "dev-pyqt5"
43
+ Requires-Dist: pytest-cov; extra == "dev-pyqt5"
44
+ Provides-Extra: dev-pyqt6
45
+ Requires-Dist: PyQt6; extra == "dev-pyqt6"
46
+ Requires-Dist: pytest; extra == "dev-pyqt6"
47
+ Requires-Dist: pytest-cov; extra == "dev-pyqt6"
35
48
  Provides-Extra: dev
49
+ Requires-Dist: PyQt5; extra == "dev"
50
+ Requires-Dist: PyQt5-sip; extra == "dev"
36
51
  Requires-Dist: pytest; extra == "dev"
37
52
  Requires-Dist: pytest-cov; extra == "dev"
38
53
  Provides-Extra: docs
@@ -48,8 +63,9 @@ Dynamic: license-file
48
63
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE.md)
49
64
  [![Tests](https://github.com/davidgrier/QVideo/actions/workflows/test.yml/badge.svg)](https://github.com/davidgrier/QVideo/actions/workflows/test.yml)
50
65
  [![Documentation](https://readthedocs.org/projects/qvideo/badge/?version=latest)](https://qvideo.readthedocs.io/en/latest/)
66
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19239402.svg)](https://doi.org/10.5281/zenodo.19239402)
51
67
 
52
- **QVideo** is a framework for integrating video cameras into PyQt5 projects
68
+ **QVideo** is a framework for integrating video cameras into PyQt projects
53
69
  for scientific research. It provides a unified, registration-based property
54
70
  system so that every camera backend — USB webcams, GenICam devices, FLIR
55
71
  cameras, Raspberry Pi cameras — is controlled through the same API. Property
@@ -87,16 +103,15 @@ pip install QVideo
87
103
  |---------|-------|-------|
88
104
  | GenICam cameras (Vimba, etc.) | `pip install QVideo[genicam]` | Requires a vendor-supplied `.cti` producer file |
89
105
  | Raspberry Pi camera | `pip install QVideo[picamera]` | Requires `picamera2` |
90
- | FLIR / Spinnaker cameras | — | Requires the proprietary PySpin SDK; install that separately |
91
106
 
92
107
  ## Quick start
93
108
 
94
109
  ```python
95
- from pyqtgraph.Qt import QtWidgets
110
+ import pyqtgraph as pg
96
111
  from QVideo.cameras.Noise import QNoiseSource
97
112
  from QVideo.lib import QVideoScreen
98
113
 
99
- app = QtWidgets.QApplication([])
114
+ pg.mkApp()
100
115
 
101
116
  source = QNoiseSource() # synthetic noise — no hardware needed
102
117
  screen = QVideoScreen()
@@ -104,7 +119,7 @@ source.newFrame.connect(screen.setImage)
104
119
 
105
120
  screen.show()
106
121
  source.start()
107
- app.exec()
122
+ pg.exec()
108
123
  ```
109
124
 
110
125
  Replace `QNoiseSource` with `QOpenCVSource`, `QGenicamSource`, etc. to switch
@@ -157,5 +172,5 @@ additional code needed.
157
172
  ## Acknowledgements
158
173
 
159
174
  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
175
+ Science Foundation of the United States under award number DMR-2428983 and
161
176
  by an award from the TAC Program of New York University.
@@ -45,6 +45,8 @@ demos/ROIdemo.py
45
45
  demos/__init__.py
46
46
  demos/demo.py
47
47
  demos/filterdemo.py
48
+ demos/trackpydemo.py
49
+ demos/yolodemo.py
48
50
  dvr/QDVRWidget.py
49
51
  dvr/QHDF5Reader.py
50
52
  dvr/QHDF5Writer.py
@@ -52,7 +54,6 @@ dvr/QOpenCVReader.py
52
54
  dvr/QOpenCVWriter.py
53
55
  dvr/__init__.py
54
56
  dvr/icons_rc.py
55
- dvr/icons_rc_qt6.py
56
57
  filters/Median.py
57
58
  filters/MoMedian.py
58
59
  filters/Normalize.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,6 @@ tests/test_qvideowriter.py
120
125
  tests/test_qvimbaxcamera.py
121
126
  tests/test_resolutions.py
122
127
  tests/test_roidemo.py
123
- tests/test_videofilter.py
128
+ tests/test_trackpy_overlay.py
129
+ tests/test_videofilter.py
130
+ tests/test_yolo_overlay.py
@@ -1,11 +1,23 @@
1
1
  h5py
2
2
  numpy
3
3
  opencv-python
4
- PyQt5
5
- PyQt5-sip
4
+ pandas
6
5
  pyqtgraph
7
6
 
8
7
  [dev]
8
+ PyQt5
9
+ PyQt5-sip
10
+ pytest
11
+ pytest-cov
12
+
13
+ [dev-pyqt5]
14
+ PyQt5
15
+ PyQt5-sip
16
+ pytest
17
+ pytest-cov
18
+
19
+ [dev-pyqt6]
20
+ PyQt6
9
21
  pytest
10
22
  pytest-cov
11
23
 
@@ -20,3 +32,10 @@ genicam
20
32
 
21
33
  [picamera]
22
34
  picamera2
35
+
36
+ [pyqt5]
37
+ PyQt5
38
+ PyQt5-sip
39
+
40
+ [pyqt6]
41
+ PyQt6
@@ -5,8 +5,9 @@
5
5
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE.md)
6
6
  [![Tests](https://github.com/davidgrier/QVideo/actions/workflows/test.yml/badge.svg)](https://github.com/davidgrier/QVideo/actions/workflows/test.yml)
7
7
  [![Documentation](https://readthedocs.org/projects/qvideo/badge/?version=latest)](https://qvideo.readthedocs.io/en/latest/)
8
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19239402.svg)](https://doi.org/10.5281/zenodo.19239402)
8
9
 
9
- **QVideo** is a framework for integrating video cameras into PyQt5 projects
10
+ **QVideo** is a framework for integrating video cameras into PyQt projects
10
11
  for scientific research. It provides a unified, registration-based property
11
12
  system so that every camera backend — USB webcams, GenICam devices, FLIR
12
13
  cameras, Raspberry Pi cameras — is controlled through the same API. Property
@@ -44,16 +45,15 @@ pip install QVideo
44
45
  |---------|-------|-------|
45
46
  | GenICam cameras (Vimba, etc.) | `pip install QVideo[genicam]` | Requires a vendor-supplied `.cti` producer file |
46
47
  | Raspberry Pi camera | `pip install QVideo[picamera]` | Requires `picamera2` |
47
- | FLIR / Spinnaker cameras | — | Requires the proprietary PySpin SDK; install that separately |
48
48
 
49
49
  ## Quick start
50
50
 
51
51
  ```python
52
- from pyqtgraph.Qt import QtWidgets
52
+ import pyqtgraph as pg
53
53
  from QVideo.cameras.Noise import QNoiseSource
54
54
  from QVideo.lib import QVideoScreen
55
55
 
56
- app = QtWidgets.QApplication([])
56
+ pg.mkApp()
57
57
 
58
58
  source = QNoiseSource() # synthetic noise — no hardware needed
59
59
  screen = QVideoScreen()
@@ -61,7 +61,7 @@ source.newFrame.connect(screen.setImage)
61
61
 
62
62
  screen.show()
63
63
  source.start()
64
- app.exec()
64
+ pg.exec()
65
65
  ```
66
66
 
67
67
  Replace `QNoiseSource` with `QOpenCVSource`, `QGenicamSource`, etc. to switch
@@ -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,16 @@ 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
+
29
+ yolodemo
30
+ Extends :mod:`demo` with a :class:`~QVideo.overlays.yolo.QYoloWidget`
31
+ panel so that YOLO object-detection bounding boxes are overlaid on the
32
+ live feed in real time and detection results are available via a signal.
33
+
24
34
  Running
25
35
  -------
26
36
  Each demo can be launched directly::
@@ -28,6 +38,8 @@ Each demo can be launched directly::
28
38
  python -m QVideo.demos.demo
29
39
  python -m QVideo.demos.filterdemo
30
40
  python -m QVideo.demos.ROIdemo
41
+ python -m QVideo.demos.trackpydemo
42
+ python -m QVideo.demos.yolodemo
31
43
 
32
44
  Camera selection
33
45
  ----------------
@@ -59,4 +71,6 @@ Example::
59
71
  from .demo import Demo
60
72
  from .filterdemo import FilterDemo
61
73
  from .ROIdemo import ROIFilter, ROIDemo
74
+ from .trackpydemo import TrackpyDemo
75
+ from .yolodemo import YoloDemo
62
76
 
@@ -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,55 @@
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
+ See :mod:`QVideo.overlays.trackpy` for literature references.
9
+ '''
10
+
11
+ from QVideo.demos.demo import Demo
12
+ from QVideo.lib import QCameraTree
13
+ from QVideo.overlays import QTrackpyWidget
14
+
15
+
16
+ __all__ = ['TrackpyDemo']
17
+
18
+
19
+ class TrackpyDemo(Demo):
20
+ '''Extends :class:`~QVideo.demos.demo.Demo` with a trackpy overlay.
21
+
22
+ Adds a :class:`~QVideo.overlays.trackpy.QTrackpyWidget` control panel
23
+ below the camera control tree. Detected particle positions are
24
+ rendered in real time as a scatter-plot overlay on the video screen.
25
+
26
+ Parameters
27
+ ----------
28
+ cameraTree : QCameraTree
29
+ The camera control tree widget to display alongside the video feed.
30
+ **kwargs :
31
+ Additional keyword arguments forwarded to :class:`~QVideo.demos.demo.Demo`.
32
+ '''
33
+
34
+ def __init__(self, cameraTree: QCameraTree, **kwargs) -> None:
35
+ super().__init__(cameraTree, **kwargs)
36
+ self.trackpy = QTrackpyWidget(self)
37
+ self.trackpy.source = self.screen.source
38
+ self.trackpy.attachTo(self.screen)
39
+ self._controls.addWidget(self.trackpy)
40
+
41
+
42
+ def main() -> None: # pragma: no cover
43
+ '''Launch the trackpy demo with an interactively chosen camera.'''
44
+ import pyqtgraph as pg
45
+ from QVideo.lib import choose_camera
46
+
47
+ pg.mkQApp()
48
+ camera = choose_camera().start()
49
+ widget = TrackpyDemo(camera)
50
+ widget.show()
51
+ pg.exec()
52
+
53
+
54
+ if __name__ == '__main__': # pragma: no cover
55
+ main()
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env python3
2
+ '''Demo combining a live video feed, camera controls, and a YOLO overlay.
3
+
4
+ Run directly::
5
+
6
+ python -m QVideo.demos.yolodemo
7
+
8
+ See :mod:`QVideo.overlays.yolo` for literature references.
9
+ '''
10
+
11
+ from QVideo.demos.demo import Demo
12
+ from QVideo.lib import QCameraTree
13
+ from QVideo.overlays import QYoloWidget
14
+
15
+
16
+ __all__ = ['YoloDemo']
17
+
18
+
19
+ class YoloDemo(Demo):
20
+ '''Extends :class:`~QVideo.demos.demo.Demo` with a YOLO overlay.
21
+
22
+ Adds a :class:`~QVideo.overlays.yolo.QYoloWidget` control panel
23
+ below the camera control tree. Detected object bounding boxes are
24
+ rendered in real time as a rectangle overlay on the video screen.
25
+
26
+ Parameters
27
+ ----------
28
+ cameraTree : QCameraTree
29
+ The camera control tree widget to display alongside the video feed.
30
+ model_name : str
31
+ YOLO model weights file passed to :class:`~QVideo.overlays.yolo.QYoloWidget`.
32
+ Default: ``'yolo11n.pt'``.
33
+ **kwargs :
34
+ Additional keyword arguments forwarded to :class:`~QVideo.demos.demo.Demo`.
35
+ '''
36
+
37
+ def __init__(self,
38
+ cameraTree: QCameraTree,
39
+ model_name: str = 'yolo11n.pt',
40
+ **kwargs) -> None:
41
+ super().__init__(cameraTree, **kwargs)
42
+ self.yolo = QYoloWidget(self, model_name=model_name)
43
+ self.yolo.source = self.screen.source
44
+ self.yolo.attachTo(self.screen)
45
+ self._controls.addWidget(self.yolo)
46
+
47
+
48
+ def main() -> None: # pragma: no cover
49
+ '''Launch the YOLO demo with an interactively chosen camera.'''
50
+ import pyqtgraph as pg
51
+ from QVideo.lib import choose_camera
52
+
53
+ pg.mkQApp()
54
+ camera = choose_camera().start()
55
+ widget = YoloDemo(camera)
56
+ widget.show()
57
+ pg.exec()
58
+
59
+
60
+ if __name__ == '__main__': # pragma: no cover
61
+ 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)