image_view.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2011, Dirk Thomas, TU Darmstadt
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  * * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following
13  * disclaimer in the documentation and/or other materials provided
14  * with the distribution.
15  * * Neither the name of the TU Darmstadt nor the names of its
16  * contributors may be used to endorse or promote products derived
17  * from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30  * POSSIBILITY OF SUCH DAMAGE.
31  */
32 
34 
36 #include <ros/master.h>
38 
39 #include <cv_bridge/cv_bridge.h>
40 #include <opencv2/imgproc/imgproc.hpp>
41 
42 #include <QMessageBox>
43 #include <QPainter>
44 
45 namespace rqt_image_view {
46 
48  : rqt_gui_cpp::Plugin()
49  , widget_(0)
50 {
51  setObjectName("ImageView");
52 }
53 
55 {
56  widget_ = new QWidget();
57  ui_.setupUi(widget_);
58 
59  if (context.serialNumber() > 1)
60  {
61  widget_->setWindowTitle(widget_->windowTitle() + " (" + QString::number(context.serialNumber()) + ")");
62  }
63  context.addWidget(widget_);
64 
65  ui_.image_frame->installEventFilter(this);
66 
68  ui_.topics_combo_box->setCurrentIndex(ui_.topics_combo_box->findText(""));
69  connect(ui_.topics_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(onTopicChanged(int)));
70 
71  ui_.refresh_topics_push_button->setIcon(QIcon::fromTheme("view-refresh"));
72  connect(ui_.refresh_topics_push_button, SIGNAL(pressed()), this, SLOT(updateTopicList()));
73 
74  ui_.zoom_1_push_button->setIcon(QIcon::fromTheme("zoom-original"));
75  connect(ui_.zoom_1_push_button, SIGNAL(toggled(bool)), this, SLOT(onZoom1(bool)));
76 
77  connect(ui_.dynamic_range_check_box, SIGNAL(toggled(bool)), this, SLOT(onDynamicRange(bool)));
78 }
79 
80 bool ImageView::eventFilter(QObject* watched, QEvent* event)
81 {
82  if (watched == ui_.image_frame && event->type() == QEvent::Paint)
83  {
84  QPainter painter(ui_.image_frame);
85  if (!qimage_.isNull())
86  {
87  // TODO: check if full draw is really necessary
88  //QPaintEvent* paint_event = dynamic_cast<QPaintEvent*>(event);
89  //painter.drawImage(paint_event->rect(), qimage_);
90  qimage_mutex_.lock();
91  painter.drawImage(ui_.image_frame->contentsRect(), qimage_);
92  qimage_mutex_.unlock();
93  } else {
94  // default image with gradient
95  QLinearGradient gradient(0, 0, ui_.image_frame->frameRect().width(), ui_.image_frame->frameRect().height());
96  gradient.setColorAt(0, Qt::white);
97  gradient.setColorAt(1, Qt::black);
98  painter.setBrush(gradient);
99  painter.drawRect(0, 0, ui_.image_frame->frameRect().width() + 1, ui_.image_frame->frameRect().height() + 1);
100  }
101  return false;
102  }
103 
104  return QObject::eventFilter(watched, event);
105 }
106 
108 {
110 }
111 
112 void ImageView::saveSettings(qt_gui_cpp::Settings& plugin_settings, qt_gui_cpp::Settings& instance_settings) const
113 {
114  QString topic = ui_.topics_combo_box->currentText();
115  //qDebug("ImageView::saveSettings() topic '%s'", topic.toStdString().c_str());
116  instance_settings.setValue("topic", topic);
117  instance_settings.setValue("zoom1", ui_.zoom_1_push_button->isChecked());
118  instance_settings.setValue("dynamic_range", ui_.dynamic_range_check_box->isChecked());
119  instance_settings.setValue("max_range", ui_.max_range_double_spin_box->value());
120 }
121 
122 void ImageView::restoreSettings(const qt_gui_cpp::Settings& plugin_settings, const qt_gui_cpp::Settings& instance_settings)
123 {
124  bool zoom1_checked = instance_settings.value("zoom1", false).toBool();
125  ui_.zoom_1_push_button->setChecked(zoom1_checked);
126 
127  bool dynamic_range_checked = instance_settings.value("dynamic_range", false).toBool();
128  ui_.dynamic_range_check_box->setChecked(dynamic_range_checked);
129 
130  double max_range = instance_settings.value("max_range", ui_.max_range_double_spin_box->value()).toDouble();
131  ui_.max_range_double_spin_box->setValue(max_range);
132 
133  QString topic = instance_settings.value("topic", "").toString();
134  //qDebug("ImageView::restoreSettings() topic '%s'", topic.toStdString().c_str());
135  selectTopic(topic);
136 }
137 
139 {
140  QSet<QString> message_types;
141  message_types.insert("sensor_msgs/Image");
142 
143  // get declared transports
144  QList<QString> transports;
146  std::vector<std::string> declared = it.getDeclaredTransports();
147  for (std::vector<std::string>::const_iterator it = declared.begin(); it != declared.end(); it++)
148  {
149  //qDebug("ImageView::updateTopicList() declared transport '%s'", it->c_str());
150  QString transport = it->c_str();
151 
152  // strip prefix from transport name
153  QString prefix = "image_transport/";
154  if (transport.startsWith(prefix))
155  {
156  transport = transport.mid(prefix.length());
157  }
158  transports.append(transport);
159  }
160 
161  QString selected = ui_.topics_combo_box->currentText();
162 
163  // fill combo box
164  QList<QString> topics = getTopicList(message_types, transports);
165  topics.append("");
166  qSort(topics);
167  ui_.topics_combo_box->clear();
168  for (QList<QString>::const_iterator it = topics.begin(); it != topics.end(); it++)
169  {
170  QString label(*it);
171  label.replace(" ", "/");
172  ui_.topics_combo_box->addItem(label, QVariant(*it));
173  }
174 
175  // restore previous selection
176  selectTopic(selected);
177 }
178 
179 QList<QString> ImageView::getTopicList(const QSet<QString>& message_types, const QList<QString>& transports)
180 {
181  ros::master::V_TopicInfo topic_info;
182  ros::master::getTopics(topic_info);
183 
184  QSet<QString> all_topics;
185  for (ros::master::V_TopicInfo::const_iterator it = topic_info.begin(); it != topic_info.end(); it++)
186  {
187  all_topics.insert(it->name.c_str());
188  }
189 
190  QList<QString> topics;
191  for (ros::master::V_TopicInfo::const_iterator it = topic_info.begin(); it != topic_info.end(); it++)
192  {
193  if (message_types.contains(it->datatype.c_str()))
194  {
195  QString topic = it->name.c_str();
196 
197  // add raw topic
198  topics.append(topic);
199  //qDebug("ImageView::getTopicList() raw topic '%s'", topic.toStdString().c_str());
200 
201  // add transport specific sub-topics
202  for (QList<QString>::const_iterator jt = transports.begin(); jt != transports.end(); jt++)
203  {
204  if (all_topics.contains(topic + "/" + *jt))
205  {
206  QString sub = topic + " " + *jt;
207  topics.append(sub);
208  //qDebug("ImageView::getTopicList() transport specific sub-topic '%s'", sub.toStdString().c_str());
209  }
210  }
211  }
212  }
213  return topics;
214 }
215 
216 void ImageView::selectTopic(const QString& topic)
217 {
218  int index = ui_.topics_combo_box->findText(topic);
219  if (index == -1)
220  {
221  index = ui_.topics_combo_box->findText("");
222  }
223  ui_.topics_combo_box->setCurrentIndex(index);
224 }
225 
227 {
229 
230  // reset image on topic change
231  qimage_ = QImage();
232  ui_.image_frame->update();
233 
234  QStringList parts = ui_.topics_combo_box->itemData(index).toString().split(" ");
235  QString topic = parts.first();
236  QString transport = parts.length() == 2 ? parts.last() : "raw";
237 
238  if (!topic.isEmpty())
239  {
241  image_transport::TransportHints hints(transport.toStdString());
242  try {
243  subscriber_ = it.subscribe(topic.toStdString(), 1, &ImageView::callbackImage, this, hints);
244  //qDebug("ImageView::onTopicChanged() to topic '%s' with transport '%s'", topic.toStdString().c_str(), subscriber_.getTransport().c_str());
246  QMessageBox::warning(widget_, tr("Loading image transport plugin failed"), e.what());
247  }
248  }
249 }
250 
251 void ImageView::onZoom1(bool checked)
252 {
253  if (checked)
254  {
255  if (qimage_.isNull())
256  {
257  return;
258  }
259  widget_->resize(ui_.image_frame->size());
260  widget_->setMinimumSize(widget_->sizeHint());
261  widget_->setMaximumSize(widget_->sizeHint());
262  } else {
263  widget_->setMinimumSize(QSize(80, 60));
264  widget_->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
265  }
266 }
267 
268 void ImageView::onDynamicRange(bool checked)
269 {
270  ui_.max_range_double_spin_box->setEnabled(!checked);
271 }
272 
273 void ImageView::callbackImage(const sensor_msgs::Image::ConstPtr& msg)
274 {
275  try
276  {
277  // First let cv_bridge do its magic
279  conversion_mat_ = cv_ptr->image;
280  }
281  catch (cv_bridge::Exception& e)
282  {
283  // If we're here, there is no conversion that makes sense, but let's try to imagine a few first
285  if (msg->encoding == "CV_8UC3")
286  {
287  // assuming it is rgb
288  conversion_mat_ = cv_ptr->image;
289  } else if (msg->encoding == "8UC1") {
290  // convert gray to rgb
291  cv::cvtColor(cv_ptr->image, conversion_mat_, CV_GRAY2RGB);
292  } else if (msg->encoding == "16UC1" || msg->encoding == "32FC1") {
293  // scale / quantify
294  double min = 0;
295  double max = ui_.max_range_double_spin_box->value();
296  if (msg->encoding == "16UC1") max *= 1000;
297  if (ui_.dynamic_range_check_box->isChecked())
298  {
299  // dynamically adjust range based on min/max in image
300  cv::minMaxLoc(cv_ptr->image, &min, &max);
301  if (min == max) {
302  // completely homogeneous images are displayed in gray
303  min = 0;
304  max = 2;
305  }
306  }
307  cv::Mat img_scaled_8u;
308  cv::Mat(cv_ptr->image-min).convertTo(img_scaled_8u, CV_8UC1, 255. / (max - min));
309  cv::cvtColor(img_scaled_8u, conversion_mat_, CV_GRAY2RGB);
310  } else {
311  qWarning("ImageView.callback_image() could not convert image from '%s' to 'rgb8' (%s)", msg->encoding.c_str(), e.what());
312  qimage_ = QImage();
313  return;
314  }
315  }
316 
317  // copy temporary image as it uses the conversion_mat_ for storage which is asynchronously overwritten in the next callback invocation
318  QImage image(conversion_mat_.data, conversion_mat_.cols, conversion_mat_.rows, QImage::Format_RGB888);
319  qimage_mutex_.lock();
320  qimage_ = image.copy();
321  qimage_mutex_.unlock();
322 
323  if (!ui_.zoom_1_push_button->isEnabled())
324  {
325  ui_.zoom_1_push_button->setEnabled(true);
326  onZoom1(ui_.zoom_1_push_button->isChecked());
327  }
328  ui_.image_frame->update();
329 }
330 
331 }
332 
CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr &source, const std::string &encoding=std::string())
virtual void initPlugin(qt_gui_cpp::PluginContext &context)
Definition: image_view.cpp:54
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())
virtual void callbackImage(const sensor_msgs::Image::ConstPtr &msg)
Definition: image_view.cpp:273
virtual QList< QString > getTopicList(const QSet< QString > &message_types, const QList< QString > &transports)
Definition: image_view.cpp:179
image_transport::Subscriber subscriber_
Definition: image_view.h:102
Ui::ImageViewWidget ui_
Definition: image_view.h:98
virtual void onDynamicRange(bool checked)
Definition: image_view.cpp:268
ROSCPP_DECL bool getTopics(V_TopicInfo &topics)
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
void addWidget(QWidget *widget)
std::vector< TopicInfo > V_TopicInfo
virtual void onZoom1(bool checked)
Definition: image_view.cpp:251
virtual bool eventFilter(QObject *watched, QEvent *event)
Definition: image_view.cpp:80
virtual void onTopicChanged(int index)
Definition: image_view.cpp:226
virtual void shutdownPlugin()
Definition: image_view.cpp:107
std::vector< std::string > getDeclaredTransports() const
void setValue(const QString &key, const QVariant &value)
ros::NodeHandle & getNodeHandle() const
virtual void updateTopicList()
Definition: image_view.cpp:138
virtual void saveSettings(qt_gui_cpp::Settings &plugin_settings, qt_gui_cpp::Settings &instance_settings) const
Definition: image_view.cpp:112
virtual void selectTopic(const QString &topic)
Definition: image_view.cpp:216
virtual void restoreSettings(const qt_gui_cpp::Settings &plugin_settings, const qt_gui_cpp::Settings &instance_settings)
Definition: image_view.cpp:122
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)


hector_rqt_plugins
Author(s): Dirk Thomas, Thorsten Graber, Gregor Gebhardt
autogenerated on Mon Jun 10 2019 13:36:34