image_plugin.cpp
Go to the documentation of this file.
1 // *****************************************************************************
2 //
3 // Copyright (c) 2014, Southwest Research Institute® (SwRI®)
4 // All rights reserved.
5 //
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above copyright
11 // notice, this list of conditions and the following disclaimer in the
12 // documentation and/or other materials provided with the distribution.
13 // * Neither the name of Southwest Research Institute® (SwRI®) nor the
14 // names of its contributors may be used to endorse or promote products
15 // derived from this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 // ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
21 // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 //
28 // *****************************************************************************
29 
31 
32 // C++ standard libraries
33 #include <cstdio>
34 #include <vector>
35 
36 // QT libraries
37 #include <QDialog>
38 #include <QGLWidget>
39 
40 // ROS libraries
41 #include <ros/master.h>
43 #include <opencv2/imgproc/imgproc.hpp>
44 
46 
47 // Declare plugin
50 
51 namespace mapviz_plugins
52 {
53  ImagePlugin::ImagePlugin() :
54  config_widget_(new QWidget()),
55  anchor_(TOP_LEFT),
56  units_(PIXELS),
57  offset_x_(0),
58  offset_y_(0),
59  width_(320),
60  height_(240),
61  transport_("default"),
62  has_image_(false),
63  last_width_(0),
64  last_height_(0),
65  original_aspect_ratio_(1.0)
66  {
67  ui_.setupUi(config_widget_);
68 
69  // Set background white
70  QPalette p(config_widget_->palette());
71  p.setColor(QPalette::Background, Qt::white);
72  config_widget_->setPalette(p);
73 
74  // Set status text red
75  QPalette p3(ui_.status->palette());
76  p3.setColor(QPalette::Text, Qt::red);
77  ui_.status->setPalette(p3);
78 
79  QObject::connect(ui_.selecttopic, SIGNAL(clicked()), this, SLOT(SelectTopic()));
80  QObject::connect(ui_.topic, SIGNAL(editingFinished()), this, SLOT(TopicEdited()));
81  QObject::connect(ui_.anchor, SIGNAL(activated(QString)), this, SLOT(SetAnchor(QString)));
82  QObject::connect(ui_.units, SIGNAL(activated(QString)), this, SLOT(SetUnits(QString)));
83  QObject::connect(ui_.offsetx, SIGNAL(valueChanged(int)), this, SLOT(SetOffsetX(int)));
84  QObject::connect(ui_.offsety, SIGNAL(valueChanged(int)), this, SLOT(SetOffsetY(int)));
85  QObject::connect(ui_.width, SIGNAL(valueChanged(double)), this, SLOT(SetWidth(double)));
86  QObject::connect(ui_.height, SIGNAL(valueChanged(double)), this, SLOT(SetHeight(double)));
87  QObject::connect(this,SIGNAL(VisibleChanged(bool)),this,SLOT(SetSubscription(bool)));
88  QObject::connect(ui_.keep_ratio, SIGNAL(toggled(bool)), this, SLOT(KeepRatioChanged(bool)));
89  QObject::connect(ui_.transport_combo_box, SIGNAL(activated(const QString&)),
90  this, SLOT(SetTransport(const QString&)));
91 
92  ui_.width->setKeyboardTracking(false);
93  ui_.height->setKeyboardTracking(false);
94  }
95 
97  {
98  }
99 
100  void ImagePlugin::SetOffsetX(int offset)
101  {
102  offset_x_ = offset;
103  }
104 
105  void ImagePlugin::SetOffsetY(int offset)
106  {
107  offset_y_ = offset;
108  }
109 
110  void ImagePlugin::SetWidth(double width)
111  {
112  width_ = width;
113  }
114 
115  void ImagePlugin::SetHeight(double height)
116  {
117  height_ = height;
118  }
119 
120  void ImagePlugin::SetAnchor(QString anchor)
121  {
122  if (anchor == "top left")
123  {
124  anchor_ = TOP_LEFT;
125  }
126  else if (anchor == "top center")
127  {
129  }
130  else if (anchor == "top right")
131  {
132  anchor_ = TOP_RIGHT;
133  }
134  else if (anchor == "center left")
135  {
137  }
138  else if (anchor == "center")
139  {
140  anchor_ = CENTER;
141  }
142  else if (anchor == "center right")
143  {
145  }
146  else if (anchor == "bottom left")
147  {
149  }
150  else if (anchor == "bottom center")
151  {
153  }
154  else if (anchor == "bottom right")
155  {
157  }
158  }
159 
160  void ImagePlugin::SetUnits(QString units)
161  {
162  // do this in both cases to avoid image clamping
163  ui_.width->setMaximum(10000);
164  ui_.height->setMaximum(10000);
165 
166  if (units == "pixels")
167  {
168  ui_.width->setDecimals(0);
169  ui_.height->setDecimals(0);
170  units_ = PIXELS;
171  width_ = width_ * double(canvas_->width()) / 100.0;
172  height_ = height_ * double(canvas_->height()) / 100.0;
173  ui_.width->setSuffix(" px");
174  ui_.height->setSuffix(" px");
175  }
176  else if (units == "percent")
177  {
178  ui_.width->setDecimals(1);
179  ui_.height->setDecimals(1);
180  units_ = PERCENT;
181  width_ = width_ * 100.0 / double(canvas_->width());
182  height_ = height_ * 100.0 / double(canvas_->height());
183  ui_.width->setSuffix(" %");
184  ui_.height->setSuffix(" %");
185  }
186  ui_.width->setValue( width_ );
187  ui_.height->setValue( height_ );
188 
189  if( units_ == PERCENT)
190  {
191  ui_.width->setMaximum(100);
192  ui_.height->setMaximum(100);
193  }
194 
195  }
196  void ImagePlugin::SetSubscription(bool visible)
197  {
198  if(topic_.empty())
199  {
200  return;
201  }
202  else if(!visible)
203  {
205  ROS_INFO("Dropped subscription to %s", topic_.c_str());
206  }
207  else
208  {
209  Resubscribe();
210  }
211  }
212 
213  void ImagePlugin::SetTransport(const QString& transport)
214  {
215  transport_ = transport.toStdString();
216  ROS_INFO("Changing image_transport to %s.", transport_.c_str());
217  TopicEdited();
218  }
219 
220  void ImagePlugin::KeepRatioChanged(bool checked)
221  {
222  ui_.height->setEnabled( !checked );
223  if( checked )
224  {
225  ui_.height->setValue( width_ * original_aspect_ratio_ );
226  }
227  }
228 
230  {
231  if (transport_ == "default")
232  {
233  force_resubscribe_ = true;
234  TopicEdited();
235  }
236  }
237 
239  {
241  "sensor_msgs/Image");
242 
243  if(topic.name.empty())
244  {
245  topic.name.clear();
246  TopicEdited();
247 
248  }
249  if (!topic.name.empty())
250  {
251  ui_.topic->setText(QString::fromStdString(topic.name));
252  TopicEdited();
253  }
254  }
255 
257  {
258  std::string topic = ui_.topic->text().trimmed().toStdString();
259  if(!this->Visible())
260  {
261  PrintWarning("Topic is Hidden");
262  initialized_ = false;
263  has_message_ = false;
264  if (!topic.empty())
265  {
266  topic_ = topic;
267  }
269  return;
270  }
271  // Re-subscribe if either the topic or the image transport
272  // have changed.
273  if (force_resubscribe_ ||
274  topic != topic_ ||
276  {
277  force_resubscribe_ = false;
278  initialized_ = false;
279  has_message_ = false;
280  topic_ = topic;
281  PrintWarning("No messages received.");
282 
284 
285  if (!topic_.empty())
286  {
288  if (transport_ == "default")
289  {
290  ROS_DEBUG("Using default transport.");
293  }
294  else
295  {
296  ROS_DEBUG("Setting transport to %s on %s.",
297  transport_.c_str(), local_node_.getNamespace().c_str());
298 
299  local_node_.setParam("image_transport", transport_);
304  local_node_));
305  }
306 
307  ROS_INFO("Subscribing to %s", topic_.c_str());
308  }
309  }
310  }
311 
312  void ImagePlugin::imageCallback(const sensor_msgs::ImageConstPtr& image)
313  {
314  if (!has_message_)
315  {
316  initialized_ = true;
317  has_message_ = true;
318  }
319 
320  image_ = *image;
321 
322  try
323  {
325  }
326  catch (const cv_bridge::Exception& e)
327  {
328  PrintError(e.what());
329  return;
330  }
331 
332  last_width_ = 0;
333  last_height_ = 0;
334  original_aspect_ratio_ = (double)image->height / (double)image->width;
335 
336  if( ui_.keep_ratio->isChecked() )
337  {
338  double height = width_ * original_aspect_ratio_;
339  if (units_ == PERCENT)
340  {
341  height *= (double)canvas_->width() / (double)canvas_->height();
342  }
343  ui_.height->setValue(height);
344  }
345 
346  has_image_ = true;
347  }
348 
349  void ImagePlugin::PrintError(const std::string& message)
350  {
351  PrintErrorHelper(ui_.status, message);
352  }
353 
354  void ImagePlugin::PrintInfo(const std::string& message)
355  {
356  PrintInfoHelper(ui_.status, message);
357  }
358 
359  void ImagePlugin::PrintWarning(const std::string& message)
360  {
361  PrintWarningHelper(ui_.status, message);
362  }
363 
364  QWidget* ImagePlugin::GetConfigWidget(QWidget* parent)
365  {
366  config_widget_->setParent(parent);
367 
368  return config_widget_;
369  }
370 
371  bool ImagePlugin::Initialize(QGLWidget* canvas)
372  {
373  canvas_ = canvas;
374 
375  return true;
376  }
377 
378  void ImagePlugin::ScaleImage(double width, double height)
379  {
380  if (!has_image_)
381  {
382  return;
383  }
384 
385  cv::resize(cv_image_->image, scaled_image_, cvSize2D32f(width, height), 0, 0, CV_INTER_AREA);
386  }
387 
388  void ImagePlugin::DrawIplImage(cv::Mat *image)
389  {
390  // TODO(malban) glTexture2D may be more efficient than glDrawPixels
391 
392  if (image == NULL || image->cols == 0 || image->rows == 0)
393  {
394  return;
395  }
396 
397  GLenum format;
398  switch (image->channels())
399  {
400  case 1:
401  format = GL_LUMINANCE;
402  break;
403  case 2:
404  format = GL_LUMINANCE_ALPHA;
405  break;
406  case 3:
407  format = GL_BGR;
408  break;
409  default:
410  return;
411  }
412 
413  glPixelZoom(1.0f, -1.0f);
414  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
415  glDrawPixels(image->cols, image->rows, format, GL_UNSIGNED_BYTE, image->ptr());
416  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
417 
418 
419  PrintInfo("OK");
420  }
421 
422  void ImagePlugin::Draw(double x, double y, double scale)
423  {
424  // Calculate the correct offsets and dimensions
425  double x_offset = offset_x_;
426  double y_offset = offset_y_;
427  double width = width_;
428  double height = height_;
429 
430  if (units_ == PERCENT)
431  {
432  x_offset = offset_x_ * canvas_->width() / 100.0;
433  y_offset = offset_y_ * canvas_->height() / 100.0;
434  width = width_ * canvas_->width() / 100.0;
435  height = height_ * canvas_->height() / 100.0;
436  }
437 
438  if( ui_.keep_ratio->isChecked() )
439  {
440  height = original_aspect_ratio_ * width;
441  }
442 
443  // Scale the source image if necessary
444  if (width != last_width_ || height != last_height_)
445  {
446  ScaleImage(width, height);
447  }
448 
449  // Calculate the correct render position
450  double x_pos = 0;
451  double y_pos = 0;
452  if (anchor_ == TOP_LEFT)
453  {
454  x_pos = x_offset;
455  y_pos = y_offset;
456  }
457  else if (anchor_ == TOP_CENTER)
458  {
459  x_pos = (canvas_->width() - width) / 2.0 + x_offset;
460  y_pos = y_offset;
461  }
462  else if (anchor_ == TOP_RIGHT)
463  {
464  x_pos = canvas_->width() - width - x_offset;
465  y_pos = y_offset;
466  }
467  else if (anchor_ == CENTER_LEFT)
468  {
469  x_pos = x_offset;
470  y_pos = (canvas_->height() - height) / 2.0 + y_offset;
471  }
472  else if (anchor_ == CENTER)
473  {
474  x_pos = (canvas_->width() - width) / 2.0 + x_offset;
475  y_pos = (canvas_->height() - height) / 2.0 + y_offset;
476  }
477  else if (anchor_ == CENTER_RIGHT)
478  {
479  x_pos = canvas_->width() - width - x_offset;
480  y_pos = (canvas_->height() - height) / 2.0 + y_offset;
481  }
482  else if (anchor_ == BOTTOM_LEFT)
483  {
484  x_pos = x_offset;
485  y_pos = canvas_->height() - height - y_offset;
486  }
487  else if (anchor_ == BOTTOM_CENTER)
488  {
489  x_pos = (canvas_->width() - width) / 2.0 + x_offset;
490  y_pos = canvas_->height() - height - y_offset;
491  }
492  else if (anchor_ == BOTTOM_RIGHT)
493  {
494  x_pos = canvas_->width() - width - x_offset;
495  y_pos = canvas_->height() - height - y_offset;
496  }
497 
498  glMatrixMode(GL_PROJECTION);
499  glPushMatrix();
500  glLoadIdentity();
501  glOrtho(0, canvas_->width(), canvas_->height(), 0, -0.5f, 0.5f);
502 
503  glRasterPos2d(x_pos, y_pos);
504 
506 
507  glPopMatrix();
508 
509  last_width_ = width;
510  last_height_ = height;
511  }
512 
513  void ImagePlugin::LoadConfig(const YAML::Node& node, const std::string& path)
514  {
515  // Note that image_transport should be loaded before the
516  // topic to make sure the transport is set appropriately before we
517  // subscribe.
518  if (node["image_transport"])
519  {
520  node["image_transport"] >> transport_;
521  int index = ui_.transport_combo_box->findText( QString::fromStdString(transport_) );
522  if (index != -1)
523  {
524  ui_.transport_combo_box->setCurrentIndex(index);
525  }
526  else
527  {
528  ROS_WARN("Saved image transport %s is unavailable.",
529  transport_.c_str());
530  }
531  }
532 
533  if (node["topic"])
534  {
535  std::string topic;
536  node["topic"] >> topic;
537  ui_.topic->setText(topic.c_str());
538  TopicEdited();
539  }
540 
541  if (node["anchor"])
542  {
543  std::string anchor;
544  node["anchor"] >> anchor;
545  ui_.anchor->setCurrentIndex(ui_.anchor->findText(anchor.c_str()));
546  SetAnchor(anchor.c_str());
547  }
548 
549  if (node["units"])
550  {
551  std::string units;
552  node["units"] >> units;
553  ui_.units->setCurrentIndex(ui_.units->findText(units.c_str()));
554  SetUnits(units.c_str());
555  }
556 
557  if (node["offset_x"])
558  {
559  node["offset_x"] >> offset_x_;
560  ui_.offsetx->setValue(offset_x_);
561  }
562 
563  if (node["offset_y"])
564  {
565  node["offset_y"] >> offset_y_;
566  ui_.offsety->setValue(offset_y_);
567  }
568 
569  if (node["width"])
570  {
571  node["width"] >> width_;
572  ui_.width->setValue(width_);
573  }
574 
575  if (node["height"])
576  {
577  node["height"] >> height_;
578  ui_.height->setValue(height_);
579  }
580 
581  if (node["keep_ratio"])
582  {
583  bool keep;
584  node["keep_ratio"] >> keep;
585  ui_.keep_ratio->setChecked( keep );
586  }
587  }
588 
589  void ImagePlugin::SaveConfig(YAML::Emitter& emitter, const std::string& path)
590  {
591  emitter << YAML::Key << "topic" << YAML::Value << ui_.topic->text().toStdString();
592  emitter << YAML::Key << "anchor" << YAML::Value << AnchorToString(anchor_);
593  emitter << YAML::Key << "units" << YAML::Value << UnitsToString(units_);
594  emitter << YAML::Key << "offset_x" << YAML::Value << offset_x_;
595  emitter << YAML::Key << "offset_y" << YAML::Value << offset_y_;
596  emitter << YAML::Key << "width" << YAML::Value << width_;
597  emitter << YAML::Key << "height" << YAML::Value << height_;
598  emitter << YAML::Key << "keep_ratio" << YAML::Value << ui_.keep_ratio->isChecked();
599  emitter << YAML::Key << "image_transport" << YAML::Value << transport_;
600  }
601 
603  {
604  std::string anchor_string = "top left";
605 
606  if (anchor == TOP_LEFT)
607  {
608  anchor_string = "top left";
609  }
610  else if (anchor == TOP_CENTER)
611  {
612  anchor_string = "top center";
613  }
614  else if (anchor == TOP_RIGHT)
615  {
616  anchor_string = "top right";
617  }
618  else if (anchor == CENTER_LEFT)
619  {
620  anchor_string = "center left";
621  }
622  else if (anchor == CENTER)
623  {
624  anchor_string = "center";
625  }
626  else if (anchor == CENTER_RIGHT)
627  {
628  anchor_string = "center right";
629  }
630  else if (anchor == BOTTOM_LEFT)
631  {
632  anchor_string = "bottom left";
633  }
634  else if (anchor == BOTTOM_CENTER)
635  {
636  anchor_string = "bottom center";
637  }
638  else if (anchor == BOTTOM_RIGHT)
639  {
640  anchor_string = "bottom right";
641  }
642 
643  return anchor_string;
644  }
645 
647  {
648  std::string units_string = "pixels";
649 
650  if (units == PIXELS)
651  {
652  units_string = "pixels";
653  }
654  else if (units == PERCENT)
655  {
656  units_string = "percent";
657  }
658 
659  return units_string;
660  }
661 
663  {
664  // This is the same way ROS generates anonymous node names.
665  // See http://docs.ros.org/api/roscpp/html/this__node_8cpp_source.html
666  // Giving each image plugin a unique node means that we can control
667  // its image transport individually.
668  char buf[200];
669  snprintf(buf, sizeof(buf), "image_%llu", (unsigned long long)ros::WallTime::now().toNSec());
671  }
672 
674  {
675  node_ = node;
676 
677  // As soon as we have a node, we can find the available image transports
678  // and add them to our combo box.
680  std::vector<std::string> transports = it.getLoadableTransports();
681  Q_FOREACH (const std::string& transport, transports)
682  {
683  QString qtransport = QString::fromStdString(transport).replace("image_transport/", "");
684  ui_.transport_combo_box->addItem(qtransport);
685  }
686 
687  CreateLocalNode();
688  }
689 }
690 
void KeepRatioChanged(bool checked)
void PrintInfo(const std::string &message)
std::vector< std::string > getLoadableTransports() const
Subscriber subscribe(const std::string &base_topic, uint32_t queue_size, const boost::function< void(const sensor_msgs::ImageConstPtr &)> &callback, const ros::VoidPtr &tracked_object=ros::VoidPtr(), const TransportHints &transport_hints=TransportHints())
#define NULL
static ros::master::TopicInfo selectTopic(const std::string &datatype, QWidget *parent=0)
ros::NodeHandle local_node_
Definition: image_plugin.h:138
void DrawIplImage(cv::Mat *image)
bool Initialize(QGLWidget *canvas)
bool Visible() const
ros::NodeHandle node_
virtual void SetNode(const ros::NodeHandle &node)
f
sensor_msgs::Image image_
Definition: image_plugin.h:142
static void PrintWarningHelper(QLabel *status_label, const std::string &message, double throttle=0.0)
static void PrintErrorHelper(QLabel *status_label, const std::string &message, double throttle=0.0)
void PrintError(const std::string &message)
void SetWidth(double width)
#define ROS_WARN(...)
static void PrintInfoHelper(QLabel *status_label, const std::string &message, double throttle=0.0)
void ScaleImage(double width, double height)
void SetAnchor(QString anchor)
image_transport::Subscriber image_sub_
Definition: image_plugin.h:139
std::string getTransport() const
void SetUnits(QString units)
#define ROS_INFO(...)
void LoadConfig(const YAML::Node &node, const std::string &path)
CvImagePtr toCvCopy(const sensor_msgs::ImageConstPtr &source, const std::string &encoding=std::string())
const std::string & getNamespace() const
void SaveConfig(YAML::Emitter &emitter, const std::string &path)
void SetSubscription(bool visible)
std::string AnchorToString(Anchor anchor)
void SetTransport(const QString &transport)
void VisibleChanged(bool visible)
void PrintWarning(const std::string &message)
QWidget * GetConfigWidget(QWidget *parent)
static WallTime now()
cv_bridge::CvImagePtr cv_image_
Definition: image_plugin.h:144
std::string UnitsToString(Units units)
void SetHeight(double height)
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
void imageCallback(const sensor_msgs::ImageConstPtr &image)
void setParam(const std::string &key, const XmlRpc::XmlRpcValue &v) const
void Draw(double x, double y, double scale)
#define ROS_DEBUG(...)


mapviz_plugins
Author(s): Marc Alban
autogenerated on Fri Mar 19 2021 02:44:32