image_cropper.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/opencv.hpp>
42 
43 #include <QMessageBox>
44 #include <QPainter>
45 
46 #include <algorithm>
47 
48 namespace rqt_image_cropping {
49 
51  : rqt_gui_cpp::Plugin()
52  , widget_(0)
53  , selected_(false)
54 {
55  setObjectName("ImageCropper");
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 
73  ui_.topics_combo_box->setCurrentIndex(ui_.topics_combo_box->findText(""));
74  connect(ui_.topics_combo_box, SIGNAL(currentIndexChanged(int)), this, SLOT(onInTopicChanged(int)));
75 
76  ui_.out_topic_line_edit->setText("/cropped");
77  connect(ui_.out_topic_line_edit, SIGNAL(editingFinished()), this, SLOT(onOutTopicChanged()));
79 
80  ui_.refresh_topics_push_button->setIcon(QIcon::fromTheme("view-refresh"));
81  connect(ui_.refresh_topics_push_button, SIGNAL(pressed()), this, SLOT(updateTopicList()));
82 
83  connect(ui_.dynamic_range_check_box, SIGNAL(toggled(bool)), this, SLOT(onDynamicRange(bool)));
84 
85  connect(ui_.image_frame, SIGNAL(rightMouseButtonClicked()), this, SLOT(onRemoveSelection()));
86  connect(ui_.image_frame, SIGNAL(selectionInProgress(QPoint,QPoint)), this, SLOT(onSelectionInProgress(QPoint,QPoint)));
87  connect(ui_.image_frame, SIGNAL(selectionFinished(QPoint,QPoint)), this, SLOT(onSelectionFinished(QPoint,QPoint)));
88 }
89 
90 bool ImageCropper::eventFilter(QObject* watched, QEvent* event)
91 {
92  if (watched == ui_.image_frame && event->type() == QEvent::Paint)
93  {
94  QPainter painter(ui_.image_frame);
95  if (!qimage_.isNull())
96  {
97  ui_.image_frame->resizeToFitAspectRatio();
98  // TODO: check if full draw is really necessary
99  //QPaintEvent* paint_event = dynamic_cast<QPaintEvent*>(event);
100  //painter.drawImage(paint_event->rect(), qimage_);
101  qimage_mutex_.lock();
102  painter.drawImage(ui_.image_frame->contentsRect(), qimage_);
103  qimage_mutex_.unlock();
104  } else {
105  // default image with gradient
106  QLinearGradient gradient(0, 0, ui_.image_frame->frameRect().width(), ui_.image_frame->frameRect().height());
107  gradient.setColorAt(0, Qt::white);
108  gradient.setColorAt(1, Qt::black);
109  painter.setBrush(gradient);
110  painter.drawRect(0, 0, ui_.image_frame->frameRect().width() + 1, ui_.image_frame->frameRect().height() + 1);
111  }
112 
113  if(selected_)
114  {
116 
117  painter.setPen(Qt::red);
118  painter.drawRect(selection_);
119  }
120 
121  ui_.image_frame->update();
122 
123  return false;
124  }
125 
126  return QObject::eventFilter(watched, event);
127 }
128 
130 {
133 }
134 
135 void ImageCropper::saveSettings(qt_gui_cpp::Settings& plugin_settings, qt_gui_cpp::Settings& instance_settings) const
136 {
137  QString topic = ui_.topics_combo_box->currentText();
138  //qDebug("ImageCropper::saveSettings() topic '%s'", topic.toStdString().c_str());
139  instance_settings.setValue("topic", topic);
140  instance_settings.setValue("dynamic_range", ui_.dynamic_range_check_box->isChecked());
141  instance_settings.setValue("max_range", ui_.max_range_double_spin_box->value());
142 }
143 
144 void ImageCropper::restoreSettings(const qt_gui_cpp::Settings& plugin_settings, const qt_gui_cpp::Settings& instance_settings)
145 {
146  bool dynamic_range_checked = instance_settings.value("dynamic_range", false).toBool();
147  ui_.dynamic_range_check_box->setChecked(dynamic_range_checked);
148 
149  double max_range = instance_settings.value("max_range", ui_.max_range_double_spin_box->value()).toDouble();
150  ui_.max_range_double_spin_box->setValue(max_range);
151 
152  QString topic = instance_settings.value("topic", "").toString();
153  //qDebug("ImageCropper::restoreSettings() topic '%s'", topic.toStdString().c_str());
154  selectTopic(topic);
155 }
156 
158 {
159  QSet<QString> message_types;
160  message_types.insert("sensor_msgs/Image");
161 
162  // get declared transports
163  QList<QString> transports;
165  std::vector<std::string> declared = it.getDeclaredTransports();
166  for (std::vector<std::string>::const_iterator it = declared.begin(); it != declared.end(); it++)
167  {
168  //qDebug("ImageCropper::updateTopicList() declared transport '%s'", it->c_str());
169  QString transport = it->c_str();
170 
171  // strip prefix from transport name
172  QString prefix = "image_transport/";
173  if (transport.startsWith(prefix))
174  {
175  transport = transport.mid(prefix.length());
176  }
177  transports.append(transport);
178  }
179 
180  QString selected = ui_.topics_combo_box->currentText();
181 
182  // fill combo box
183  QList<QString> topics = getTopicList(message_types, transports);
184  topics.append("");
185  qSort(topics);
186  ui_.topics_combo_box->clear();
187  for (QList<QString>::const_iterator it = topics.begin(); it != topics.end(); it++)
188  {
189  QString label(*it);
190  label.replace(" ", "/");
191  ui_.topics_combo_box->addItem(label, QVariant(*it));
192  }
193 
194  // restore previous selection
195  selectTopic(selected);
196 }
197 
198 QList<QString> ImageCropper::getTopicList(const QSet<QString>& message_types, const QList<QString>& transports)
199 {
200  ros::master::V_TopicInfo topic_info;
201  ros::master::getTopics(topic_info);
202 
203  QSet<QString> all_topics;
204  for (ros::master::V_TopicInfo::const_iterator it = topic_info.begin(); it != topic_info.end(); it++)
205  {
206  all_topics.insert(it->name.c_str());
207  }
208 
209  QList<QString> topics;
210  for (ros::master::V_TopicInfo::const_iterator it = topic_info.begin(); it != topic_info.end(); it++)
211  {
212  if (message_types.contains(it->datatype.c_str()))
213  {
214  QString topic = it->name.c_str();
215 
216  // add raw topic
217  topics.append(topic);
218  //qDebug("ImageCropper::getTopicList() raw topic '%s'", topic.toStdString().c_str());
219 
220  // add transport specific sub-topics
221  for (QList<QString>::const_iterator jt = transports.begin(); jt != transports.end(); jt++)
222  {
223  if (all_topics.contains(topic + "/" + *jt))
224  {
225  QString sub = topic + " " + *jt;
226  topics.append(sub);
227  //qDebug("ImageCropper::getTopicList() transport specific sub-topic '%s'", sub.toStdString().c_str());
228  }
229  }
230  }
231  }
232  return topics;
233 }
234 
235 void ImageCropper::selectTopic(const QString& topic)
236 {
237  int index = ui_.topics_combo_box->findText(topic);
238  if (index == -1)
239  {
240  index = ui_.topics_combo_box->findText("");
241  }
242  ui_.topics_combo_box->setCurrentIndex(index);
243 }
244 
246 {
248 
249  // reset image on topic change
250  qimage_ = QImage();
251  ui_.image_frame->update();
252 
253  QStringList parts = ui_.topics_combo_box->itemData(index).toString().split(" ");
254  QString topic = parts.first();
255  QString transport = parts.length() == 2 ? parts.last() : "raw";
256 
257  if (!topic.isEmpty())
258  {
260  image_transport::TransportHints hints(transport.toStdString());
261  try {
262  subscriber_ = it.subscribeCamera(topic.toStdString(), 1, &ImageCropper::callbackImage, this, hints);
263  //qDebug("ImageCropper::onInTopicChanged() to topic '%s' with transport '%s'", topic.toStdString().c_str(), subscriber_.getTransport().c_str());
265  QMessageBox::warning(widget_, tr("Loading image transport plugin failed"), e.what());
266  }
267  }
268 
269  selected_ = false;
270 }
271 
273 {
275 
276  QString topic = ui_.out_topic_line_edit->text();
277 
278  if(!topic.isEmpty())
279  {
281 
282  try {
283  publisher_ = it.advertiseCamera(topic.toStdString(), 1, true);
285  QMessageBox::warning(widget_, tr("Loading image transport plugin failed"), e.what());
286  }
287  }
288 
289  selected_ = false;
290 }
291 
292 void ImageCropper::onSelectionInProgress(QPoint p1, QPoint p2)
293 {
296 
297  int tl_x = std::min(p1.x(), p2.x());
298  int tl_y = std::min(p1.y(), p2.y());
299 
300  selection_top_left_rect_ = QPointF(tl_x,tl_y);
301  selection_size_rect_ = QSizeF(abs(p1.x() - p2.x()), abs(p1.y() - p2.y()));
302 
303  //ROS_DEBUG_STREAM << "p1: " << p1.x() << " " << p1.y() << " p2: " << p2.x() << " " << p2.y();
304 
305  selected_ = true;
306 }
307 
308 void ImageCropper::onSelectionFinished(QPoint p1, QPoint p2)
309 {
312 
313  int tl_x = p1.x() < p2.x() ? p1.x() : p2.x();
314  int tl_y = p1.y() < p2.y() ? p1.y() : p2.y();
315 
316  selection_top_left_rect_ = QPointF(tl_x,tl_y);
317  selection_size_rect_ = QSizeF(abs(p1.x() - p2.x()), abs(p1.y() - p2.y()));
318 
319  selection_top_left_ = QPointF(tl_x,tl_y);
320  selection_size_ = QSizeF(abs(p1.x() - p2.x()), abs(p1.y() - p2.y()));
321 
322  selection_top_left_ *= (double)qimage_.width() / (double)ui_.image_frame->contentsRect().width();// width();
323  selection_size_ *= (double)qimage_.width() / (double)ui_.image_frame->width();
324 
325  // crop image from cv image
326  cv::Mat roi = cv::Mat(conversion_mat_, cv::Rect(selection_top_left_.x(), selection_top_left_.y(), selection_size_.width(), selection_size_.height()));
327 
328 // std::cout << "sle height: " << selection_size_.height() << " width: " << selection_size_.width() << std::endl;
329 // std::cout << "roi rows: " << roi.rows << " cols: " << roi.cols << std::endl;
330 
331 // cv_bridge::CvImage crop;
332 // crop.header = sens_msg_image_->header;
333 // crop.encoding = sensor_msgs::image_encodings::RGB8;
334 // crop.image = roi;
335 
336  // adapt camera info
337  // Create updated CameraInfo message
338  sensor_msgs::CameraInfoPtr out_info = boost::make_shared<sensor_msgs::CameraInfo>(*camera_info_);
339  int binning_x = std::max((int)camera_info_->binning_x, 1);
340  int binning_y = std::max((int)camera_info_->binning_y, 1);
341  out_info->binning_x = binning_x * 1;
342  out_info->binning_y = binning_y * 1;
343  out_info->roi.x_offset += selection_top_left_.x() * binning_x;
344  out_info->roi.y_offset += selection_top_left_.y() * binning_y;
345  out_info->roi.height = selection_size_.height() * binning_y;
346  out_info->roi.width = selection_size_.width() * binning_x;
347 
348 
350  {
352  }
353 }
354 
356 {
357  selected_region_ = QImage();
358  selected_ = false;
359 }
360 
362 {
363  int min_x = 1;
364  int max_x = ui_.image_frame->width() - 2 * ui_.image_frame->frameWidth();
365 
366  int min_y = 1;
367  int max_y = ui_.image_frame->height() - 2 * ui_.image_frame->frameWidth();
368 
369  p.setX(std::min(std::max(p.x(),min_x),max_x));
370  p.setY(std::min(std::max(p.y(),min_y),max_y));
371 }
372 
374 {
375  ui_.max_range_double_spin_box->setEnabled(!checked);
376 }
377 
378 void ImageCropper::callbackImage(const sensor_msgs::Image::ConstPtr& img, const sensor_msgs::CameraInfoConstPtr& ci)
379 {
380  if(!selected_)
381  {
382 
383  sens_msg_image_ = img;
384  camera_info_ = ci;
385 
386  try
387  {
388  // First let cv_bridge do its magic
390  conversion_mat_ = cv_ptr->image;
391  }
392  catch (cv_bridge::Exception& e)
393  {
394  // If we're here, there is no conversion that makes sense, but let's try to imagine a few first
396  if (img->encoding == "CV_8UC3")
397  {
398  // assuming it is rgb
399  conversion_mat_ = cv_ptr->image;
400  } else if (img->encoding == "8UC1") {
401  // convert gray to rgb
402  cv::cvtColor(cv_ptr->image, conversion_mat_, CV_GRAY2RGB);
403  } else if (img->encoding == "16UC1" || img->encoding == "32FC1") {
404  // scale / quantify
405  image_min_value_ = 0;
406  image_max_value_ = ui_.max_range_double_spin_box->value();
407  if (img->encoding == "16UC1") image_max_value_ *= 1000;
408  if (ui_.dynamic_range_check_box->isChecked())
409  {
410  // dynamically adjust range based on min/max in image
411  cv::minMaxLoc(cv_ptr->image, &image_min_value_, &image_max_value_);
413  // completely homogeneous images are displayed in gray
414  image_min_value_ = 0;
415  image_max_value_ = 2;
416  }
417  }
418  cv::Mat img_scaled_8u;
419  cv::Mat(cv_ptr->image-image_min_value_).convertTo(img_scaled_8u, CV_8UC1, 255. / (image_max_value_ - image_min_value_));
420  cv::cvtColor(img_scaled_8u, conversion_mat_, CV_GRAY2RGB);
421  } else {
422  qWarning("ImageCropper.callback_image() could not convert image from '%s' to 'rgb8' (%s)", img->encoding.c_str(), e.what());
423  qimage_ = QImage();
424  return;
425  }
426  }
427 
428  // copy temporary image as it uses the conversion_mat_ for storage which is asynchronously overwritten in the next callback invocation
429  QImage image(conversion_mat_.data, conversion_mat_.cols, conversion_mat_.rows, QImage::Format_RGB888);
430  qimage_mutex_.lock();
431  qimage_ = image.copy();
432  qimage_mutex_.unlock();
433 
434  ui_.image_frame->setAspectRatio(qimage_.width(), qimage_.height());
435  //onZoom1(false);
436 
437  ui_.image_frame->setInnerFrameMinimumSize(QSize(80, 60));
438  ui_.image_frame->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
439  widget_->setMinimumSize(QSize(80, 60));
440  widget_->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
441  }
442 }
443 
445 {
446 
447 }
448 
449 }
450 
CvImageConstPtr toCvShare(const sensor_msgs::ImageConstPtr &source, const std::string &encoding=std::string())
sensor_msgs::Image::ConstPtr sens_msg_image_
void publish(const sensor_msgs::Image &image, const sensor_msgs::CameraInfo &info) const
image_transport::CameraSubscriber subscriber_
virtual void selectTopic(const QString &topic)
virtual void onInTopicChanged(int index)
CameraSubscriber subscribeCamera(const std::string &base_topic, uint32_t queue_size, const CameraSubscriber::Callback &callback, const ros::VoidPtr &tracked_object=ros::VoidPtr(), const TransportHints &transport_hints=TransportHints())
virtual void onDynamicRange(bool checked)
uint32_t getNumSubscribers() const
virtual QList< QString > getTopicList(const QSet< QString > &message_types, const QList< QString > &transports)
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
CameraPublisher advertiseCamera(const std::string &base_topic, uint32_t queue_size, bool latch=false)
virtual bool eventFilter(QObject *watched, QEvent *event)
virtual void onSelectionFinished(QPoint p1, QPoint p2)
std::vector< std::string > getDeclaredTransports() const
virtual void callbackImage(const sensor_msgs::Image::ConstPtr &msg, const sensor_msgs::CameraInfoConstPtr &ci)
image_transport::CameraPublisher publisher_
virtual void onSelectionInProgress(QPoint p1, QPoint p2)
virtual void initPlugin(qt_gui_cpp::PluginContext &context)
void setValue(const QString &key, const QVariant &value)
virtual void saveSettings(qt_gui_cpp::Settings &plugin_settings, qt_gui_cpp::Settings &instance_settings) const
ros::NodeHandle & getNodeHandle() const
Ui::ImageCropperWidget ui_
virtual void restoreSettings(const qt_gui_cpp::Settings &plugin_settings, const qt_gui_cpp::Settings &instance_settings)
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
sensor_msgs::CameraInfoConstPtr camera_info_
virtual void enforceSelectionConstraints(QPoint &p)


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