QVideo 3.2.2__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 (130) hide show
  1. {qvideo-3.2.2 → qvideo-3.4.0}/PKG-INFO +4 -3
  2. {qvideo-3.2.2 → qvideo-3.4.0}/QCamcorder.py +5 -1
  3. {qvideo-3.2.2 → qvideo-3.4.0}/QVideo.egg-info/PKG-INFO +4 -3
  4. {qvideo-3.2.2 → qvideo-3.4.0}/QVideo.egg-info/SOURCES.txt +11 -1
  5. {qvideo-3.2.2 → qvideo-3.4.0}/QVideo.egg-info/requires.txt +1 -1
  6. {qvideo-3.2.2 → qvideo-3.4.0}/README.md +2 -1
  7. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Basler/_tree.py +1 -1
  8. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Flir/_camera.py +8 -0
  9. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Flir/_tree.py +3 -2
  10. qvideo-3.4.0/cameras/Genicam/__init__.py +11 -0
  11. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Genicam/_camera.py +42 -5
  12. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Genicam/_tree.py +68 -1
  13. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/IDS/_tree.py +1 -1
  14. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/MV/_tree.py +1 -1
  15. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/OpenCV/_resolution_tree.py +4 -0
  16. qvideo-3.4.0/demos/ROIdemo.py +146 -0
  17. qvideo-3.4.0/demos/__init__.py +69 -0
  18. qvideo-3.4.0/demos/demo.py +63 -0
  19. qvideo-3.4.0/demos/filterdemo.py +68 -0
  20. qvideo-3.4.0/demos/trackpydemo.py +53 -0
  21. {qvideo-3.2.2 → qvideo-3.4.0}/dvr/QDVRWidget.py +6 -2
  22. {qvideo-3.2.2 → qvideo-3.4.0}/filters/Median.py +4 -4
  23. {qvideo-3.2.2 → qvideo-3.4.0}/filters/MoMedian.py +10 -8
  24. {qvideo-3.2.2 → qvideo-3.4.0}/filters/QBlurFilter.py +2 -2
  25. {qvideo-3.2.2 → qvideo-3.4.0}/filters/QEdgeFilter.py +6 -6
  26. {qvideo-3.2.2 → qvideo-3.4.0}/filters/QRGBFilter.py +2 -2
  27. {qvideo-3.2.2 → qvideo-3.4.0}/filters/QSampleHold.py +7 -5
  28. qvideo-3.4.0/filters/QYOLOFilter.py +89 -0
  29. {qvideo-3.2.2 → qvideo-3.4.0}/filters/_MedianBase.py +1 -1
  30. {qvideo-3.2.2 → qvideo-3.4.0}/filters/__init__.py +2 -0
  31. {qvideo-3.2.2 → qvideo-3.4.0}/lib/QCamera.py +2 -0
  32. {qvideo-3.2.2 → qvideo-3.4.0}/lib/QCameraTree.py +6 -6
  33. {qvideo-3.2.2 → qvideo-3.4.0}/lib/QFilterBank.py +10 -3
  34. {qvideo-3.2.2 → qvideo-3.4.0}/lib/QListCameras.py +1 -0
  35. qvideo-3.2.2/lib/VideoFilter.py → qvideo-3.4.0/lib/QVideoFilter.py +14 -14
  36. {qvideo-3.2.2 → qvideo-3.4.0}/lib/QVideoReader.py +1 -0
  37. {qvideo-3.2.2 → qvideo-3.4.0}/lib/QVideoScreen.py +34 -4
  38. {qvideo-3.2.2 → qvideo-3.4.0}/lib/QVideoSource.py +1 -0
  39. {qvideo-3.2.2 → qvideo-3.4.0}/lib/__init__.py +3 -3
  40. {qvideo-3.2.2 → qvideo-3.4.0}/lib/chooser.py +3 -1
  41. {qvideo-3.2.2 → qvideo-3.4.0}/lib/clickable.py +18 -12
  42. {qvideo-3.2.2 → qvideo-3.4.0}/lib/resolutions.py +3 -1
  43. qvideo-3.4.0/overlays/__init__.py +5 -0
  44. qvideo-3.4.0/overlays/trackpy.py +244 -0
  45. qvideo-3.4.0/overlays/yolo.py +21 -0
  46. {qvideo-3.2.2 → qvideo-3.4.0}/pyproject.toml +6 -2
  47. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_demo.py +8 -3
  48. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_filterdemo.py +10 -5
  49. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qblurfilter.py +1 -1
  50. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qdvrwidget.py +2 -3
  51. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qedgefilter.py +1 -1
  52. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qfilterbank.py +3 -3
  53. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qgenicamcamera.py +3 -0
  54. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qgenicamtree.py +8 -1
  55. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qopencvresolutiontree.py +15 -1
  56. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qrgbfilter.py +1 -1
  57. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qsamplehold.py +1 -1
  58. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_resolutions.py +20 -4
  59. qvideo-3.4.0/tests/test_trackpy_overlay.py +346 -0
  60. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_videofilter.py +6 -6
  61. qvideo-3.2.2/cameras/Genicam/__init__.py +0 -5
  62. {qvideo-3.2.2 → qvideo-3.4.0}/LICENSE.md +0 -0
  63. {qvideo-3.2.2 → qvideo-3.4.0}/QVideo.egg-info/dependency_links.txt +0 -0
  64. {qvideo-3.2.2 → qvideo-3.4.0}/QVideo.egg-info/top_level.txt +0 -0
  65. {qvideo-3.2.2 → qvideo-3.4.0}/__init__.py +0 -0
  66. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Basler/__init__.py +0 -0
  67. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Basler/_camera.py +0 -0
  68. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Flir/QListFlirCameras.py +0 -0
  69. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Flir/__init__.py +0 -0
  70. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/IDS/__init__.py +0 -0
  71. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/IDS/_camera.py +0 -0
  72. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/MV/__init__.py +0 -0
  73. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/MV/_camera.py +0 -0
  74. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Noise/__init__.py +0 -0
  75. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Noise/_camera.py +0 -0
  76. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Noise/_tree.py +0 -0
  77. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/OpenCV/QListCVCameras.py +0 -0
  78. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/OpenCV/__init__.py +0 -0
  79. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/OpenCV/_camera.py +0 -0
  80. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/OpenCV/_tree.py +0 -0
  81. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Picamera/__init__.py +0 -0
  82. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Picamera/_camera.py +0 -0
  83. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Picamera/_tree.py +0 -0
  84. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Vimbax/__init__.py +0 -0
  85. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Vimbax/_camera.py +0 -0
  86. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/Vimbax/_tree.py +0 -0
  87. {qvideo-3.2.2 → qvideo-3.4.0}/cameras/__init__.py +0 -0
  88. {qvideo-3.2.2 → qvideo-3.4.0}/dvr/QHDF5Reader.py +0 -0
  89. {qvideo-3.2.2 → qvideo-3.4.0}/dvr/QHDF5Writer.py +0 -0
  90. {qvideo-3.2.2 → qvideo-3.4.0}/dvr/QOpenCVReader.py +0 -0
  91. {qvideo-3.2.2 → qvideo-3.4.0}/dvr/QOpenCVWriter.py +0 -0
  92. {qvideo-3.2.2 → qvideo-3.4.0}/dvr/__init__.py +0 -0
  93. {qvideo-3.2.2 → qvideo-3.4.0}/dvr/icons_rc.py +0 -0
  94. {qvideo-3.2.2 → qvideo-3.4.0}/dvr/icons_rc_qt6.py +0 -0
  95. {qvideo-3.2.2 → qvideo-3.4.0}/filters/Normalize.py +0 -0
  96. {qvideo-3.2.2 → qvideo-3.4.0}/lib/QFPSMeter.py +0 -0
  97. {qvideo-3.2.2 → qvideo-3.4.0}/lib/QVideoWriter.py +0 -0
  98. {qvideo-3.2.2 → qvideo-3.4.0}/lib/types.py +0 -0
  99. {qvideo-3.2.2 → qvideo-3.4.0}/setup.cfg +0 -0
  100. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_chooser.py +0 -0
  101. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_clickable.py +0 -0
  102. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_icons_rc.py +0 -0
  103. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_median.py +0 -0
  104. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_momedian.py +0 -0
  105. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_normalize.py +0 -0
  106. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qbaslercamera.py +0 -0
  107. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qcamcorder.py +0 -0
  108. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qcamera.py +0 -0
  109. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qcameratree.py +0 -0
  110. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qflircamera.py +0 -0
  111. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qfpsmeter.py +0 -0
  112. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qhdf5reader.py +0 -0
  113. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qhdf5writer.py +0 -0
  114. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qidscamera.py +0 -0
  115. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qlistcameras.py +0 -0
  116. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qlistcvcameras.py +0 -0
  117. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qmvcamera.py +0 -0
  118. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qnoisecamera.py +0 -0
  119. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qnoisetree.py +0 -0
  120. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qopencvcamera.py +0 -0
  121. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qopencvreader.py +0 -0
  122. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qopencvtree.py +0 -0
  123. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qopencvwriter.py +0 -0
  124. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qpicamera.py +0 -0
  125. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qvideoreader.py +0 -0
  126. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qvideoscreen.py +0 -0
  127. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qvideosource.py +0 -0
  128. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qvideowriter.py +0 -0
  129. {qvideo-3.2.2 → qvideo-3.4.0}/tests/test_qvimbaxcamera.py +0 -0
  130. {qvideo-3.2.2 → 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.2
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
@@ -36,7 +36,7 @@ Provides-Extra: dev
36
36
  Requires-Dist: pytest; extra == "dev"
37
37
  Requires-Dist: pytest-cov; extra == "dev"
38
38
  Provides-Extra: docs
39
- Requires-Dist: furo; extra == "docs"
39
+ Requires-Dist: pydata-sphinx-theme; extra == "docs"
40
40
  Requires-Dist: sphinx; extra == "docs"
41
41
  Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
42
42
  Dynamic: license-file
@@ -47,6 +47,7 @@ Dynamic: license-file
47
47
  [![Python](https://img.shields.io/pypi/pyversions/QVideo)](https://pypi.org/project/QVideo/)
48
48
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE.md)
49
49
  [![Tests](https://github.com/davidgrier/QVideo/actions/workflows/test.yml/badge.svg)](https://github.com/davidgrier/QVideo/actions/workflows/test.yml)
50
+ [![Documentation](https://readthedocs.org/projects/qvideo/badge/?version=latest)](https://qvideo.readthedocs.io/en/latest/)
50
51
 
51
52
  **QVideo** is a framework for integrating video cameras into PyQt5 projects
52
53
  for scientific research. It provides a unified, registration-based property
@@ -156,5 +157,5 @@ additional code needed.
156
157
  ## Acknowledgements
157
158
 
158
159
  Work on this project at New York University is supported by the National
159
- 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
160
161
  by an award from the TAC Program of New York University.
@@ -3,7 +3,7 @@
3
3
 
4
4
  Run directly to launch a full camcorder application with camera selection::
5
5
 
6
- python -m QVideo.QCamcorder [-c|-f|-s|-v] [cameraID]
6
+ python -m QVideo.QCamcorder [-b|-c|-f|-i|-m|-p|-r|-v] [cameraID]
7
7
 
8
8
  Camera flags (mutually exclusive):
9
9
 
@@ -15,7 +15,9 @@ Camera flags (mutually exclusive):
15
15
  -i [cameraID] IDS Imaging camera (requires IDS peak SDK)
16
16
  -m [cameraID] MATRIX VISION mvGenTLProducer (universal GenICam, not FLIR)
17
17
  -p [cameraID] Raspberry Pi camera module (requires picamera2)
18
+ -r [cameraID] OpenCV camera with resolution drop-down selector
18
19
  -v [cameraID] Allied Vision VimbaX camera
20
+ -h Show help and exit
19
21
 
20
22
  If no flag is given, a noise camera is used as a fallback.
21
23
  '''
@@ -67,6 +69,8 @@ class QCamcorder(QWidget):
67
69
  def _setupUi(self) -> None:
68
70
  uic.loadUi(str(self.UIFILE), self)
69
71
  self.controls.layout().addWidget(self.cameraWidget)
72
+ self.layout().setStretch(0, 1) # screen takes all surplus horizontal space
73
+ self.layout().setStretch(1, 0) # controls stay at their natural width
70
74
 
71
75
  def _connectSignals(self) -> None:
72
76
  self.dvr.playing.connect(self.dvrPlayback)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QVideo
3
- Version: 3.2.2
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
@@ -36,7 +36,7 @@ Provides-Extra: dev
36
36
  Requires-Dist: pytest; extra == "dev"
37
37
  Requires-Dist: pytest-cov; extra == "dev"
38
38
  Provides-Extra: docs
39
- Requires-Dist: furo; extra == "docs"
39
+ Requires-Dist: pydata-sphinx-theme; extra == "docs"
40
40
  Requires-Dist: sphinx; extra == "docs"
41
41
  Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
42
42
  Dynamic: license-file
@@ -47,6 +47,7 @@ Dynamic: license-file
47
47
  [![Python](https://img.shields.io/pypi/pyversions/QVideo)](https://pypi.org/project/QVideo/)
48
48
  [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](LICENSE.md)
49
49
  [![Tests](https://github.com/davidgrier/QVideo/actions/workflows/test.yml/badge.svg)](https://github.com/davidgrier/QVideo/actions/workflows/test.yml)
50
+ [![Documentation](https://readthedocs.org/projects/qvideo/badge/?version=latest)](https://qvideo.readthedocs.io/en/latest/)
50
51
 
51
52
  **QVideo** is a framework for integrating video cameras into PyQt5 projects
52
53
  for scientific research. It provides a unified, registration-based property
@@ -156,5 +157,5 @@ additional code needed.
156
157
  ## Acknowledgements
157
158
 
158
159
  Work on this project at New York University is supported by the National
159
- 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
160
161
  by an award from the TAC Program of New York University.
@@ -41,6 +41,11 @@ cameras/Picamera/_tree.py
41
41
  cameras/Vimbax/__init__.py
42
42
  cameras/Vimbax/_camera.py
43
43
  cameras/Vimbax/_tree.py
44
+ demos/ROIdemo.py
45
+ demos/__init__.py
46
+ demos/demo.py
47
+ demos/filterdemo.py
48
+ demos/trackpydemo.py
44
49
  dvr/QDVRWidget.py
45
50
  dvr/QHDF5Reader.py
46
51
  dvr/QHDF5Writer.py
@@ -56,6 +61,7 @@ filters/QBlurFilter.py
56
61
  filters/QEdgeFilter.py
57
62
  filters/QRGBFilter.py
58
63
  filters/QSampleHold.py
64
+ filters/QYOLOFilter.py
59
65
  filters/_MedianBase.py
60
66
  filters/__init__.py
61
67
  lib/QCamera.py
@@ -63,16 +69,19 @@ lib/QCameraTree.py
63
69
  lib/QFPSMeter.py
64
70
  lib/QFilterBank.py
65
71
  lib/QListCameras.py
72
+ lib/QVideoFilter.py
66
73
  lib/QVideoReader.py
67
74
  lib/QVideoScreen.py
68
75
  lib/QVideoSource.py
69
76
  lib/QVideoWriter.py
70
- lib/VideoFilter.py
71
77
  lib/__init__.py
72
78
  lib/chooser.py
73
79
  lib/clickable.py
74
80
  lib/resolutions.py
75
81
  lib/types.py
82
+ overlays/__init__.py
83
+ overlays/trackpy.py
84
+ overlays/yolo.py
76
85
  tests/test_chooser.py
77
86
  tests/test_clickable.py
78
87
  tests/test_demo.py
@@ -116,4 +125,5 @@ tests/test_qvideowriter.py
116
125
  tests/test_qvimbaxcamera.py
117
126
  tests/test_resolutions.py
118
127
  tests/test_roidemo.py
128
+ tests/test_trackpy_overlay.py
119
129
  tests/test_videofilter.py
@@ -10,7 +10,7 @@ pytest
10
10
  pytest-cov
11
11
 
12
12
  [docs]
13
- furo
13
+ pydata-sphinx-theme
14
14
  sphinx
15
15
  sphinx-autodoc-typehints
16
16
 
@@ -4,6 +4,7 @@
4
4
  [![Python](https://img.shields.io/pypi/pyversions/QVideo)](https://pypi.org/project/QVideo/)
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
+ [![Documentation](https://readthedocs.org/projects/qvideo/badge/?version=latest)](https://qvideo.readthedocs.io/en/latest/)
7
8
 
8
9
  **QVideo** is a framework for integrating video cameras into PyQt5 projects
9
10
  for scientific research. It provides a unified, registration-based property
@@ -113,5 +114,5 @@ additional code needed.
113
114
  ## Acknowledgements
114
115
 
115
116
  Work on this project at New York University is supported by the National
116
- 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
117
118
  by an award from the TAC Program of New York University.
@@ -56,7 +56,7 @@ class QBaslerTree(QGenicamTree):
56
56
  controls: list[str] | None = None,
57
57
  **kwargs) -> None:
58
58
  camera = camera or QBaslerCamera(cameraID=cameraID)
59
- camera.setSettings(self._DEFAULT_SETTINGS)
59
+ camera.settings = self._DEFAULT_SETTINGS
60
60
  super().__init__(*args,
61
61
  camera=camera,
62
62
  controls=controls or self._DEFAULT_CONTROLS,
@@ -15,6 +15,14 @@ class QFlirCamera(QGenicamCamera):
15
15
 
16
16
  If Spinnaker is not installed, instantiation raises :exc:`TypeError`.
17
17
 
18
+ .. warning::
19
+
20
+ Spinnaker GenTL producer **4.3.0.189** contains a bug in which
21
+ ``DevClose()`` hangs indefinitely when the camera is released.
22
+ This causes the application to hang on exit. FLIR customer
23
+ support has confirmed the bug. If you experience this issue,
24
+ downgrade to Spinnaker **4.1.0.172** or earlier.
25
+
18
26
  Parameters
19
27
  ----------
20
28
  cameraID : int
@@ -10,7 +10,7 @@ class QFlirTree(QGenicamTree):
10
10
 
11
11
  '''Camera property tree for :class:`~QVideo.cameras.Flir.QFlirCamera`.
12
12
 
13
- Builds a :class:`~QVideo.lib.QCameraTree.QCameraTree` with a curated
13
+ Builds a :class:`~QVideo.lib.QcameraTree.QCameraTree` with a curated
14
14
  set of controls and sensible default settings for FLIR cameras.
15
15
 
16
16
  Parameters
@@ -59,7 +59,8 @@ class QFlirTree(QGenicamTree):
59
59
  controls: list[str] | None = None,
60
60
  **kwargs) -> None:
61
61
  camera = camera or QFlirCamera(cameraID=cameraID)
62
- camera.setSettings(self._DEFAULT_SETTINGS)
62
+ camera.settings = {k: v for k, v in self._DEFAULT_SETTINGS.items()
63
+ if k in camera.properties}
63
64
  super().__init__(*args,
64
65
  camera=camera,
65
66
  controls=controls or self._DEFAULT_CONTROLS,
@@ -0,0 +1,11 @@
1
+ from ._camera import QGenicamCamera, QGenicamSource
2
+ from ._tree import QGenicamTree
3
+
4
+ # Tell Sphinx that these classes belong to the public package namespace,
5
+ # not the private _camera / _tree submodules, to avoid duplicate
6
+ # object descriptions in the generated docs.
7
+ QGenicamCamera.__module__ = __name__
8
+ QGenicamSource.__module__ = __name__
9
+ QGenicamTree.__module__ = __name__
10
+
11
+ __all__ = 'QGenicamCamera QGenicamSource QGenicamTree'.split()
@@ -103,10 +103,25 @@ class QGenicamCamera(QCamera):
103
103
 
104
104
  @staticmethod
105
105
  def _make_getter(feature: IValue):
106
- '''Return a zero-argument callable that reads the feature value.'''
106
+ '''Return a zero-argument callable that reads the feature value.
107
+
108
+ The returned callable checks the current access mode before reading,
109
+ returning ``None`` for features that are not readable at call time
110
+ (e.g. enumeration nodes whose access mode changes after acquisition
111
+ starts).
112
+ '''
107
113
  if isinstance(feature, IEnumeration):
108
- return lambda: feature.to_string()
109
- return lambda: feature.value
114
+ def getter():
115
+ if feature.node.get_access_mode() in (EAccessMode.RO, EAccessMode.RW):
116
+ return feature.to_string()
117
+ return None
118
+ return getter
119
+
120
+ def getter():
121
+ if feature.node.get_access_mode() in (EAccessMode.RO, EAccessMode.RW):
122
+ return feature.value
123
+ return None
124
+ return getter
110
125
 
111
126
  @staticmethod
112
127
  def _feature_ptype(feature: IValue) -> type:
@@ -179,8 +194,7 @@ class QGenicamCamera(QCamera):
179
194
  return
180
195
  name = feature.node.name
181
196
  getter = self._make_getter(feature)
182
- writable = (mode == EAccessMode.RW) or (name in self.protected)
183
- setter = self._make_setter(feature, name) if writable else None
197
+ setter = self._make_setter(feature, name)
184
198
  self.registerProperty(name,
185
199
  getter=getter,
186
200
  setter=setter,
@@ -271,6 +285,7 @@ class QGenicamCamera(QCamera):
271
285
  self.harvester.reset()
272
286
  except Exception:
273
287
  pass
288
+ self.nodeMap = None
274
289
 
275
290
  def _deinitialize(self) -> None:
276
291
  '''Stop acquisition and release the GenICam device.'''
@@ -343,6 +358,28 @@ class QGenicamCamera(QCamera):
343
358
  logger.warning(f'node {name} is unknown')
344
359
  return None
345
360
 
361
+ _SHAPE_ALIASES = frozenset(('width', 'height'))
362
+
363
+ @property
364
+ def settings(self) -> QCamera.Settings:
365
+ '''All registered property values, excluding lowercase shape aliases.
366
+
367
+ GenICam cameras register ``width``/``height`` as lowercase aliases for
368
+ ``Width``/``Height`` so that :attr:`~QVideo.lib.QCamera.QCamera.shape`
369
+ and attribute access (``camera.width``) work the same as on other
370
+ backends. Those aliases are excluded here so that
371
+ :class:`~QVideo.cameras.Genicam._tree.QGenicamTree` does not try to
372
+ sync them to tree parameters (which use the canonical GenICam names).
373
+ '''
374
+ return {name: spec['getter']()
375
+ for name, spec in self._properties.items()
376
+ if name not in self._SHAPE_ALIASES}
377
+
378
+ @settings.setter
379
+ def settings(self, settings: QCamera.Settings) -> None:
380
+ for key, value in settings.items():
381
+ self.set(key, value)
382
+
346
383
  def is_readwrite(self, feature: str) -> bool:
347
384
  '''Return ``True`` if the named feature is currently writable.
348
385
 
@@ -4,6 +4,7 @@ from QVideo.cameras.Genicam import QGenicamCamera
4
4
  from genicam.genapi import (IValue, EAccessMode, EVisibility,
5
5
  ICategory, ICommand, IEnumeration,
6
6
  IBoolean, IInteger, IFloat, IString)
7
+ from pyqtgraph.Qt import QtCore
7
8
  import logging
8
9
 
9
10
 
@@ -21,6 +22,12 @@ class QGenicamTree(QCameraTree):
21
22
  GenICam node map and exposes visibility and per-feature enable/disable
22
23
  controls.
23
24
 
25
+ A timer polls the camera periodically so that autonomous camera-side
26
+ changes (e.g. ``Gain`` being adjusted by auto-exposure, ``GainAuto``
27
+ reverting from ``"Once"`` to ``"Off"``) are reflected in the UI.
28
+ ``PyNodeCallback`` is not used because it only fires when the **host**
29
+ writes a node, not when the camera changes a value autonomously.
30
+
24
31
  Parameters
25
32
  ----------
26
33
  camera : QGenicamCamera
@@ -48,12 +55,23 @@ class QGenicamTree(QCameraTree):
48
55
  self.controls = controls
49
56
  self.visibility = visibility
50
57
  self._updateEnabled()
58
+ self._pollTimer = QtCore.QTimer(self)
59
+ self._pollTimer.setInterval(500)
60
+ self._pollTimer.timeout.connect(self._pollCamera)
61
+ self._pollTimer.start()
62
+ QtCore.QCoreApplication.instance().aboutToQuit.connect(self._pollTimer.stop)
63
+
64
+ def closeEvent(self, event) -> None:
65
+ self._pollTimer.stop()
66
+ super().closeEvent(event)
51
67
 
52
68
  def description(self, camera: QGenicamCamera) -> dict:
53
69
  '''Return a dictionary describing the node map of the camera'''
54
70
  root = camera.node('Root')
71
+ if root is None:
72
+ return []
55
73
  description = self.describe(root)
56
- return description['children']
74
+ return description.get('children', [])
57
75
 
58
76
  def describe(self, feature: IValue) -> dict[str, object]:
59
77
  '''Return a dictionary describing the specified feature'''
@@ -110,6 +128,8 @@ class QGenicamTree(QCameraTree):
110
128
  p.sigValueChanged.connect(self._handleItemChanges)
111
129
 
112
130
  def _handleItemChanges(self) -> None:
131
+ if self._ignoreSync:
132
+ return
113
133
  logger.debug('Handling item changes')
114
134
  self._updateVisible()
115
135
  self._updateEnabled()
@@ -168,6 +188,53 @@ class QGenicamTree(QCameraTree):
168
188
  finally:
169
189
  p.blockSignals(False)
170
190
 
191
+ def _pollCamera(self) -> None:
192
+ '''Refresh all readable Parameter values and enabled states.
193
+
194
+ Called by the poll timer to pick up autonomous camera-side changes
195
+ that do not generate host-side notifications — e.g. ``Gain`` being
196
+ adjusted during auto-exposure, or ``GainAuto`` reverting from
197
+ ``"Once"`` to ``"Off"`` after the sweep completes.
198
+
199
+ Returns immediately if the camera is no longer open (e.g. during
200
+ application shutdown) to prevent accessing freed C++ genapi objects.
201
+
202
+ Signals are not blocked so that the visual widgets update and
203
+ ``_handleItemChanges`` fires when a value changes.
204
+ :attr:`_ignoreSync` is set for the duration so that the resulting
205
+ ``sigTreeStateChanged`` emissions do not send values back to the
206
+ camera. :meth:`_updateEnabled` is called unconditionally so that
207
+ access-mode changes (e.g. ``ExposureTime`` becoming writable again
208
+ after the sweep) are reflected even when the controlling node value
209
+ has not changed.
210
+ '''
211
+ if not self.camera.isOpen():
212
+ return
213
+ self._ignoreSync = True
214
+ try:
215
+ self._updateLimits()
216
+ for item in self.listAllItems()[1:]:
217
+ p = item.param
218
+ if not p.opts.get('visible', False):
219
+ continue
220
+ name = p.opts.get('name')
221
+ if not self.camera.has_node(name):
222
+ continue
223
+ node = self.camera.node(name)
224
+ mode = node.node.get_access_mode()
225
+ if mode not in (EAccessMode.RO, EAccessMode.RW):
226
+ continue
227
+ if isinstance(node, IEnumeration):
228
+ value = node.to_string()
229
+ elif isinstance(node, (IBoolean, IInteger, IFloat, IString)):
230
+ value = node.value
231
+ else:
232
+ continue
233
+ p.setValue(value)
234
+ self._updateEnabled()
235
+ finally:
236
+ self._ignoreSync = False
237
+
171
238
  def _updateLimits(self) -> None:
172
239
  '''Refresh Parameter constraints from the live GenICam node values.
173
240
 
@@ -54,7 +54,7 @@ class QIDSTree(QGenicamTree):
54
54
  controls: list[str] | None = None,
55
55
  **kwargs) -> None:
56
56
  camera = camera or QIDSCamera(cameraID=cameraID)
57
- camera.setSettings(self._DEFAULT_SETTINGS)
57
+ camera.settings = self._DEFAULT_SETTINGS
58
58
  super().__init__(*args,
59
59
  camera=camera,
60
60
  controls=controls or self._DEFAULT_CONTROLS,
@@ -54,7 +54,7 @@ class QMVTree(QGenicamTree):
54
54
  controls: list[str] | None = None,
55
55
  **kwargs) -> None:
56
56
  camera = camera or QMVCamera(cameraID=cameraID)
57
- camera.setSettings(self._DEFAULT_SETTINGS)
57
+ camera.settings = self._DEFAULT_SETTINGS
58
58
  super().__init__(*args,
59
59
  camera=camera,
60
60
  controls=controls or self._DEFAULT_CONTROLS,
@@ -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
 
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env python3
2
+ '''Camcorder demo with a draggable ROI for recording a cropped video region.
3
+
4
+ Run directly::
5
+
6
+ python -m QVideo.demos.ROIdemo
7
+ '''
8
+
9
+ from QVideo.QCamcorder import QCamcorder
10
+ from pyqtgraph.Qt.QtCore import pyqtSignal, pyqtSlot
11
+ import pyqtgraph as pg
12
+ import numpy as np
13
+ from pathlib import Path
14
+
15
+
16
+ __all__ = ['ROIFilter', 'ROIDemo']
17
+
18
+
19
+ class ROIFilter(pg.RectROI):
20
+ '''Draggable rectangular ROI that crops video frames to its bounds.
21
+
22
+ Subclasses :class:`pyqtgraph.RectROI` and adds a :attr:`newFrame`
23
+ signal and :meth:`crop` slot, making it compatible with
24
+ :class:`~QVideo.lib.QVideoWriter` as a frame source.
25
+
26
+ Parameters
27
+ ----------
28
+ fps : float
29
+ Frame rate of the video source [frames per second].
30
+ Stored as an attribute for :class:`~QVideo.lib.QVideoWriter`
31
+ compatibility.
32
+ pos : list[float]
33
+ Initial [x, y] position of the ROI.
34
+ size : list[float]
35
+ Initial [width, height] of the ROI.
36
+ **kwargs :
37
+ Additional keyword arguments forwarded to
38
+ :class:`~pyqtgraph.RectROI`.
39
+
40
+ Signals
41
+ -------
42
+ newFrame : np.ndarray
43
+ Emitted with the cropped frame each time :meth:`crop` is called.
44
+ '''
45
+
46
+ #: Emitted with the cropped frame each time :meth:`crop` is called.
47
+ newFrame = pyqtSignal(np.ndarray)
48
+
49
+ def __init__(self, fps: float, *args, **kwargs) -> None:
50
+ super().__init__(*args, **kwargs)
51
+ self.fps = fps
52
+
53
+ @pyqtSlot(np.ndarray)
54
+ def crop(self, frame: np.ndarray) -> None:
55
+ '''Crop *frame* to the current ROI bounds and emit :attr:`newFrame`.
56
+
57
+ Parameters
58
+ ----------
59
+ frame : np.ndarray
60
+ Input video frame to crop.
61
+ '''
62
+ x, y = self.pos()
63
+ w, h = self.size()
64
+ crop = frame[int(y):int(y + h), int(x):int(x + w)]
65
+ self.newFrame.emit(crop)
66
+
67
+
68
+ class ROIDemo(QCamcorder):
69
+ '''Camcorder demo with a draggable ROI for recording cropped video.
70
+
71
+ Subclasses :class:`~QVideo.QCamcorder.QCamcorder` and overlays a
72
+ resizable :class:`ROIFilter` on the video screen. While recording,
73
+ camera frames are routed through the ROI cropper before being saved
74
+ by the DVR.
75
+
76
+ Parameters
77
+ ----------
78
+ cameraTree : QCameraTree
79
+ Camera control tree providing the video source.
80
+ **kwargs :
81
+ Additional keyword arguments forwarded to
82
+ :class:`~QVideo.QCamcorder.QCamcorder`.
83
+
84
+ Attributes
85
+ ----------
86
+ ROI_POS : list[int]
87
+ Default [x, y] position of the ROI overlay. Default: ``[100, 100]``.
88
+ ROI_SIZE : list[int]
89
+ Default [width, height] of the ROI overlay. Default: ``[400, 400]``.
90
+ '''
91
+
92
+ ROI_POS: list[int] = [100, 100]
93
+ ROI_SIZE: list[int] = [400, 400]
94
+
95
+ def _setupUi(self) -> None:
96
+ super()._setupUi()
97
+ self.roi = ROIFilter(self.source.fps,
98
+ self.ROI_POS, self.ROI_SIZE,
99
+ snapSize=8,
100
+ scaleSnap=True,
101
+ sideScalers=True,
102
+ movable=True,
103
+ invertible=False,
104
+ rotatable=False,
105
+ removable=False)
106
+ self.screen.view.addItem(self.roi)
107
+ self.dvr.filename = str(Path.home() / 'roidemo.avi')
108
+
109
+ def _connectSignals(self) -> None:
110
+ super()._connectSignals()
111
+ self.dvr.source = self.roi
112
+ self.dvr.recording.connect(self.recording)
113
+
114
+ @pyqtSlot(bool)
115
+ def recording(self, is_recording: bool) -> None:
116
+ '''Respond to DVR recording state changes.
117
+
118
+ Locks the ROI and connects the camera source to the ROI cropper
119
+ when recording starts; unlocks and disconnects when stopped.
120
+
121
+ Parameters
122
+ ----------
123
+ is_recording : bool
124
+ ``True`` when the DVR starts recording, ``False`` when stopped.
125
+ '''
126
+ if is_recording:
127
+ self.roi.movable = False
128
+ self.source.newFrame.connect(self.roi.crop)
129
+ else:
130
+ self.roi.movable = True
131
+ self.source.newFrame.disconnect(self.roi.crop)
132
+
133
+
134
+ def main() -> None: # pragma: no cover
135
+ '''Launch the ROI demo with an interactively chosen camera.'''
136
+ from QVideo.lib import choose_camera
137
+
138
+ pg.mkQApp()
139
+ camera = choose_camera().start()
140
+ widget = ROIDemo(camera)
141
+ widget.show()
142
+ pg.exec()
143
+
144
+
145
+ if __name__ == '__main__': # pragma: no cover
146
+ main()
@@ -0,0 +1,69 @@
1
+ '''Runnable demo applications built on the QVideo framework.
2
+
3
+ Each demo composes a :class:`~QVideo.lib.QCameraTree.QCameraTree` with one
4
+ or more QVideo widgets into a standalone window. All demos use
5
+ :func:`~QVideo.lib.chooser.choose_camera` to present a camera-selection
6
+ dialog at startup, so no camera-specific code is needed in the demo itself.
7
+
8
+ Demos
9
+ -----
10
+ demo
11
+ Minimal layout: live video screen alongside a camera control tree.
12
+ The starting point for building a custom camera application.
13
+
14
+ filterdemo
15
+ Extends :mod:`demo` with a :class:`~QVideo.lib.QFilterBank.QFilterBank`
16
+ panel so that image-processing filters can be toggled and adjusted
17
+ alongside the live feed.
18
+
19
+ ROIdemo
20
+ Extends :class:`~QVideo.QCamcorder.QCamcorder` with a draggable
21
+ rectangular ROI overlay. Only the cropped region is saved when
22
+ the DVR records, making it easy to capture a sub-region of interest.
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
+ Running
30
+ -------
31
+ Each demo can be launched directly::
32
+
33
+ python -m QVideo.demos.demo
34
+ python -m QVideo.demos.filterdemo
35
+ python -m QVideo.demos.ROIdemo
36
+ python -m QVideo.demos.trackpydemo
37
+
38
+ Camera selection
39
+ ----------------
40
+ All demos accept the same command-line flags to select a camera backend.
41
+ If no flag is given, a noise camera is used as a fallback.
42
+
43
+ .. code-block:: text
44
+
45
+ -b [cameraID] Basler camera (requires pylon SDK)
46
+ -c [cameraID] OpenCV camera
47
+ -f [cameraID] FLIR camera (requires Spinnaker SDK)
48
+ -i [cameraID] IDS Imaging camera (requires IDS peak SDK)
49
+ -m [cameraID] MATRIX VISION mvGenTLProducer (universal GenICam, not FLIR)
50
+ -p [cameraID] Raspberry Pi camera module (requires picamera2)
51
+ -r [cameraID] OpenCV camera with resolution drop-down selector
52
+ -v [cameraID] Allied Vision VimbaX camera
53
+ -h Show help and exit
54
+
55
+ ``cameraID`` is an optional integer index (default ``0``) used when
56
+ multiple cameras of the same type are connected. The flags are mutually
57
+ exclusive — only one backend can be selected at a time.
58
+
59
+ Example::
60
+
61
+ python -m QVideo.demos.demo -f # first FLIR camera
62
+ python -m QVideo.demos.filterdemo -c 1 # second OpenCV camera
63
+ '''
64
+
65
+ from .demo import Demo
66
+ from .filterdemo import FilterDemo
67
+ from .ROIdemo import ROIFilter, ROIDemo
68
+ from .trackpydemo import TrackpyDemo
69
+