GevServer/QmlGevServer

This example requires Qt5 >= 5.9 setup for building.

You may build it with Ubuntu 18.04's default Qt5 after installing:

1 // ---------------------------------------------------------------------------
4 // ---------------------------------------------------------------------------
5 
6 #include <iostream>
7 
8 #include <QApplication>
9 #include <QIcon>
10 #include <QMetaType>
11 #include <QQmlContext>
12 #include <QQuickStyle>
13 #include <QQuickView>
14 
15 #include <cvb/image.hpp>
16 
17 #include "backend.hpp"
18 
19 int main(int argc, char *argv[])
20 {
21  try
22  {
23  QQuickStyle::setStyle("Windows");
24  QQuickStyle::setFallbackStyle("Universal");
25  QApplication app(argc, argv);
26  app.setOrganizationName("STEMMER IMAGING");
27  app.setOrganizationDomain("https://www.stemmer-imaging.com/");
28  app.setApplicationName("GevServer C++ tutorial");
29 
30  // Register QML components for an image display
32 
33  QQuickView *view = new QQuickView;
34  view->setResizeMode(QQuickView::SizeRootObjectToView);
35  view->setIcon(QIcon(":/qttutorial.png"));
36 
37  view->resize(640, 390);
38  view->show();
39 
40  // Set context property before creating QML file
41  BackEnd be(view);
42  view->rootContext()->setContextProperty("backEnd", &be);
43 
44  // Load main QML file
45  view->setSource(QUrl::fromLocalFile("../main.qml"));
46 
47  return app.exec();
48  }
49  catch (const std::exception &error)
50  {
51  std::cout << error.what() << std::endl;
52  }
53 }
void setSource(const QUrl &url)
void setResizeMode(QQuickView::ResizeMode)
void show()
void setOrganizationDomain(const QString &orgDomain)
QQmlContext * rootContext() const const
static void Register()
Convenience method to register this type in QML.
Definition: image_view_item.hpp:669
void setContextProperty(const QString &name, QObject *value)
STL class.
void resize(const QSize &newSize)
void setOrganizationName(const QString &orgName)
void setApplicationName(const QString &application)
QUrl fromLocalFile(const QString &localFile)
void setIcon(const QIcon &icon)
1 import QtQuick 2.3
2 import CvbQuick 1.0 as CvbQuick
3 import QtQuick.Controls 2.2
4 import QtQuick.Layouts 1.3
5 import QtQuick.Dialogs 1.2
6 import QtQuick.Controls.Universal 2.2
7 
8 GroupBox {
9  id: gridBox
10  width: 640
11  height: 390
12  Layout.fillWidth: true
13  Layout.fillHeight: true
14 
15  property var loadFile: true
16  property var enableSnapSend: false
17 
18  Universal.theme: Universal.System
19 
20  background: Rectangle {
21  width: parent.width
22  height: parent.height
23  color: Universal.background
24  }
25 
26  FileDialog {
27  id: fileDialog
28  modality: Qt.NonModal
29  title: loadFile ? "Select an image or driver to load" : "Save image"
30  selectExisting: loadFile
31  selectFolder: false
32  nameFilters: ["Drivers (*.vin *.emu)", "Image files (*png *.bmp)", "All files (*)"]
33  selectedNameFilter: loadFile ? "Drivers" : "Image files"
34  sidebarVisible: true
35  onAccepted: {
36  var path = fileUrl.toString();
37  // remove prefixed "file:///"
38  path = path.replace(/^(file:\/{3})/,"");
39  // unescape html codes like '%23' for '#'
40  var cleanPath = decodeURIComponent(path);
41 
42  loadFile ? backEnd.LoadDevice(path) : backEnd.SaveImg(path)
43  }
44  }
45 
46  GridLayout {
47  id: gridLayout
48  rows: 4
49  flow: GridLayout.TopToBottom
50  anchors.fill: parent
51 
52  ComboBox
53  {
54  id: connectionCombo
55  enabled: btnStartServer.m_server_disabled
56  Layout.fillWidth: true
57  Layout.columnSpan: 2
58  model: backEnd.ConnectionList()
59  }
60 
61  CvbQuick.ImageView
62  {
63  id: imageView
64  image : mainImage
65  Layout.rowSpan: 3
66  Layout.columnSpan: 3
67  Layout.fillHeight: true
68  Layout.fillWidth: true
69  uploadMode : CvbQuick.UploadMode.Viewport
70  }
71 
72 
73  ComboBox
74  {
75  id: driverTypeCombo
76  enabled: btnStartServer.m_server_disabled
77  Layout.fillWidth: true
78  model:
79  (Qt.platform.os == "windows") ?
80  [
81  qsTr("Socket Driver (loopback)"),
82  qsTr("Filter Driver (default)")
83  ]
84  :
85  [
86  qsTr("Socket Driver")
87  ]
88  }
89 
90  Button
91  {
92  objectName: "btnStartServer"
93  id: btnStartServer
94  implicitWidth: btnLoadDevice.width
95  property var m_server_disabled: true
96  text: m_server_disabled ? qsTr("Start Server") : qsTr("Stop Server")
97  onClicked: {
98  m_server_disabled = m_server_disabled ? false : true
99  backEnd.StartServer(connectionCombo.currentIndex, driverTypeCombo.currentIndex)
100  }
101  }
102 
103  Column
104  {
105  spacing: 5
106 
107  Button
108  {
109  objectName: "btnLoadDevice"
110  id: btnLoadDevice
111  enabled: btnStartServer.m_server_disabled
112  text: qsTr("Load Device")
113  onClicked:
114  {
115  loadFile = true
116  fileDialog.open()
117  }
118 
119  }
120 
121  Button
122  {
123  objectName: "btnSaveImg"
124  id: btnSaveImg
125  implicitWidth: btnLoadDevice.width
126  text: qsTr("Save Image")
127  onClicked:
128  {
129  loadFile = false
130  fileDialog.open()
131  }
132  }
133 
134  Button
135  {
136  objectName: "btnSnap"
137  id: btnSnap
138  implicitWidth: btnLoadDevice.width
139  width: btnSaveImg.width
140  enabled: btnStartServer.m_server_disabled
141  text: qsTr("Snap")
142  onClicked: backEnd.BtnSnap()
143  }
144 
145  Switch
146  {
147  objectName: "switchEnableSnapSend"
148  id: switchEnableSnapSend
149  text: qsTr("Enable Snap Send")
150  checked: false
151  enabled: !btnStartServer.m_server_disabled
152  onCheckedChanged:
153  {
154  enableSnapSend = enableSnapSend ? false : true
155  backEnd.SwitchEnableSnapSend(checked)
156  }
157  }
158 
159  Button
160  {
161  objectName: "btnSnapSend"
162  id: btnSnapSend
163  implicitWidth: btnLoadDevice.width
164  enabled: !btnStartServer.m_server_disabled && enableSnapSend
165  text: qsTr("Snap Send")
166  onClicked: backEnd.BtnSnapSend()
167  }
168  }
169 
170  Switch
171  {
172  objectName: "switchGrab"
173  id: switchGrab
174  text: qsTr("Grab")
175  checked: false
176  enabled: btnStartServer.m_server_disabled
177  onCheckedChanged: backEnd.SwitchGrab(checked)
178  }
179  }
180 }
1 #include "backend.hpp"
2 
3 #include "QDebug"
4 
5 #include <cvb/data_type.hpp>
6 #include <cvb/gevserver/node.hpp>
7 #include <cvb/gevserver/node_map.hpp>
8 #include <cvb/gevserver/server.hpp>
9 #include <cvb/gevserver/stream.hpp>
10 #include <cvb/size_2d.hpp>
11 
12 #include <cvb/device_factory.hpp>
13 #include <cvb/image.hpp>
14 #include <cvb/ui/image_view_item.hpp>
15 
16 BackEnd::BackEnd(QQuickView *view, QObject *parent) : QObject(parent), view_{view}
17 {
18  // Register view to queue commands over threads
19  qRegisterMetaType<QWindow::Visibility>();
20 
21 
22  // open a device
23  device_ = Cvb::DeviceFactory::Open(Cvb::InstallPath() + CVB_LIT("drivers/CVMock.vin"));
24 
25  hasRingBuffer_ = !!device_->Stream()->RingBuffer();
26  if (hasRingBuffer_)
27  device_->Stream()->RingBuffer()->SetLockMode(Cvb::Driver::RingBufferLockMode::On);
28 
29  streamHandler_->Create(device_->Stream());
30 
31  context_ = view->rootContext();
32  context_->setContextProperty("mainImage", &imageController_);
33  device_->Stream()->GetSnapshot();
34 
35  imageController_.Refresh(device_->DeviceImage(), Cvb::UI::AutoRefresh::On);
36 }
37 
38 BackEnd::~BackEnd()
39 {
40  if (acqThread_)
41  StopAcq();
42  if (server_ != Cvb::GevServer::ServerPtr() && server_->State() != Cvb::GevServer::State::Configuration)
43  server_->Stop();
44  if (device_->Stream()->IsRunning())
45  device_->Stream()->Stop();
46 }
47 
48 void BackEnd::StartServer(const int &addressIndex, const int &driverTypeIndex)
49 {
50  qDebug() << "Starting server on address index " << addressIndex << " and driver type index " << driverTypeIndex;
51 
52  // Check for recursions
53  if (startInProgress_)
54  return;
55 
56  startInProgress_ = true;
57 
58  try
59  {
60  if (!acqStarted_)
61  {
62  // Stop and reset grab button if running
63  StopGrab();
64 
65  // Create server
66  server_ = Cvb::GevServer::Server::CreateWithConstSize(device_->DeviceImage()->Size(),
67  device_->DeviceImage()->ColorModel(),
68  device_->DeviceImage()->Plane(0).DataType(),
69  static_cast<Cvb::GevServer::DriverType>(driverTypeIndex));
70 
71  AddGenICamFeatures();
72 
73  // Set custom version/id
74  server_->SetUserVersion(CVB_LIT("_C++GevServer"));
75  server_->NodeMap()->SetXmlFileVersion(Cvb::GenApi::NodeMap::GenApiVersion(1, 0, 0));
76 
77  if (hasRingBuffer_)
78  server_->Stream()->SetResendBuffersCount(2);
79  else
80  server_->Stream()->SetResendBuffersCount(0);
81 
82  // Start server
83  auto address = iFaces_.at(addressIndex).IPAddress();
84  server_->Start(address);
85 
86  // Start acquisition thread
87  if (!acqThread_)
88  StartAcq();
89 
90  acqStarted_ = true;
91  qDebug() << "Started server";
92  }
93  else
94  {
95  if (acqThread_)
96  StopAcq();
97  server_->Stop();
98  acqStarted_ = false;
99  qDebug() << "Stopped server";
100  }
101  }
102  catch (...)
103  {
104  }
105  startInProgress_ = false;
106 }
107 
108 void BackEnd::operator()() noexcept
109 {
110  std::unique_lock<std::mutex> guard(mtx_);
111  device_->Stream()->Start();
112  while (!stopAcq_)
113  {
114  if (grabSwitchSwitched_)
115  {
116  if (hasRingBuffer_)
117  {
118  auto img = device_->Stream()->Wait<Cvb::RingBufferImage>();
119  img.Image->Unlock();
120  }
121  else
122  device_->Stream()->Wait();
123  }
124  else if (server_ != Cvb::GevServer::ServerPtr() && server_->State() == Cvb::GevServer::State::AcquisitionEnabled &&
125  !snapSend_)
126  {
127  try
128  {
129  auto img = device_->Stream()->Wait<Cvb::RingBufferImage>();
130  if (img.Status == Cvb::WaitStatus::Ok)
131  {
132  if (hasRingBuffer_)
133  {
134  bufferIndex_ = img.Image->BufferIndex();
135  imgMap_[bufferIndex_] = img.Image;
136  server_->Stream()->Send(img.Image, [this](const Cvb::ImagePtr &) {
137  imgMap_[bufferIndex_]->Unlock();
138  qDebug() << "unlocked buffer index " << std::to_string(bufferIndex_).c_str();
139  });
140  }
141  else
142  server_->Stream()->Send(*img.Image.get());
143  }
144  }
145  catch (...)
146  {
147  }
148  }
149  }
150  device_->Stream()->Stop();
151 }
152 
153 void BackEnd::SwitchGrab(const bool &switched)
154 {
155  grabSwitchSwitched_ = switched;
156 
157  if (grabSwitchSwitched_)
158  {
159  if (!acqThread_)
160  StartAcq();
161  }
162  else
163  {
164  if (acqThread_)
165  StopAcq();
166  }
167  qDebug() << "Grab switch switched to" << switched;
168 }
169 
170 QVariantList BackEnd::ConnectionList()
171 {
172  iFaces_.clear();
174 
175  QVariantList list;
176  list.clear();
177 
178  for (auto const &iFace : iFaces_)
179  {
181  ss << iFace.IPAddress() << " (" << iFace.IPv4Mask() << ")";
182  list.append(std::string(ss.str()).c_str());
183  }
184 
185  return list;
186 }
187 
188 void BackEnd::LoadDevice(const QString &path)
189 {
190  qDebug() << "Loading new device on path " << path;
191  StopGrab();
192  try
193  {
194  auto tmpDevice = Cvb::DeviceFactory::Open(CVB_LIT(path.toLatin1().toStdString().c_str()));
195  device_.reset();
196  device_ = tmpDevice;
197  device_->Stream()->GetSnapshot();
198 
199  hasRingBuffer_ = !!device_->Stream()->RingBuffer();
200  if (hasRingBuffer_)
201  device_->Stream()->RingBuffer()->SetLockMode(Cvb::Driver::RingBufferLockMode::On);
202  imageController_.Refresh(device_->DeviceImage(), Cvb::UI::AutoRefresh::On);
203  }
204  catch (...)
205  {
206  qDebug() << "Failed to load new device";
207  }
208 }
209 
210 void BackEnd::SaveImg(const QString &path)
211 {
212  qDebug() << "Saving new file on path " << path;
213  device_->DeviceImage()->Save(CVB_LIT(path.toLatin1().toStdString().c_str()));
214 }
215 
216 void BackEnd::BtnSnap()
217 {
218  qDebug() << "Snap image";
219  StopGrab();
220  // Snap a single image
221  device_->Stream()->GetSnapshot();
222 }
223 
224 void BackEnd::SwitchEnableSnapSend(const bool &switched)
225 {
226  snapSend_ = switched;
227 
228  if (snapSend_)
229  StopAcq();
230  else
231  StartAcq();
232 
233  qDebug() << "Enable snap switch switched to" << switched;
234 }
235 
236 void BackEnd::BtnSnapSend()
237 {
238  if (server_->State() == Cvb::GevServer::State::AcquisitionEnabled)
239  {
240  qDebug() << "Snap send";
241  try
242  {
243  auto img = device_->Stream()->GetSnapshot();
244  if (img.Status == Cvb::WaitStatus::Ok)
245  if (hasRingBuffer_)
246  {
247  server_->Stream()->Send(img.Image, nullptr);
248  }
249  else
250  {
251  auto img2 = device_->Stream()->GetSnapshot();
252  if (img2.Status == Cvb::WaitStatus::Ok)
253  server_->Stream()->Send(*img2.Image);
254  }
255  }
256  catch (...)
257  {
258  }
259  }
260  else
261  qDebug() << "Acquisition not enabled on server";
262 }
263 
264 void BackEnd::StopGrab()
265 {
266  // If grab is currently running, stop it
267  if (device_->Stream()->IsRunning() && grabSwitchSwitched_)
268  {
269  QObject *box = view_->findChild<QObject *>("switchGrab", Qt::FindChildrenRecursively);
270  if (box)
271  box->setProperty("checked", false);
272  }
273  if (device_->Stream()->IsRunning() && acqStarted_)
274  {
275  StopAcq();
276  }
277 }
278 
279 void BackEnd::OnWindowSizeChanged(const Cvb::GevServer::ValueNode & /*value*/)
280 {
281  switch (windowStateRegNode_->Value())
282  {
283  case 0:
284  view_->showMinimized();
285  break;
286  case 1:
287  view_->showNormal();
288  break;
289  case 2:
290  view_->showMaximized();
291  default:
292  throw std::runtime_error("Invalid window state value");
293  break;
294  }
295 }
296 
297 void BackEnd::AddGenICamFeatures()
298 {
299  auto catNode = Cvb::GevServer::CategoryNode::Create(CVB_LIT("Cust::CustomFeatures"));
300  server_->NodeMap()->AddNode(catNode);
301  catNode->SetDisplayName(CVB_LIT("Custom Features"));
302  catNode->SetToolTip(CVB_LIT("Contains all application defined features."));
303  auto rootNode = server_->NodeMap()->Node(CVB_LIT("Root"));
304  rootNode->Add(catNode, Cvb::GevServer::NodeList::Child);
305 
306  windowStateRegNode_ = Cvb::GevServer::Int32RegNode::Create(CVB_LIT("Cust::WindowStateReg"));
307  server_->NodeMap()->AddNode(windowStateRegNode_);
308  windowStateRegNode_->SetVisibility(Cvb::GenApi::Visibility::Invisible);
309  // No cache because of polling
310  windowStateRegNode_->SetCacheMode(Cvb::GenApi::CacheMode::NoCache);
311  // Polling time in ms
312  windowStateRegNode_->SetPollingTime<float, std::ratio<1, 1000>>(
314  windowStateRegNode_->SetValue(1); // View in normal mode
315  windowStateRegNode_->RegisterEventWrittenUpdated(
316  [this](const Cvb::GevServer::ValueNode &value) { OnWindowSizeChanged(value); });
317 
318  auto enumerationNode = Cvb::GevServer::EnumerationNode::Create(CVB_LIT("Cust::WindowState"));
319  server_->NodeMap()->AddNode(enumerationNode);
320  enumerationNode->SetDisplayName(CVB_LIT("Window State"));
321  enumerationNode->SetToolTip(CVB_LIT("Current window state of server application."));
322  enumerationNode->SetValueConfig<Cvb::GevServer::IntegerBaseNodePtr>(windowStateRegNode_);
323 
324  auto minimized = Cvb::GevServer::EnumEntryNode::Create(CVB_LIT("Cust::Minimized"));
325  minimized->SetNumericValue(0);
326  enumerationNode->Add(minimized, Cvb::GevServer::NodeList::Child);
327 
328  auto normalNode = Cvb::GevServer::EnumEntryNode::Create(CVB_LIT("Cust::Normal"));
329  normalNode->SetNumericValue(1); // Must be ascending entry in vector
330  enumerationNode->Add(normalNode, Cvb::GevServer::NodeList::Child);
331 
332  auto maxNode = Cvb::GevServer::EnumEntryNode::Create(CVB_LIT("Cust::Maximized"));
333  maxNode->SetNumericValue(2); // Must be ascending entry in vector
334  enumerationNode->Add(maxNode, Cvb::GevServer::NodeList::Child);
335 
336  catNode->Add(enumerationNode, Cvb::GevServer::NodeList::Child);
337 }
static ServerPtr CreateWithConstSize(Size2D< int > size, PfncFormat pixelFormat, GevServer::DriverType driverType=GevServer::DriverType::Auto)
Creates a new Server object with a constant width and height.
Definition: detail_server.hpp:37
Base class for all nodes that have a value.
Definition: value_node.hpp:27
Everything is fine, a new image arrived.
static EnumEntryNodePtr Create(const String &name, const GevServer::Namespace &nameSpace)
Creates a new EnumEntryNode with the given name and nameSpace .
Definition: enum_entry_node.hpp:40
std::string toStdString() const const
static Int32RegNodePtr Create(const String &name, const GevServer::Namespace &nameSpace, const std::int64_t &address)
Creates a new int_32_reg_node with the given name and nameSpace .
Definition: int_32_reg_node.hpp:34
STL class.
static EnumerationNodePtr Create(const String &name, const GevServer::Namespace &nameSpace)
Creates a new EnumerationNode with the given name and nameSpace .
Definition: enumeration_node.hpp:41
Stream image that is returned, when the ring buffer interface is available on a device.
Definition: decl_ring_buffer_image.hpp:27
static CategoryNodePtr Create(const String &name, const GevServer::Namespace &nameSpace)
Creates a new CategoryNode with the given name and nameSpace .
Definition: category_node.hpp:38
QByteArray toLatin1() const const
Node should not be displayed.
static std::shared_ptr< T > Open(const String &provider, AcquisitionStack acquisitionStack=AcquisitionStack::PreferVin)
Opens a device with the given provider with its default board and port (if applicable).
Definition: decl_device_factory.hpp:50
Version information for GenICam related objects.
Definition: decl_node_map.hpp:58
bool setProperty(const char *name, const QVariant &value)
static std::vector< LogicalNetworkInterface > GetAllAvailable()
Gets all available network interfaces usable by the GevServer.
Definition: logical_network_interface.hpp:38
T findChild(const QString &name, Qt::FindChildOptions options) const const
1 #ifndef BACKEND_H
2 #define BACKEND_H
3 
4 #include "QVariantList"
5 #include <QObject>
6 #include <QQmlContext>
7 #include <QQuickView>
8 #include <QString>
9 
10 #include <thread>
11 
12 #include <cvb/async/single_stream_handler.hpp>
13 #include <cvb/gevserver/logical_network_interface.hpp>
14 #include <cvb/ui/image_view_item.hpp>
15 
16 class BackEnd : public QObject
17 {
18  // This macro is required for QObject support. You should get a compiler
19  // error if you don't include this.
20  Q_OBJECT
21 
22 public:
23  // QObjects are expected to support a parent/child hierarchy
24  explicit BackEnd(QQuickView *view, QObject *parent = 0);
25 
26  ~BackEnd();
27 
28  void OnWindowSizeChanged(const Cvb::GevServer::ValueNode &value);
29 
30  void StartAcq()
31  {
32  std::unique_lock<std::mutex> guard(mtx_);
33 
34  stopAcq_ = false;
35  acqThread_.reset(new std::thread(std::ref(*this)));
36  }
37 
38  void StopAcq()
39  {
40  stopAcq_ = true;
41  if (acqThread_)
42  {
43  acqThread_->join();
44  acqThread_.reset();
45  }
46  }
47 
48  // Acquisition thread
49  void operator()() noexcept;
50 
51 signals:
52 
53 public slots:
54  void StartServer(const int &addressIndex, const int &driverTypeIndex);
55 
56  void LoadDevice(const QString &path);
57 
58  void SaveImg(const QString &path);
59 
60  void BtnSnap();
61 
62  void BtnSnapSend();
63 
64  QVariantList ConnectionList();
65 
66  void SwitchGrab(const bool &switched);
67 
68  void SwitchEnableSnapSend(const bool &switched);
69 
70 private:
71  void StopGrab();
72 
73  void AddGenICamFeatures();
74 
75  QQuickView *view_;
76  QQmlContext *context_;
77  // Create a controller object to communicate with QML
78  Cvb::UI::ImageController imageController_;
79 
80  Cvb::EventCookie eventCookie_;
81  // Hold this node for callback
82  Cvb::GevServer::Int32RegNodePtr windowStateRegNode_;
83 
84  bool startInProgress_{false};
85  bool grabSwitchSwitched_{false};
86  bool acqStarted_{false};
87  bool snapSend_{false};
88  bool hasRingBuffer_{false};
89  int bufferIndex_;
90 
94  Cvb::DevicePtr device_;
95  Cvb::SingleStreamHandlerPtr streamHandler_;
96 
97  std::atomic<bool> stopAcq_;
99  std::mutex mtx_;
100 };
101 
102 #endif
STL class.
Base class for all nodes that have a value.
Definition: value_node.hpp:27
STL class.
Controller object for the QML image view item.
Definition: image_view_item.hpp:252
STL class.
STL class.