gevserver/QmlGevServer
1 # ---------------------------------------------------------------------------
2 # The QmlGevServer example starts a QML GevServer which can load a device
3 # and stream the device image.
4 # ---------------------------------------------------------------------------
5 
6 import os, sys
7 
8 import cvb
9 import cvb.gevserver as gs
10 import cvb.ui
11 
12 from PySide2.QtWidgets import QApplication
13 from PySide2.QtQuick import QQuickView
14 from PySide2.QtCore import QObject, Signal, Slot, Property, QUrl
15 from PySide2.QtGui import QIcon
16 
17 class 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 
57 class 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 == 0:
239  self.view.showMinimized()
240  elif val == 1:
241  self.view.showNormal()
242  elif val == 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 
285 if __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 
Controller object for the QML image view item.
Definition: __init__.py:14
Common Vision Blox UI module for Python.
Definition: __init__.py:1
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
Handler object for a single stream.
Definition: __init__.py:4387
str expand_path(str path)
Expands a path containing an environment variable.
Definition: __init__.py:7053
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
str install_path()
Directory Common Vision Blox has been installed to.
Definition: __init__.py:7153
Version information for GenICam related objects.
Definition: __init__.py:1707
1 import QtQuick 2.0
2 import CvbQuick 1.0 as CvbQuick
3 import QtQuick.Controls 2.12
4 import QtQuick.Layouts 1.12
5 import QtQuick.Dialogs 1.3
6 import QtQuick.Controls.Universal 2.4
7 import QtQuick.Window 2.3
8 
9 
10 GroupBox {
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 }