CVBpy 14.0
gevserver/QmlGevServer
1# ---------------------------------------------------------------------------
2# The QmlGevServer example starts a QML GevServer which can load a device
3# and stream the device image.
4# ---------------------------------------------------------------------------
5
6import os, sys
7
8import cvb
9import cvb.gevserver as gs
10import cvb.ui
11
12from PySide2.QtWidgets import QApplication
13from PySide2.QtQuick import QQuickView
14from PySide2.QtCore import QObject, Signal, Slot, Property, QUrl
15from PySide2.QtGui import QIcon
16
17class StreamProcessingHandler(cvb.SingleStreamHandler):
18 def __init__(self, stream):
19 super().__init__(stream)
20
21 def handle_async_wait_result(self, image, status):
22 if status is cvb.WaitStatus.Ok:
23 print("New image arrived")
24 # display image or
25 if BackEnd.grab_image or BackEnd.single_shot:
26 BackEnd.single_shot = False
27 BackEnd.image_controller.refresh(image)
28
29 if BackEnd.has_ring_buffer:
30 image.unlock()
31 # send image
32 elif BackEnd.server_started and (not BackEnd.server_single_shot_send_enabled or BackEnd.server_single_shot_send) and BackEnd.server.stream.is_running:
33 try:
34 print('Sending image')
35 BackEnd.server_single_shot_send = False
36 if BackEnd.has_ring_buffer:
37 BackEnd.buffer_index = image.buffer_index
38 BackEnd.img_list[image.buffer_index] = image
39
40 def image_release(img):
41 img.unlock()
42 BackEnd.img_list[BackEnd.buffer_index].unlock()
43 print('Unlocked buffer at index ' + str(img.buffer_index))
44
45 BackEnd.server.stream.send(image, image_release)
46 BackEnd.image_controller.refresh(BackEnd.img_list[BackEnd.buffer_index])
47 else:
48 BackEnd.server.stream.send(image)
49 BackEnd.image_controller.refresh(image)
50 except:
51 print('Failed to send image')
52 elif BackEnd.has_ring_buffer:
53 print("Unlock unused image")
54 image.unlock()
55
56
57class BackEnd(QObject):
58 connection_list_changed = Signal()
59 server_started = False
60 server_single_shot_send_enabled = False
61 server_single_shot_send = False
62 single_shot = False
63 server = None
64 grab_image = False
65 has_ring_buffer = False
66 img_list = list()
67 buffer_index = 0
68 # create an image controller to interact with the UI
69 image_controller = cvb.ui.ImageController()
70
71 def __init__(self):
72 QObject.__init__(self)
73 self.start_in_progress = False
74 self.window_state_reg_node = None
75 self.event_cookie = None
76
77 # load the device
78 self.device = cvb.DeviceFactory.open(
79 os.path.join(cvb.install_path(), 'drivers', 'CVMock.vin'),
80 cvb.AcquisitionStack.Vin)
81 self.stream = self.device.stream()
82
83 BackEnd.has_ring_buffer = bool(self.stream.ring_buffer)
84 if BackEnd.has_ring_buffer:
85 self.stream.ring_buffer.lock_mode = cvb.RingBufferLockMode.On
86 BackEnd.img_list = [None] * self.stream.ring_buffer.count
87
88 # use a single stream handler to setup an acquisition thread and acquire images
89 self.handler = StreamProcessingHandler(self.stream)
90 self.view = view
91 self.view.rootContext().setContextProperty('mainImage', BackEnd.image_controller)
92 # register the device image with UI controller to trigger automatic refreshes
93 self.stream.get_snapshot()
94 BackEnd.image_controller.refresh(self.device.device_image)
95 self.handler.run()
96
97 # fill combo box with all available connections
98 self.connections = gs.LogicalNetworkInterface.get_all_available()
99 self.m_connection_list = list()
100 for connection in self.connections:
101 self.m_connection_list.append(connection.ip_address() + ' (' + connection.ipv4_mask() + ')')
102 self.connection_list_changed.emit()
103
104 def __enter__(self):
105 return self
106
107 def __exit__(self, exc_type, exc_val, exc_tb):
108 if self.handler.is_active:
109 self.handler.finish()
110 if BackEnd.server:
111 BackEnd.server = None
112
113 @Property('QVariantList', notify=connection_list_changed)
114 def connection_list(self):
115 return self.m_connection_list
116
117 @Property('QUrl')
118 def driver_path(self):
119 return QUrl.fromLocalFile(cvb.expand_path('%CVB%drivers'))
120
121 @Property('QUrl')
122 def desktop_path(self):
123 return QUrl.fromLocalFile(os.path.expanduser("~/Desktop"))
124
125 @Slot(bool)
126 def switch_grab(self, switched):
127 if switched and not self.handler.is_active:
128 self.handler.run()
129 BackEnd.grab_image = switched
130
131 def reset_ui(self):
132 print("Resetting ui")
133 if BackEnd.grab_image:
134 print('Resetting grab switch')
135 BackEnd.grab_image = False
136 switch = self.view.findChild(QObject, 'switchGrab')
137 switch.setProperty('checked', False)
138
139 def stop_grab(self):
140 print('Stopping grab')
141 self.reset_ui()
142 if self.stream.is_running:
143 self.handler.try_finish()
144 print('Grab stopped')
145
146 @Slot(str)
147 def load_device(self, path):
148 print('Loading device from ' + path)
149 self.stop_grab()
150 try:
151 tmp_device = cvb.DeviceFactory.open(path, cvb.AcquisitionStack.Vin)
152 self.device = tmp_device
153 self.stream = self.device.stream()
154 self.handler = StreamProcessingHandler(self.stream)
155
156 self.stream.get_snapshot()
157 BackEnd.has_ring_buffer = bool(self.stream.ring_buffer)
158 if BackEnd.has_ring_buffer:
159 self.stream.ring_buffer.lock_mode = cvb.RingBufferLockMode.On
160 BackEnd.img_list = [None] * self.stream.ring_buffer.count
161 BackEnd.image_controller.refresh(self.device.device_image)
162 self.handler.run()
163 except:
164 print('Failed to load new device')
165
166 @Slot(str)
167 def save_image(self, path):
168 print('Saving image to ' + path)
169 self.device.device_image.save(path)
170
171 @Slot()
172 def btn_single_shot(self):
173 print('Single Shot')
174 self.reset_ui()
175 if not self.handler.is_active:
176 self.handler.run()
177 BackEnd.single_shot = True
178
179 @Slot(bool)
180 def switch_enable_single_shot_send(self, enabled):
181 BackEnd.server_single_shot_send_enabled = enabled
182
183 @Slot()
184 def btn_single_shot_send(self):
185 if BackEnd.server.stream.is_running:
186 print('Send single shot')
187 BackEnd.server_single_shot_send = True
188 else:
189 print('Acquisition not enabled on server')
190
191 @Slot(int, int)
192 def start_server(self, connection_index, driver_type_index):
193 print('connection_index', connection_index, 'driver_type_index', driver_type_index)
194 # check for recursions
195 if self.start_in_progress:
196 return
197
198 self.start_in_progress = True
199
200 try:
201 if BackEnd.server_started is False:
202 self.reset_ui()
203
204 # create server
205 BackEnd.server = gs.Server.create_with_const_size(self.device.device_image.size,
206 self.device.device_image.color_model,
207 self.device.device_image.planes[0].data_type,
208 driver_type_index)
209
210 self.add_genicam_features()
211
212 BackEnd.server.user_version = 'Python GevServer'
213 BackEnd.server.node_map.xml_file_version = cvb.GenApiVersion(1, 0, 0)
214
215 if(BackEnd.has_ring_buffer):
216 BackEnd.server.stream.resend_buffers_count = 2
217 else:
218 BackEnd.server.stream.resend_buffers_count = 0
219
220 # start server
221 BackEnd.server.start(self.connections[connection_index].ip_address())
222
223 # start acquisition
224 BackEnd.server_started = True
225 print('Started server')
226 else:
227 BackEnd.server.stop()
228 BackEnd.server_started = False
229 print('Server stopped')
230
231 except:
232 print('Failed to start/stop the server')
233 self.start_in_progress = False
234
235 def on_window_size_changed(self, valueNode):
236 print('Window size change callback called')
237 val = self.window_state_reg_node.value
238 if val is 0:
239 self.view.showMinimized()
240 elif val is 1:
241 self.view.showNormal()
242 elif val is 2:
243 view.showMaximized()
244
245
246 def add_genicam_features(self):
247 cat_node = gs.CategoryNode.create('Cust::CustomFeatures')
248 BackEnd.server.node_map.add_node(cat_node)
249 cat_node.display_name = 'Custom Features'
250 cat_node.tool_tip = 'Contains all application defined features'
251 root_node = BackEnd.server.node_map['Root']
252 root_node.add(cat_node, gs.NodeList.Child)
253
254 self.window_state_reg_node = gs.Int32RegNode.create('Cust::WindowStateReg')
255 BackEnd.server.node_map.add_node(self.window_state_reg_node)
256 self.window_state_reg_node.visibility = cvb.Visibility.Invisible
257 # no cache because of polling
258 self.window_state_reg_node.cache_mode = cvb.CacheMode.NoCache
259 # polling time in ms
260 self.window_state_reg_node.polling_time = 333
261 self.window_state_reg_node.value = 1 # view in normal mode
262 self.event_cookie = self.window_state_reg_node.register_event_written_updated(self.on_window_size_changed)
263
264 enumeration_node = gs.EnumerationNode.create('Cust::WindowState')
265 BackEnd.server.node_map.add_node(enumeration_node)
266 enumeration_node.display_name = 'Window State'
267 enumeration_node.tool_tip = 'Current window state of server application'
268 enumeration_node.value_config_as_node = self.window_state_reg_node
269
270 minimized_node = gs.EnumEntryNode.create('Cust::Minimized')
271 minimized_node.numeric_value = 0
272 enumeration_node.add(minimized_node, gs.NodeList.Child)
273
274 normal_node = gs.EnumEntryNode.create('Cust::Normal')
275 normal_node.numeric_value = 1 # must be ascending entry
276 enumeration_node.add(normal_node, gs.NodeList.Child)
277
278 max_node = gs.EnumEntryNode.create('Cust::Maximized')
279 max_node.numeric_value = 2
280 enumeration_node.add(max_node, gs.NodeList.Child)
281
282 cat_node.add(enumeration_node, gs.NodeList.Child)
283
284
285if __name__ == '__main__':
286 sys.argv += ['--style', 'Windows']
287 app = QApplication(sys.argv)
288 app.setOrganizationName('STEMMER IMAGING')
289 app.setOrganizationDomain('https://www.stemmer-imaging.com/')
290 app.setApplicationName('GevServer Python tutorial')
291
292 # tell Windows the correct AppUserModelID for this process (shows icon in the taskbar)
293 if sys.platform == 'win32':
294 import ctypes
295 myappid = u'stemmerimaging.commonvisionblox.pygevserver.0'
296 ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
297
298 app.setWindowIcon(QIcon('Tutorial-Python_32x32.png'))
299
300 # register the display component with QML
302
303 # setup the QML UI
304 view = QQuickView()
305 view.setResizeMode(QQuickView.SizeRootObjectToView)
306
307 # create backend object
308 with BackEnd() as backEnd:
309
310 # register it to the context of QML
311 view.rootContext().setContextProperty('backEnd', backEnd)
312 # load the qml file into the engine
313 view.setSource(QUrl('main.qml'))
314 # call single shot to update the UI
315 backEnd.btn_single_shot()
316
317 view.resize(640, 390)
318 view.show()
319
320 # start the UI event handler
321 app.exec_()
322
Union[cvb.GenICamDevice, cvb.VinDevice, cvb.EmuDevice, cvb.VideoDevice, cvb.NonStreamingDevice] open(str provider, int acquisition_stack=cvb.AcquisitionStack.PreferVin)
Opens a device with the given provider and acquisition stack.
Definition: __init__.py:1327
Version information for GenICam related objects.
Definition: __init__.py:1707
Handler object for a single stream.
Definition: __init__.py:4380
Controller object for the QML image view item.
Definition: __init__.py:14
None register(cls, str uri="CvbQuick", int version_major=1, int version_minor=0, str qml_name="ImageView")
Convenience method to register this type or a derived type in QML.
Definition: __init__.py:193
Common Vision Blox GevServer module for Python.
Definition: __init__.py:1
Common Vision Blox UI module for Python.
Definition: __init__.py:1
str install_path()
Directory Common Vision Blox has been installed to.
Definition: __init__.py:7146
str expand_path(str path)
Expands a path containing an environment variable.
Definition: __init__.py:7046
1import QtQuick 2.0
2import CvbQuick 1.0 as CvbQuick
3import QtQuick.Controls 2.12
4import QtQuick.Layouts 1.12
5import QtQuick.Dialogs 1.3
6import QtQuick.Controls.Universal 2.4
7import QtQuick.Window 2.3
8
9
10GroupBox {
11 id: gridBox
12 width: 640
13 height: 390
14 Layout.fillWidth: true
15 Layout.fillHeight: true
16
17 property var loadFile: true
18 property var enableSingleShotSend: false
19
20 Universal.theme: Universal.System
21
22 background: Rectangle {
23 width: parent.width
24 height: parent.height
25 color: Universal.background
26 }
27
28 FileDialog {
29 id: fileDialog
30 modality: Qt.NonModal
31 title: loadFile ? "Select an image or driver to load" : "Save image"
32 selectExisting: loadFile
33 selectFolder: false
34 nameFilters: ["Drivers (*.vin *.emu)", "Image files (*png *.bmp)", "All files (*)"]
35 selectedNameFilter: loadFile ? "Drivers" : "Image files"
36 sidebarVisible: true
37 onAccepted: {
38 var path = fileUrl.toString();
39 // remove prefixed "file:///"
40 path = path.replace(/^(file:\/{3})/,"");
41 // unescape html codes like '%23' for '#'
42 var cleanPath = decodeURIComponent(path);
43
44 loadFile ? backEnd.load_device(path) : backEnd.save_image(path)
45 }
46 }
47
48 GridLayout {
49 id: gridLayout
50 rows: 4
51 flow: GridLayout.TopToBottom
52 anchors.fill: parent
53
54 ComboBox
55 {
56 id: connectionCombo
57 objectName: "connectionCombo"
58 enabled: btnStartServer.m_server_disabled
59 Layout.fillWidth: true
60 Layout.columnSpan: 2
61 model: backEnd.connection_list
62 }
63
64 Rectangle {
65 id: page
66 Layout.rowSpan: 3
67 Layout.columnSpan: 3
68 Layout.fillHeight: true
69 Layout.fillWidth: true
70 CvbQuick.ImageView
71 {
72 id: view
73 image: mainImage
74 anchors.fill: parent
75 }
76 }
77
78 ComboBox
79 {
80 id: driverTypeCombo
81 enabled: btnStartServer.m_server_disabled
82 Layout.fillWidth: true
83 model:
84 (Qt.platform.os == "windows") ?
85 [
86 qsTr("Socket Driver (loopback)"),
87 qsTr("Filter Driver (default)")
88 ]
89 :
90 [
91 qsTr("Socket Driver")
92 ]
93 }
94
95 Button
96 {
97 objectName: "btnStartServer"
98 id: btnStartServer
99 implicitWidth: btnLoadDevice.width
100 property var m_server_disabled: true
101 text: m_server_disabled ? qsTr("Start Server") : qsTr("Stop Server")
102 onClicked: {
103 m_server_disabled = m_server_disabled ? false : true
104 backEnd.start_server(connectionCombo.currentIndex, driverTypeCombo.currentIndex)
105 }
106 }
107
108 Column
109 {
110 spacing: 5
111
112 Button
113 {
114 objectName: "btnLoadDevice"
115 id: btnLoadDevice
116 enabled: btnStartServer.m_server_disabled
117 text: qsTr("Load Device")
118 onClicked:
119 {
120 loadFile = true
121 fileDialog.folder = backEnd.driver_path
122 fileDialog.open()
123 }
124
125 }
126
127 Button
128 {
129 objectName: "btnSaveImg"
130 id: btnSaveImg
131 implicitWidth: btnLoadDevice.width
132 text: qsTr("Save Image")
133 onClicked:
134 {
135 loadFile = false
136 fileDialog.folder = backEnd.desktop_path
137 fileDialog.open()
138 }
139 }
140
141 Button
142 {
143 objectName: "btnSingleShot"
144 id: btnSingleShot
145 implicitWidth: btnLoadDevice.width
146 width: btnSaveImg.width
147 enabled: btnStartServer.m_server_disabled
148 text: qsTr("Single Shot")
149 onClicked: backEnd.btn_single_shot()
150 }
151
152 Switch
153 {
154 objectName: "switchEnableSingleShotSend"
155 id: switchEnableSingleShotSend
156 text: qsTr("Enable Single Shot Send")
157 checked: false
158 enabled: !btnStartServer.m_server_disabled
159 onCheckedChanged:
160 {
161 enableSingleShotSend = enableSingleShotSend ? false : true
162 backEnd.switch_enable_single_shot_send(checked)
163 }
164 }
165
166 Button
167 {
168 objectName: "btnSingleShotSend"
169 id: btnSingleShotSend
170 implicitWidth: btnLoadDevice.width
171 enabled: !btnStartServer.m_server_disabled && enableSingleShotSend
172 text: qsTr("Single Shot Send")
173 onClicked: backEnd.btn_single_shot_send()
174 }
175 }
176
177 Switch
178 {
179 objectName: "switchGrab"
180 id: switchGrab
181 text: qsTr("Grab")
182 checked: false
183 enabled: btnStartServer.m_server_disabled
184 onCheckedChanged: backEnd.switch_grab(checked)
185 }
186 }
187}