CustusX  15.8
An IGT application
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
cxImageStreamerOpenCV.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) 2008-2014, SINTEF Department of Medical Technology
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice,
11  this list of conditions and the following disclaimer.
12 
13 2. Redistributions in binary form must reproduce the above copyright notice,
14  this list of conditions and the following disclaimer in the documentation
15  and/or other materials provided with the distribution.
16 
17 3. Neither the name of the copyright holder nor the names of its contributors
18  may be used to endorse or promote products derived from this software
19  without specific prior written permission.
20 
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 =========================================================================*/
32 #include "cxImageStreamerOpenCV.h"
33 #include "cxConfig.h"
34 
35 #include <QCoreApplication>
36 #include <QTimer>
37 #include <QTime>
38 #include <QHostAddress>
39 #include "igtlOSUtil.h"
40 #include "igtlImageMessage.h"
41 #include "igtlServerSocket.h"
42 #include "vtkImageData.h"
43 #include "vtkSmartPointer.h"
44 #include "vtkMetaImageReader.h"
45 #include "vtkImageImport.h"
46 #include "vtkLookupTable.h"
47 #include "vtkImageMapToColors.h"
48 #include "vtkMetaImageWriter.h"
49 
50 #include "cxTypeConversions.h"
52 #include "cxStringHelpers.h"
53 #include "cxDoubleProperty.h"
54 #include "cxBoolProperty.h"
55 #include "cxLogger.h"
56 #include "cxVideoServerConfig.h"
57 
58 #ifdef CX_USE_OpenCV
59 #include <opencv2/highgui/highgui.hpp>
60 #include <opencv2/imgproc/imgproc.hpp>
61 #endif
62 
63 namespace cx
64 {
65 
66 std::vector<PropertyPtr> ImageStreamerOpenCVArguments::getSettings(QDomElement root)
67 {
68  std::vector<PropertyPtr> retval;
69  retval.push_back(this->getVideoPortOption(root));
70  retval.push_back(this->getPrintPropertiesOption(root));
71  return retval;
72 }
73 
75 {
76  DoublePropertyPtr retval;
77  retval = DoubleProperty::initialize("videoport", "Video Port", "Select video source as an integer from 0 and up.", 0, DoubleRange(0, 10, 1), 0, root);
78  retval->setGuiRepresentation(DoublePropertyBase::grSPINBOX);
79  retval->setGroup("OpenCV");
80  return retval;
81 }
82 
84 {
85  BoolPropertyPtr retval;
86  bool defaultValue = false;
87  retval = BoolProperty::initialize("properties", "Print Properties",
88  "When starting OpenCV, print properties to console",
89  defaultValue, root);
90  retval->setAdvanced(true);
91  retval->setGroup("OpenCV");
92  return retval;
93 }
94 
96 {
97  StringMap retval;
98  retval["--type"] = "OpenCV";
99  retval["--videoport"] = this->getVideoPortOption(root)->getValueAsVariant().toString();
100  if (this->getPrintPropertiesOption(root)->getValue())
101  retval["--properties"] = "1";
102  return retval;
103 }
104 
106 {
107  QStringList retval;
108 #ifdef CX_USE_OpenCV
109  retval << "--videoport: video id, default=0";
110  retval << "--in_width: width of incoming image, default=camera";
111  retval << "--in_height: height of incoming image, default=camera";
112  retval << "--out_width: width of outgoing image, default=camera";
113  retval << "--out_height: width of outgoing image, default=camera";
114  retval << "--properties: dump image properties";
115 #endif
116  return retval;
117 }
118 
119 } // namespace cx
120 
121 //------------------------------------------------------------
122 //------------------------------------------------------------
123 //------------------------------------------------------------
124 
125 namespace
126 {
127 
128 //------------------------------------------------------------
129 // Function to generate random matrix.
130 void GetRandomTestMatrix(igtl::Matrix4x4& matrix)
131 {
132  //float position[3];
133  //float orientation[4];
134 
135  matrix[0][0] = 1.0;
136  matrix[1][0] = 0.0;
137  matrix[2][0] = 0.0;
138  matrix[3][0] = 0.0;
139  matrix[0][1] = 0.0;
140  matrix[1][1] = -1.0;
141  matrix[2][1] = 0.0;
142  matrix[3][1] = 0.0;
143  matrix[0][2] = 0.0;
144  matrix[1][2] = 0.0;
145  matrix[2][2] = 1.0;
146  matrix[3][2] = 0.0;
147  matrix[0][3] = 0.0;
148  matrix[1][3] = 0.0;
149  matrix[2][3] = 0.0;
150  matrix[3][3] = 1.0;
151 }
152 
153 }
154 
155 namespace cx
156 {
158 {
159  mGrabbing = false;
160  mAvailableImage = false;
161  setSendInterval(40);
162 
163 #ifdef CX_USE_OpenCV
164  mVideoCapture.reset(new cv::VideoCapture());
165 #endif
166  mSendTimer = new QTimer(this);
167  connect(mSendTimer, SIGNAL(timeout()), this, SLOT(send())); // this signal will be executed in the thread of THIS, i.e. the main thread.
168 }
169 
171 {
172  this->deinitialize_local();
173 }
174 
176 {
177  return "OpenCV";
178 }
179 
181 {
183 }
184 
185 
187 {
189 }
190 
191 void ImageStreamerOpenCV::deinitialize_local()
192 {
193 #ifdef CX_USE_OpenCV
194  while (mGrabbing) // grab() method seems to call processmessages itself...
195  qApp->processEvents();
196  mVideoCapture->release();
197  mVideoCapture.reset(new cv::VideoCapture());
198 #endif
199 }
200 
201 void ImageStreamerOpenCV::initialize_local()
202 {
203 #ifdef CX_USE_OpenCV
204 
205  if (!mArguments.count("videoport"))
206  mArguments["videoport"] = "0";
207  if (!mArguments.count("out_width"))
208  mArguments["out_width"] = "";
209  if (!mArguments.count("out_height"))
210  mArguments["out_height"] = "";
211  if (!mArguments.count("in_width"))
212  mArguments["in_width"] = "";
213  if (!mArguments.count("in_height"))
214  mArguments["in_height"] = "";
215 
216  QString videoSource = mArguments["videoport"];
217  int videoport = convertStringWithDefault(mArguments["videoport"], 0);
218 
219  bool sourceIsInt = false;
220  videoSource.toInt(&sourceIsInt);
221 
222  if (sourceIsInt){
223  // open device (camera)
224  mVideoCapture->open(videoport);
225  }
226  else{
227  // open file
228  mVideoCapture->open(videoSource.toStdString().c_str());
229  }
230 
231  if (!mVideoCapture->isOpened())
232  {
233  cerr << "ImageStreamerOpenCV: Failed to open a video device or video file!\n" << endl;
234  return;
235  }
236 
237  // try one grab before accepting the streamer
238  // - this fails if no camera is attached
239  try
240  {
241  mVideoCapture->grab();
242  }
243  catch(cv::Exception e)
244  {
245  CX_LOG_ERROR() << "OpenCV failed with message: " << e.what();
246  mVideoCapture->release();
247  return;
248  }
249 
250 
251  {
252  //determine default values
253  int default_width = mVideoCapture->get(CV_CAP_PROP_FRAME_WIDTH);
254  int default_height = mVideoCapture->get(CV_CAP_PROP_FRAME_HEIGHT);
255 
256  //set input size
257  int in_width = convertStringWithDefault(mArguments["in_width"], default_width);
258  int in_height = convertStringWithDefault(mArguments["in_height"], default_height);
259  mVideoCapture->set(CV_CAP_PROP_FRAME_WIDTH, in_width);
260  mVideoCapture->set(CV_CAP_PROP_FRAME_HEIGHT, in_height);
261 
262  //set output size (resize)
263  int out_width = convertStringWithDefault(mArguments["out_width"], in_width);
264  int out_height = convertStringWithDefault(mArguments["out_height"], in_height);
265  mRescaleSize.setWidth(out_width);
266  mRescaleSize.setHeight(out_height);
267 
268  if (mArguments.count("properties"))
269  this->dumpProperties();
270 
271  std::cout << "ImageStreamerOpenCV: Started streaming from openCV device "
272  << videoSource.toStdString()
273  << ", size=(" << in_width << "," << in_height << ")";
274  if (( in_width!=mRescaleSize.width() )|| (in_height!=mRescaleSize.height()))
275  std::cout << ". Scaled to (" << mRescaleSize.width() << "," << mRescaleSize.height() << ")";
276 
277  std::cout << std::endl;
278  }
279 #endif
280 }
281 
283 {
284 #ifdef CX_USE_OpenCV
285  this->initialize_local();
286 
287  if (!mSendTimer || !mVideoCapture->isOpened())
288  {
289  reportError("ImageStreamerOpenCV: Failed to start streaming: Not initialized.");
290  return;
291  }
292 
293  mSender = sender;
294  mSendTimer->start(getSendInterval());
295  this->continousGrabEvent(); // instead of grabtimer
296 
297 #else
298  reportWarning("ImageStreamerOpenCV: Failed to start streaming: CX_USE_OpenCV not defined.");
299 #endif //CX_USE_OpenCV
300 }
301 
303 {
304  if (!mSendTimer)
305  return;
306  mSendTimer->stop();
307  mSender.reset();
308 
309  this->deinitialize_local();
310 }
311 
313 {
314  return (mSendTimer && mVideoCapture->isOpened());
315 }
316 
317 void ImageStreamerOpenCV::dumpProperties()
318 {
319 #ifdef CX_USE_OpenCV
320  this->dumpProperty(CV_CAP_PROP_POS_MSEC, "CV_CAP_PROP_POS_MSEC");
321  this->dumpProperty(CV_CAP_PROP_POS_FRAMES, "CV_CAP_PROP_POS_FRAMES");
322  this->dumpProperty(CV_CAP_PROP_POS_AVI_RATIO, "CV_CAP_PROP_POS_AVI_RATIO");
323  this->dumpProperty(CV_CAP_PROP_FRAME_WIDTH, "CV_CAP_PROP_FRAME_WIDTH");
324  this->dumpProperty(CV_CAP_PROP_FRAME_HEIGHT, "CV_CAP_PROP_FRAME_HEIGHT");
325  this->dumpProperty(CV_CAP_PROP_FPS, "CV_CAP_PROP_FPS");
326  this->dumpProperty(CV_CAP_PROP_FOURCC, "CV_CAP_PROP_FOURCC");
327  this->dumpProperty(CV_CAP_PROP_FRAME_COUNT, "CV_CAP_PROP_FRAME_COUNT");
328  this->dumpProperty(CV_CAP_PROP_FORMAT, "CV_CAP_PROP_FORMAT");
329  this->dumpProperty(CV_CAP_PROP_MODE, "CV_CAP_PROP_MODE");
330  this->dumpProperty(CV_CAP_PROP_BRIGHTNESS, "CV_CAP_PROP_BRIGHTNESS");
331  this->dumpProperty(CV_CAP_PROP_CONTRAST, "CV_CAP_PROP_CONTRAST");
332  this->dumpProperty(CV_CAP_PROP_SATURATION, "CV_CAP_PROP_SATURATION");
333  this->dumpProperty(CV_CAP_PROP_HUE, "CV_CAP_PROP_HUE");
334  this->dumpProperty(CV_CAP_PROP_GAIN, "CV_CAP_PROP_GAIN");
335  this->dumpProperty(CV_CAP_PROP_EXPOSURE, "CV_CAP_PROP_EXPOSURE");
336  this->dumpProperty(CV_CAP_PROP_CONVERT_RGB, "CV_CAP_PROP_CONVERT_RGB");
337  // this->dumpProperty(CV_CAP_PROP_WHITE_BALANCE, "CV_CAP_PROP_WHITE_BALANCE");
338  this->dumpProperty(CV_CAP_PROP_RECTIFICATION, "CV_CAP_PROP_RECTIFICATION");
339 #endif
340 }
341 
342 void ImageStreamerOpenCV::dumpProperty(int val, QString name)
343 {
344 #ifdef CX_USE_OpenCV
345  double value = mVideoCapture->get(val);
346  if (value != -1)
347  std::cout << "Property " << name.toStdString() << " : " << mVideoCapture->get(val) << std::endl;
348 #endif
349 }
350 
356 void ImageStreamerOpenCV::continousGrabEvent()
357 {
358  if (!mSendTimer->isActive())
359  return;
360  this->grab();
361  QMetaObject::invokeMethod(this, "continousGrabEvent", Qt::QueuedConnection);
362 }
363 
364 void ImageStreamerOpenCV::grab()
365 {
366 #ifdef CX_USE_OpenCV
367  if (!mVideoCapture->isOpened())
368  {
369  return;
370  }
371  mGrabbing = true;
372  // grab images from camera to opencv internal buffer, do not process
373  mVideoCapture->grab();
374  mLastGrabTime = QDateTime::currentDateTime();
375  mAvailableImage = true;
376  mGrabbing = false;
377 #endif
378 }
379 
380 void ImageStreamerOpenCV::send()
381 {
382  if (!mSender || !mSender->isReady())
383  return;
384  if (!mAvailableImage)
385  {
386 // reportDebug("dropped resend of frame");
387  return;
388  }
389  PackagePtr package(new Package());
390  package->mIgtLinkImageMessage = this->getImageMessage();
391  mSender->send(package);
392  mAvailableImage = false;
393 
394 // static int counter=0;
395 // if (++counter%50==0)
396 // std::cout << "=== ImageStreamerOpenCV send: " << start.msecsTo(QTime::currentTime()) << " ms" << std::endl;
397 }
398 
399 IGTLinkImageMessage::Pointer ImageStreamerOpenCV::getImageMessage()
400 {
401  IGTLinkImageMessage::Pointer imgMsg = IGTLinkImageMessage::New();
402 
403 #ifdef CX_USE_OpenCV
404  if (!mVideoCapture->isOpened())
406 
407  QTime start = QTime::currentTime();
408 
409  cv::Mat frame_source;
410  // mVideoCapture >> frame_source;
411  if (!mVideoCapture->retrieve(frame_source, 0))
413 
414  if (this->thread() == QCoreApplication::instance()->thread() && !mSender)
415  {
416  cv::imshow("ImageStreamerOpenCV", frame_source);
417  }
418 
419  // std::cout << "grab" << start.msecsTo(QTime::currentTime()) << " ms" << std::endl;
420  // return igtl::ImageMessage::Pointer();
421 
422  igtl::TimeStamp::Pointer timestamp;
423  timestamp = igtl::TimeStamp::New();
424  // double now = 1.0/1000*(double)QDateTime::currentDateTime().toMSecsSinceEpoch();
425  double grabTime = 1.0 / 1000 * (double) mLastGrabTime.toMSecsSinceEpoch();
426  timestamp->SetTime(grabTime);
427  static QDateTime lastlastGrabTime = mLastGrabTime;
428 // std::cout << "OpenCV stamp:\t" << mLastGrabTime.toString("hh:mm:ss.zzz").toStdString() << std::endl;
429 // std::cout << "OpenCV diff:\t" <<lastlastGrabTime.msecsTo(mLastGrabTime) << "\tdelay:\t" << mLastGrabTime.msecsTo(QDateTime::currentDateTime()) << std::endl;
430  lastlastGrabTime = mLastGrabTime;
431 
432  cv::Mat frame = frame_source;
433  if (( frame.cols!=mRescaleSize.width() )|| (frame.rows!=mRescaleSize.height()))
434  {
435  cv::resize(frame_source, frame, cv::Size(mRescaleSize.width(), mRescaleSize.height()), 0, 0, CV_INTER_LINEAR);
436  }
437 
438  // std::cout << "grab " << start.msecsTo(QTime::currentTime()) << " ms" << std::endl;
439 // std::cout << "WH=("<< frame.cols << "," << frame.rows << ")" << ", Channels,Depth=("<< frame.channels() << "," << frame.depth() << ")" << std::endl;
440 
441 
442  int size[] =
443  { 1.0, 1.0, 1.0 }; // spacing (mm/pixel)
444  size[0] = frame.cols;
445  size[1] = frame.rows;
446 
447  float spacingF[] =
448  { 1.0, 1.0, 1.0 }; // spacing (mm/pixel)
449  int* svsize = size;
450  int svoffset[] =
451  { 0, 0, 0 }; // sub-volume offset
452  int scalarType = -1;
453 
454  if (frame.channels() == 3 || frame.channels() == 4)
455  {
456  scalarType = IGTLinkImageMessage::TYPE_UINT32;// scalar type
457  }
458  else if (frame.channels() == 1)
459  {
460  if (frame.depth() == 16)
461  {
462  scalarType = IGTLinkImageMessage::TYPE_UINT16;// scalar type
463  }
464  else if (frame.depth() == 8)
465  {
466  scalarType = IGTLinkImageMessage::TYPE_UINT8;// scalar type
467  }
468  }
469 
470  if (scalarType == -1)
471  {
472  std::cerr << "unknown image type" << std::endl;
474  }
475  //------------------------------------------------------------
476  // Create a new IMAGE type message
477 // IGTLinkImageMessage::Pointer imgMsg = IGTLinkImageMessage::New();
478  imgMsg->SetDimensions(size);
479  imgMsg->SetSpacing(spacingF);
480  imgMsg->SetScalarType(scalarType);
481  imgMsg->SetSubVolume(svsize, svoffset);
482  imgMsg->AllocateScalars();
483  imgMsg->SetTimeStamp(timestamp);
484 
485  unsigned char* destPtr = reinterpret_cast<unsigned char*> (imgMsg->GetScalarPointer());
486  uchar* src = frame.data;
487  // std::cout << "pre copy " << start.msecsTo(QTime::currentTime()) << " ms" << std::endl;
488  int N = size[0] * size[1];
489  QString colorFormat;
490 
491  if (frame.channels() >= 3)
492  {
493  if (frame.isContinuous())
494  {
495  // 3-channel continous colors
496  for (int i = 0; i < N; ++i)
497  {
498  *destPtr++ = 255;
499  *destPtr++ = src[2];
500  *destPtr++ = src[1];
501  *destPtr++ = src[0];
502  src += 3;
503  }
504  }
505  else
506  {
507 // std::cout << "noncontinous conversion, rows=" << size[1] << std::endl;
508  for (int i=0; i<size[1]; ++i)
509  {
510  const uchar* src = frame.ptr<uchar>(i);
511  for (int j=0; j<size[0]; ++j)
512  {
513  *destPtr++ = 255;
514  *destPtr++ = src[2];
515  *destPtr++ = src[1];
516  *destPtr++ = src[0];
517  src += 3;
518  }
519  }
520  }
521  colorFormat = "ARGB";
522  }
523  if (frame.channels() == 1)
524  {
525  if (!frame.isContinuous())
526  {
527  std::cout << "Error: Non-continous frame data." << std::endl;
529  }
530 
531  // BW camera
532  for (int i = 0; i < N; ++i)
533  {
534  *destPtr++ = *src++;
535  }
536  colorFormat = "R";
537  }
538 
539  imgMsg->SetDeviceName(cstring_cast(QString("cxOpenCV [%1]").arg(colorFormat)));
540 
541  //------------------------------------------------------------
542  // Get randome orientation matrix and set it.
543  igtl::Matrix4x4 matrix;
544  GetRandomTestMatrix(matrix);
545  imgMsg->SetMatrix(matrix);
546 
547 // std::cout << " grab+process " << start.msecsTo(QTime::currentTime()) << " ms" << std::endl;
548 #endif
549  return imgMsg;
550 }
551 
552 //------------------------------------------------------------
553 //------------------------------------------------------------
554 //------------------------------------------------------------
555 
556 }
static BoolPropertyPtr initialize(const QString &uid, QString name, QString help, bool value, QDomNode root=QDomNode())
void reportError(QString msg)
Definition: cxLogger.cpp:92
DoublePropertyBasePtr getVideoPortOption(QDomElement root)
void setSendInterval(int milliseconds)
how often an image should be sent (in milliseconds)
Definition: cxStreamer.cpp:43
virtual void initialize(StringMap arguments)
Utility class for describing a bounded numeric range.
Definition: cxDoubleRange.h:53
virtual QStringList getArgumentDescription()
cstring_cast_Placeholder cstring_cast(const T &val)
BoolPropertyBasePtr getPrintPropertiesOption(QDomElement root)
StringMap convertToCommandLineArguments(QDomElement root)
std::map< QString, QString > StringMap
void reportWarning(QString msg)
Definition: cxLogger.cpp:91
#define CX_LOG_ERROR
Definition: cxLogger.h:113
boost::shared_ptr< class BoolPropertyBase > BoolPropertyBasePtr
boost::shared_ptr< class DoublePropertyBase > DoublePropertyBasePtr
boost::shared_ptr< class DoubleProperty > DoublePropertyPtr
boost::shared_ptr< struct Package > PackagePtr
static DoublePropertyPtr initialize(const QString &uid, QString name, QString help, double value, DoubleRange range, int decimals, QDomNode root=QDomNode())
QTimer * mSendTimer
Definition: cxStreamer.h:87
virtual void startStreaming(SenderPtr sender)
unsigned char uchar
int convertStringWithDefault(QString text, int def)
boost::shared_ptr< class BoolProperty > BoolPropertyPtr
boost::shared_ptr< Sender > SenderPtr
Definition: cxSender.h:88
SenderPtr mSender
Definition: cxStreamer.h:86
virtual void initialize(StringMap arguments)
Definition: cxStreamer.cpp:80
int getSendInterval() const
how often an image should be sent (in milliseconds)
Definition: cxStreamer.cpp:48
virtual std::vector< PropertyPtr > getSettings(QDomElement root)