image_enhancer.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>
37 #include <ros/console.h>
39 
40 #include <cv_bridge/cv_bridge.h>
41 #include <opencv2/imgproc/imgproc.hpp>
42 
43 #include <QMessageBox>
44 #include <QPainter>
45 
46 #include <algorithm>
47 
48 namespace rqt_image_enhancer {
49 
51  : rqt_gui_cpp::Plugin()
52  , widget_(0)
53  , selected_(false)
54 {
55  setObjectName("ImageEnhancer");
56 }
57 
59 {
60  widget_ = new QWidget();
61  ui_.setupUi(widget_);
62 
63  if (context.serialNumber() > 1)
64  {
65  widget_->setWindowTitle(widget_->windowTitle() + " (" + QString::number(context.serialNumber()) + ")");
66  }
67  context.addWidget(widget_);
68 
69  ui_.image_frame->installEventFilter(this);
70 
72  ui_.topics_combo_box->setCurrentIndex(ui_.topics_combo_box->findText(""));
73  connect(ui_.topics_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(onTopicChanged(int)));
74 
75  ui_.refresh_topics_push_button->setIcon(QIcon::fromTheme("view-refresh"));
76  connect(ui_.refresh_topics_push_button, SIGNAL(pressed()), this, SLOT(updateTopicList()));
77 
78  connect(ui_.dynamic_range_check_box, SIGNAL(toggled(bool)), this, SLOT(onDynamicRange(bool)));
79 
80  connect(ui_.image_frame, SIGNAL(rightMouseButtonClicked()), this, SLOT(onRemoveSelection()));
81  connect(ui_.image_frame, SIGNAL(selectionInProgress(QPoint,QPoint)), this, SLOT(onSelectionInProgress(QPoint,QPoint)));
82  connect(ui_.image_frame, SIGNAL(selectionFinished(QPoint,QPoint)), this, SLOT(onSelectionFinished(QPoint,QPoint)));
83 }
84 
85 bool ImageEnhancer::eventFilter(QObject* watched, QEvent* event)
86 {
87  if (watched == ui_.image_frame && event->type() == QEvent::Paint)
88  {
89  QPainter painter(ui_.image_frame);
90  if (!qimage_.isNull())
91  {
92  ui_.image_frame->resizeToFitAspectRatio();
93  // TODO: check if full draw is really necessary
94  //QPaintEvent* paint_event = dynamic_cast<QPaintEvent*>(event);
95  //painter.drawImage(paint_event->rect(), qimage_);
96  qimage_mutex_.lock();
97  painter.drawImage(ui_.image_frame->contentsRect(), qimage_);
98  qimage_mutex_.unlock();
99  } else {
100  // default image with gradient
101  QLinearGradient gradient(0, 0, ui_.image_frame->frameRect().width(), ui_.image_frame->frameRect().height());
102  gradient.setColorAt(0, Qt::white);
103  gradient.setColorAt(1, Qt::black);
104  painter.setBrush(gradient);
105  painter.drawRect(0, 0, ui_.image_frame->frameRect().width() + 1, ui_.image_frame->frameRect().height() + 1);
106  }
107 
108  if(selected_)
109  {
111  painter.setPen(Qt::red);
112  painter.drawRect(rect);
113  }
114 
115  ui_.image_frame->update();
116 
117  return false;
118  }
119 
120  return QObject::eventFilter(watched, event);
121 }
122 
124 {
126 }
127 
128 void ImageEnhancer::saveSettings(qt_gui_cpp::Settings& plugin_settings, qt_gui_cpp::Settings& instance_settings) const
129 {
130  QString topic = ui_.topics_combo_box->currentText();
131  //qDebug("ImageEnhancer::saveSettings() topic '%s'", topic.toStdString().c_str());
132  instance_settings.setValue("topic", topic);
133  instance_settings.setValue("dynamic_range", ui_.dynamic_range_check_box->isChecked());
134  instance_settings.setValue("max_range", ui_.max_range_double_spin_box->value());
135 }
136 
137 void ImageEnhancer::restoreSettings(const qt_gui_cpp::Settings& plugin_settings, const qt_gui_cpp::Settings& instance_settings)
138 {
139  bool dynamic_range_checked = instance_settings.value("dynamic_range", false).toBool();
140  ui_.dynamic_range_check_box->setChecked(dynamic_range_checked);
141 
142  double max_range = instance_settings.value("max_range", ui_.max_range_double_spin_box->value()).toDouble();
143  ui_.max_range_double_spin_box->setValue(max_range);
144 
145  QString topic = instance_settings.value("topic", "").toString();
146  //qDebug("ImageEnhancer::restoreSettings() topic '%s'", topic.toStdString().c_str());
147  selectTopic(topic);
148 }
149 
151 {
152  QSet<QString> message_types;
153  message_types.insert("sensor_msgs/Image");
154 
155  // get declared transports
156  QList<QString> transports;
158  std::vector<std::string> declared = it.getDeclaredTransports();
159  for (std::vector<std::string>::const_iterator it = declared.begin(); it != declared.end(); it++)
160  {
161  //qDebug("ImageEnhancer::updateTopicList() declared transport '%s'", it->c_str());
162  QString transport = it->c_str();
163 
164  // strip prefix from transport name
165  QString prefix = "image_transport/";
166  if (transport.startsWith(prefix))
167  {
168  transport = transport.mid(prefix.length());
169  }
170  transports.append(transport);
171  }
172 
173  QString selected = ui_.topics_combo_box->currentText();
174 
175  // fill combo box
176  QList<QString> topics = getTopicList(message_types, transports);
177  topics.append("");
178  qSort(topics);
179  ui_.topics_combo_box->clear();
180  for (QList<QString>::const_iterator it = topics.begin(); it != topics.end(); it++)
181  {
182  QString label(*it);
183  label.replace(" ", "/");
184  ui_.topics_combo_box->addItem(label, QVariant(*it));
185  }
186 
187  // restore previous selection
188  selectTopic(selected);
189 }
190 
191 QList<QString> ImageEnhancer::getTopicList(const QSet<QString>& message_types, const QList<QString>& transports)
192 {
193  ros::master::V_TopicInfo topic_info;
194  ros::master::getTopics(topic_info);
195 
196  QSet<QString> all_topics;
197  for (ros::master::V_TopicInfo::const_iterator it = topic_info.begin(); it != topic_info.end(); it++)
198  {
199  all_topics.insert(it->name.c_str());
200  }
201 
202  QList<QString> topics;
203  for (ros::master::V_TopicInfo::const_iterator it = topic_info.begin(); it != topic_info.end(); it++)
204  {
205  if (message_types.contains(it->datatype.c_str()))
206  {
207  QString topic = it->name.c_str();
208 
209  // add raw topic
210  topics.append(topic);
211  //qDebug("ImageEnhancer::getTopicList() raw topic '%s'", topic.toStdString().c_str());
212 
213  // add transport specific sub-topics
214  for (QList<QString>::const_iterator jt = transports.begin(); jt != transports.end(); jt++)
215  {
216  if (all_topics.contains(topic + "/" + *jt))
217  {
218  QString sub = topic + " " + *jt;
219  topics.append(sub);
220  //qDebug("ImageEnhancer::getTopicList() transport specific sub-topic '%s'", sub.toStdString().c_str());
221  }
222  }
223  }
224  }
225  return topics;
226 }
227 
228 void ImageEnhancer::selectTopic(const QString& topic)
229 {
230  int index = ui_.topics_combo_box->findText(topic);
231  if (index == -1)
232  {
233  index = ui_.topics_combo_box->findText("");
234  }
235  ui_.topics_combo_box->setCurrentIndex(index);
236 }
237 
239 {
241 
242  // reset image on topic change
243  qimage_ = QImage();
244  ui_.image_frame->update();
245 
246  QStringList parts = ui_.topics_combo_box->itemData(index).toString().split(" ");
247  QString topic = parts.first();
248  QString transport = parts.length() == 2 ? parts.last() : "raw";
249 
250  if (!topic.isEmpty())
251  {
253  image_transport::TransportHints hints(transport.toStdString());
254  try {
255  subscriber_ = it.subscribe(topic.toStdString(), 1, &ImageEnhancer::callbackImage, this, hints);
256  //qDebug("ImageEnhancer::onTopicChanged() to topic '%s' with transport '%s'", topic.toStdString().c_str(), subscriber_.getTransport().c_str());
258  QMessageBox::warning(widget_, tr("Loading image transport plugin failed"), e.what());
259  }
260  }
261 }
262 
263 void ImageEnhancer::onZoom1(bool checked)
264 {
265  if (checked)
266  {
267  if (qimage_.isNull())
268  {
269  return;
270  }
271  ui_.image_frame->setInnerFrameFixedSize(qimage_.size());
272  widget_->resize(ui_.image_frame->size());
273  widget_->setMinimumSize(widget_->sizeHint());
274  widget_->setMaximumSize(widget_->sizeHint());
275  } else {
276  ui_.image_frame->setInnerFrameMinimumSize(QSize(80, 60));
277  ui_.image_frame->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
278  widget_->setMinimumSize(QSize(80, 60));
279  widget_->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
280  }
281 }
282 
283 void ImageEnhancer::onSelectionInProgress(QPoint p1, QPoint p2)
284 {
287 
288  int tl_x = std::min(p1.x(), p2.x());
289  int tl_y = std::min(p1.y(), p2.y());
290 
291  selection_top_left_ = QPoint(tl_x,tl_y);
292  selection_size_ = QSize(abs(p1.x() - p2.x()), abs(p1.y() - p2.y()));
293 
294  //ROS_DEBUG_STREAM << "p1: " << p1.x() << " " << p1.y() << " p2: " << p2.x() << " " << p2.y();
295 
296  selected_ = true;
297 }
298 
299 void ImageEnhancer::onSelectionFinished(QPoint p1, QPoint p2)
300 {
303 
304  int tl_x = p1.x() < p2.x() ? p1.x() : p2.x();
305  int tl_y = p1.y() < p2.y() ? p1.y() : p2.y();
306 
307  selection_top_left_ = QPoint(tl_x,tl_y);
308  selection_size_ = QSize(abs(p1.x() - p2.x()), abs(p1.y() - p2.y()));
309 
310 
311 }
312 
314 {
315  selected_ = false;
316 }
317 
319 {
320  int min_x = 0;
321  int max_x = ui_.image_frame->width();
322 
323  int min_y = 0;
324  int max_y = ui_.image_frame->height();
325 
326  p.setX(std::min(std::max(p.x(),min_x),max_x));
327  p.setY(std::min(std::max(p.y(),min_y),max_y));
328 }
329 
331 {
332  ui_.max_range_double_spin_box->setEnabled(!checked);
333 }
334 
335 void ImageEnhancer::callbackImage(const sensor_msgs::Image::ConstPtr& msg)
336 {
337  if(!selected_)
338  {
339  try
340  {
341  // First let cv_bridge do its magic
343  conversion_mat_ = cv_ptr->image;
344  }
345  catch (cv_bridge::Exception& e)
346  {
347  // If we're here, there is no conversion that makes sense, but let's try to imagine a few first
349  if (msg->encoding == "CV_8UC3")
350  {
351  // assuming it is rgb
352  conversion_mat_ = cv_ptr->image;
353  } else if (msg->encoding == "8UC1") {
354  // convert gray to rgb
355  cv::cvtColor(cv_ptr->image, conversion_mat_, CV_GRAY2RGB);
356  } else if (msg->encoding == "16UC1" || msg->encoding == "32FC1") {
357  // scale / quantify
358  double min = 0;
359  double max = ui_.max_range_double_spin_box->value();
360  if (msg->encoding == "16UC1") max *= 1000;
361  if (ui_.dynamic_range_check_box->isChecked())
362  {
363  // dynamically adjust range based on min/max in image
364  cv::minMaxLoc(cv_ptr->image, &min, &max);
365  if (min == max) {
366  // completely homogeneous images are displayed in gray
367  min = 0;
368  max = 2;
369  }
370  }
371  cv::Mat img_scaled_8u;
372  cv::Mat(cv_ptr->image-min).convertTo(img_scaled_8u, CV_8UC1, 255. / (max - min));
373  cv::cvtColor(img_scaled_8u, conversion_mat_, CV_GRAY2RGB);
374  } else {
375  qWarning("ImageEnhancer.callback_image() could not convert image from '%s' to 'rgb8' (%s)", msg->encoding.c_str(), e.what());
376  qimage_ = QImage();
377  return;
378  }
379  }
380 
381  // copy temporary image as it uses the conversion_mat_ for storage which is asynchronously overwritten in the next callback invocation
382  QImage image(conversion_mat_.data, conversion_mat_.cols, conversion_mat_.rows, QImage::Format_RGB888);
383  qimage_mutex_.lock();
384  qimage_ = image.copy();
385  qimage_mutex_.unlock();
386 
387  ui_.image_frame->setAspectRatio(qimage_.width(), qimage_.height());
388  //onZoom1(false);
389 
390  ui_.image_frame->setInnerFrameMinimumSize(QSize(80, 60));
391  ui_.image_frame->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
392  widget_->setMinimumSize(QSize(80, 60));
393  widget_->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
394  }
395 }
396 
397 }
398 
virtual void saveSettings(qt_gui_cpp::Settings &plugin_settings, qt_gui_cpp::Settings &instance_settings) const
CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr &source, const std::string &encoding=std::string())
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 onSelectionFinished(QPoint p1, QPoint p2)
virtual void initPlugin(qt_gui_cpp::PluginContext &context)
virtual void onZoom1(bool checked)
ROSCPP_DECL bool getTopics(V_TopicInfo &topics)
virtual void selectTopic(const QString &topic)
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
image_transport::Subscriber subscriber_
void addWidget(QWidget *widget)
std::vector< TopicInfo > V_TopicInfo
virtual void onDynamicRange(bool checked)
std::vector< std::string > getDeclaredTransports() const
void setValue(const QString &key, const QVariant &value)
virtual bool eventFilter(QObject *watched, QEvent *event)
ros::NodeHandle & getNodeHandle() const
virtual void callbackImage(const sensor_msgs::Image::ConstPtr &msg)
virtual void restoreSettings(const qt_gui_cpp::Settings &plugin_settings, const qt_gui_cpp::Settings &instance_settings)
Ui::ImageEnhancerWidget ui_
virtual void enforceSelectionConstraints(QPoint &p)
virtual QList< QString > getTopicList(const QSet< QString > &message_types, const QList< QString > &transports)
virtual void onSelectionInProgress(QPoint p1, QPoint p2)
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
virtual void onTopicChanged(int index)


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