CustusX  2023.01.05-dev+develop.0da12
An IGT application
cxVideoConnection.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 #include "cxVideoConnection.h"
12 
13 #include <vtkDataSetMapper.h>
14 #include <vtkImageFlip.h>
15 
16 #include "cxTrackingService.h"
17 #include "cxBasicVideoSource.h"
18 #include "cxVideoServiceBackend.h"
19 #include "cxNullDeleter.h"
20 #include "cxImageReceiverThread.h"
21 #include "cxImage.h"
22 #include "cxLogger.h"
23 #include <QApplication>
24 #include "boost/function.hpp"
25 #include "boost/bind.hpp"
26 
27 typedef vtkSmartPointer<vtkDataSetMapper> vtkDataSetMapperPtr;
28 typedef vtkSmartPointer<vtkImageFlip> vtkImageFlipPtr;
29 
30 namespace cx
31 {
32 
34 {
35  mBackend = backend;
36  mUnusedProbeDefinitionVector.clear();
37 
38  connect(mBackend->tracking().get(), &TrackingService::stateChanged, this, &VideoConnection::connectVideoToProbe);
39  connect(mBackend->tracking().get(), SIGNAL(activeToolChanged(QString)), this, SLOT(connectVideoToProbe()));
40 }
41 
43 {
44  this->stopClient();
45  this->waitForClientFinished();
46 }
47 
48 void VideoConnection::waitForClientFinished()
49 {
50  if (mThread)
51  {
52  mThread->wait(2000);
53  // NOTE: OpenCV requires a running event loop in the main thread to quit normally.
54  // During system shutdown, this is not the case and we get this warning.
55  // Attempts to solve using qApp->processEvents() has failed due to (lots of) side effects.
56  // Seems to happen with other streamers as well.
57 // CX_LOG_WARNING() << "Video thread finished: " << mThread->isFinished();
58 // CX_LOG_WARNING() << "Video thread running: " << mThread->isRunning();
59 // if (mThread->isRunning())
60 // CX_LOG_WARNING() << "Video thread did not quit normally - ignoring.";
61  }
62 }
63 
64 void VideoConnection::fpsSlot(QString source, double fpsNumber)
65 {
66  mFPS = fpsNumber;
67  emit fps(source, fpsNumber);
68 }
69 
71 {
72  return mThread;
73 }
74 
75 StreamerServicePtr VideoConnection::getStreamerInterface()
76 {
77  return mStreamerInterface;
78 }
79 
80 
81 namespace
82 {
83 class EventProcessingThread : public QThread
84 {
85  virtual void run()
86  {
87  this->exec();
88  qApp->processEvents(); // exec() docs doesn't guarantee that the posted events are processed. - do that here.
89  }
90 };
91 }
92 
94 {
95  if (mClient)
96  {
97  // in this case we already have a working system: ignore
98  CX_LOG_INFO() << "Video client already exists - cannot start";
99  return;
100  }
101  if (mThread)
102  {
103  // in this case we have a thread but no client: probably shutting down: ignore
104  CX_LOG_INFO() << "Video thread already exists (in shutdown?) - cannot start";
105  return;
106  }
107 
108  mStreamerInterface = service;
109  mClient = new ImageReceiverThread(mStreamerInterface);
110 
111  connect(mClient.data(), &ImageReceiverThread::imageReceived, this, &VideoConnection::imageReceivedSlot); // thread-bridging connection
112  connect(mClient.data(), &ImageReceiverThread::sonixStatusReceived, this, &VideoConnection::statusReceivedSlot); // thread-bridging connection
113  connect(mClient.data(), &ImageReceiverThread::fps, this, &VideoConnection::fpsSlot); // thread-bridging connection
114 
115  mThread = new EventProcessingThread;
116  mThread->setObjectName("org.custusx.core.video.imagereceiver");
117  mClient->moveToThread(mThread);
118 
119  connect(mThread.data(), &QThread::started, this, &VideoConnection::onConnected);
120  connect(mThread.data(), &QThread::started, mClient.data(), &ImageReceiverThread::initialize);
121 
122  connect(mClient.data(), &ImageReceiverThread::finished, mThread.data(), &QThread::quit);
123  connect(mClient.data(), &ImageReceiverThread::finished, mClient.data(), &ImageReceiverThread::deleteLater);
124  connect(mThread.data(), &QThread::finished, this, &VideoConnection::onDisconnected);
125  connect(mThread.data(), &QThread::finished, mThread.data(), &QThread::deleteLater);
126 
127  mThread->start();
128 }
129 
130 void VideoConnection::imageReceivedSlot()
131 {
132  if (!mClient)
133  return;
134  this->updateImage(mClient->getLastImageMessage());
135 }
136 
137 void VideoConnection::statusReceivedSlot()
138 {
139  if (!mClient)
140  return;
141  this->updateStatus(mClient->getLastSonixStatusMessage());
142 }
143 
144 void VideoConnection::stopClient()
145 {
146  if (!mThread)
147  return;
148 
149  if (mClient)
150  {
151  disconnect(mClient.data(), &ImageReceiverThread::imageReceived, this, &VideoConnection::imageReceivedSlot); // thread-bridging connection
152  disconnect(mClient.data(), &ImageReceiverThread::sonixStatusReceived, this, &VideoConnection::statusReceivedSlot); // thread-bridging connection
153  disconnect(mClient.data(), &ImageReceiverThread::fps, this, &VideoConnection::fpsSlot); // thread-bridging connection
154 
155  QMetaObject::invokeMethod(mClient, "shutdown", Qt::QueuedConnection);
156 
157  mClient = NULL;
158  }
159 }
160 
162 {
163  this->stopClient();
164 }
165 
166 void VideoConnection::onConnected()
167 {
168  this->startAllSources();
169  emit connected(true);
170 }
171 
172 void VideoConnection::onDisconnected()
173 {
174  mClient = NULL;
175  mThread = NULL; // because this method listens to thread::finished
176 
177  this->resetProbe();
178 
179  this->stopAllSources();
180 
181  for (unsigned i=0; i<mSources.size(); ++i)
182  mSources[i]->setInput(ImagePtr());
183 
184  ToolPtr tool = mBackend->tracking()->getFirstProbe();
185  if (tool && tool->getProbe())
186  this->removeSourceFromProbe(tool);
187 
188  mSources.clear();
189  mStreamerInterface.reset();
190 
191  emit connected(false);
192  emit videoSourcesChanged();
193 }
194 
195 void VideoConnection::useUnusedProbeDefinitionSlot()
196 {
197  disconnect(mBackend->tracking().get(), &TrackingService::stateChanged, this, &VideoConnection::useUnusedProbeDefinitionSlot);
198 
199  std::vector<ProbeDefinitionPtr> unusedProbeDefinitionVector = mUnusedProbeDefinitionVector;
200  mUnusedProbeDefinitionVector.clear();
201 
202  for (unsigned i = 0; i < unusedProbeDefinitionVector.size(); ++i)
203  this->updateStatus(unusedProbeDefinitionVector[i]);
204 }
205 
206 void VideoConnection::resetProbe()
207 {
208  ToolPtr tool = mBackend->tracking()->getFirstProbe();
209  if (!tool || !tool->getProbe())
210  return;
211  ProbePtr probe = tool->getProbe();
212  if (probe)
213  {
214  ProbeDefinition data = probe->getProbeDefinition();
215  data.setUseDigitalVideo(false);
216  probe->setProbeDefinition(data);
217  }
218 }
219 
224 void VideoConnection::updateStatus(ProbeDefinitionPtr msg)
225 {
226  ToolPtr tool = mBackend->tracking()->getFirstProbe();
227  if (!tool || !tool->getProbe())
228  {
229  //Don't throw away the ProbeDefinition. Save it until it can be used
230  if (mUnusedProbeDefinitionVector.empty())
231  connect(mBackend->tracking().get(), &TrackingService::stateChanged, this, &VideoConnection::useUnusedProbeDefinitionSlot);
232  mUnusedProbeDefinitionVector.push_back(msg);
233  return;
234  }
235  ProbePtr probe = tool->getProbe();
236 
237  // start with getting a valid data object from the probe, in order to keep
238  // existing values (such as temporal calibration).
239  // Note that the 'active' data is get while the 'uid' data is set.
240  ProbeDefinition data = probe->getProbeDefinition();
241 
242  data.setUid(msg->getUid());
243  data.setType(msg->getType());
244  data.setSector(msg->getDepthStart(), msg->getDepthEnd(), msg->getWidth());
245  data.setOrigin_p(msg->getOrigin_p());
246  data.setSize(msg->getSize());
247  data.setSpacing(msg->getSpacing());
248  data.setClipRect_p(msg->getClipRect_p());
249  data.setUseDigitalVideo(true);
250 
251  probe->setProbeDefinition(data);
252  probe->setActiveStream(msg->getUid());
253 }
254 
255 void VideoConnection::startAllSources()
256 {
257  for (unsigned i=0; i<mSources.size(); ++i)
258  mSources[i]->start();
259 }
260 
261 void VideoConnection::stopAllSources()
262 {
263  for (unsigned i=0; i<mSources.size(); ++i)
264  mSources[i]->stop();
265 }
266 
267 void VideoConnection::removeSourceFromProbe(ToolPtr tool)
268 {
269  ProbePtr probe = tool->getProbe();
270  for (unsigned i=0; i<mSources.size(); ++i)
271  probe->removeRTSource(mSources[i]);
272 }
273 
274 void VideoConnection::updateImage(ImagePtr message)
275 {
276  BasicVideoSourcePtr source;
277 
278  // look for existing VideoSource
279  for (unsigned i=0; i<mSources.size(); ++i)
280  {
281  if (message && message->getUid() == mSources[i]->getUid())
282  source = mSources[i];
283  }
284 
285  bool newSource = false;
286  // no existing found: create new
287  if (!source)
288  {
289  source.reset(new BasicVideoSource());
290  mSources.push_back(source);
291  source->start();
292  newSource = true;
293  }
294  // set input.
295  source->setInput(message);
296 
297  QString info = mClient->hostDescription() + " - " + QString::number(mFPS, 'f', 1) + " fps";
298  source->setInfoString(info);
299 
300  if (newSource)
301  {
302  this->connectVideoToProbe();
303  emit videoSourcesChanged();
304  }
305 }
306 
307 std::vector<VideoSourcePtr> VideoConnection::getVideoSources()
308 {
309  std::vector<VideoSourcePtr> retval;
310  std::copy(mSources.begin(), mSources.end(), std::back_inserter(retval));
311  return retval;
312 }
313 
321 void VideoConnection::connectVideoToProbe()
322 {
323  ToolPtr tool = mBackend->tracking()->getFirstProbe();
324  if (!tool)
325  return;
326 
327  ProbePtr probe = tool->getProbe();
328  if (!probe)
329  return;
330 
331  for (unsigned i=0; i<mSources.size(); ++i)
332  probe->setRTSource(mSources[i]);
333 }
334 
335 }
bool connected(bool)
void setSpacing(Vector3D spacing)
void fps(QString, double)
#define CX_LOG_INFO
Definition: cxLogger.h:96
boost::shared_ptr< class Image > ImagePtr
Definition: cxDicomWidget.h:27
VideoConnection(VideoServiceBackendPtr backend)
std::vector< VideoSourcePtr > getVideoSources()
void setSector(double depthStart, double depthEnd, double width, double centerOffset=0)
boost::shared_ptr< class VideoServiceBackend > VideoServiceBackendPtr
boost::shared_ptr< Probe > ProbePtr
Definition: cxProbe.h:72
void setOrigin_p(Vector3D origin_p)
void setUid(QString uid)
void setUseDigitalVideo(bool val)
RTSource is digital (eg. US sector is set digitally, not read from .xml file)
void setClipRect_p(DoubleBoundingBox3D clipRect_p)
Base class for receiving images from a video stream.
void runDirectLinkClient(StreamerServicePtr service)
virtual bool isConnected() const
VideoSource controlled by a vtkImageData.
vtkSmartPointer< vtkDataSetMapper > vtkDataSetMapperPtr
Definition of characteristics for an Ultrasound Probe Sector.
vtkSmartPointer< vtkImageFlip > vtkImageFlipPtr
boost::shared_ptr< class StreamerService > StreamerServicePtr
boost::shared_ptr< class BasicVideoSource > BasicVideoSourcePtr
void setSize(QSize size)
boost::shared_ptr< class ProbeDefinition > ProbeDefinitionPtr
void fps(QString source, int fps)
Namespace for all CustusX production code.
boost::shared_ptr< class Tool > ToolPtr