Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image display with mouse clicking functionality #1737

Merged
1 change: 1 addition & 0 deletions src/rviz/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ add_library(${PROJECT_NAME}
help_panel.cpp
image/ros_image_texture.cpp
image/image_display_base.cpp
image/mouse_click.cpp
loading_dialog.cpp
message_filter_display.h
mesh_loader.cpp
Expand Down
23 changes: 23 additions & 0 deletions src/rviz/default_plugin/image_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ ImageDisplay::ImageDisplay() : ImageDisplayBase(), texture_()
this, SLOT(updateNormalizeOptions()));

got_float_image_ = false;

mouse_click = new MouseClick();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: For deletion, you rely on RenderPanel being mouse_click's parent.
However, this is only set in onInitialize()!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sorry but my cpp is a bit rusty. Like every sane person, I've moved to python a few years ago : - )

What do you suggest we do here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass a parent QObject* to MouseClick's constructor a forward this argument to the base class constructor (see below).

}

void ImageDisplay::onInitialize()
Expand Down Expand Up @@ -130,6 +132,8 @@ void ImageDisplay::onInitialize()
render_panel_->getCamera()->setNearClipDistance(0.01f);

updateNormalizeOptions();

mouse_click->onInitialize(render_panel_);
}

ImageDisplay::~ImageDisplay()
Expand All @@ -145,13 +149,17 @@ ImageDisplay::~ImageDisplay()
void ImageDisplay::onEnable()
{
ImageDisplayBase::subscribe();
mouse_click->publish();

render_panel_->getRenderWindow()->setActive(true);
}

void ImageDisplay::onDisable()
{
render_panel_->getRenderWindow()->setActive(false);
ImageDisplayBase::unsubscribe();
mouse_click->unpublish();

reset();
}

Expand Down Expand Up @@ -211,6 +219,8 @@ void ImageDisplay::update(float wall_dt, float ros_dt)
}

render_panel_->getRenderWindow()->update();

mouse_click->setDimensions(img_width, img_height, win_width, win_height);
}
catch (UnsupportedImageEncoding& e)
{
Expand Down Expand Up @@ -241,6 +251,19 @@ void ImageDisplay::processMessage(const sensor_msgs::Image::ConstPtr& msg)
texture_.addMessage(msg);
}

void ImageDisplay::setTopic(const QString& topic, const QString& datatype)
{
ImageDisplayBase::setTopic(topic, datatype);
mouse_click->setTopic(topic);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to override ImageDisplayBase::setTopic() as this will call updateTopic() anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I will remove this override.

void ImageDisplay::updateTopic()
{
ImageDisplayBase::updateTopic();
mouse_click->updateTopic(topic_property_->getTopic());
}


} // namespace rviz

#include <pluginlib/class_list_macros.hpp>
Expand Down
5 changes: 5 additions & 0 deletions src/rviz/default_plugin/image_display.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

#include "rviz/image/image_display_base.h"
#include "rviz/image/ros_image_texture.h"
#include "rviz/image/mouse_click.h"
#include "rviz/render_panel.h"

#include "rviz/properties/bool_property.h"
Expand Down Expand Up @@ -81,6 +82,8 @@ public Q_SLOTS:

/* This is called by incomingMessage(). */
void processMessage(const sensor_msgs::Image::ConstPtr& msg) override;
void setTopic(const QString& topic, const QString& datatype) override;
void updateTopic() override;

Ogre::SceneManager* img_scene_manager_;

Expand All @@ -98,6 +101,8 @@ public Q_SLOTS:
FloatProperty* max_property_;
IntProperty* median_buffer_size_property_;
bool got_float_image_;

MouseClick* mouse_click;
miguelriemoliveira marked this conversation as resolved.
Show resolved Hide resolved
};

} // namespace rviz
Expand Down
127 changes: 127 additions & 0 deletions src/rviz/image/mouse_click.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include "rviz/image/mouse_click.h"

#include <ros/names.h>


namespace rviz
{

MouseClick::MouseClick() : QObject()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provide a proper parent in the constructor as suggested in #1737 (comment):

Suggested change
MouseClick::MouseClick() : QObject()
MouseClick::MouseClick(QObject* parent) : QObject(parent)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have done this but it had an implication which I am not sure is ok.

Because I only get the parent render_panel_ during the ImageDisplay::onInitialize() I have to call the cnstructor of the MouseClick from there, instead of the ImageDisplay::ImageDisplay() ...

{
}

MouseClick::~MouseClick()
{
}


void MouseClick::onInitialize(QObject* parent)
{
setParent(parent);
parent->installEventFilter(this);

has_dimensions_ = false;
is_topic_name_ok_ = false;
miguelriemoliveira marked this conversation as resolved.
Show resolved Hide resolved
node_handle_.reset(new ros::NodeHandle());
miguelriemoliveira marked this conversation as resolved.
Show resolved Hide resolved
}

void MouseClick::publish()
{
if (is_topic_name_ok_)
{
publisher_.reset(
new ros::Publisher(node_handle_->advertise<geometry_msgs::PointStamped>(topic_, 1000)));
miguelriemoliveira marked this conversation as resolved.
Show resolved Hide resolved
}
}

void MouseClick::unpublish()
{
publisher_.reset();
}


bool MouseClick::eventFilter(QObject* obj, QEvent* event)
{
if (has_dimensions_ == false)
return false; // Cannot compute pixel coordinates.

if (is_topic_name_ok_ == false)
return false; // Cannot publish.


if (event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QPointF windowPos = me->windowPos();

if (img_width_ != 0 && img_height_ != 0 && win_width_ != 0 && win_height_ != 0)
{
float img_aspect = float(img_width_) / float(img_height_);
float win_aspect = float(win_width_) / float(win_height_);

int pix_x = -1;
int pix_y = -1;
if (img_aspect > win_aspect) // Window is taller than the image: black bars over and under image.
{
pix_x = int(float(windowPos.x()) / float(win_width_) * float(img_width_));

int resized_img_height = int(float(win_width_) / float(img_width_) * float(img_height_));
int bias = int((float(win_height_) - float(resized_img_height)) / 2.0);
pix_y = (float(windowPos.y()) - bias) / float(resized_img_height) * float(img_height_);
}
else // Window wider than the image: black bards on the side.
miguelriemoliveira marked this conversation as resolved.
Show resolved Hide resolved
{
pix_y = int(float(windowPos.y()) / float(win_height_) * float(img_height_));

int resized_img_width = int(float(win_height_) / float(img_height_) * float(img_width_));
int bias = int((float(win_width_) - float(resized_img_width)) / 2.0);
pix_x = (float(windowPos.x()) - bias) / float(resized_img_width) * float(img_width_);
}

// Publish if clicked point is inside the image.
if (pix_x > 0 && pix_x < img_width_ && pix_y > 0 && pix_y < img_height_)
miguelriemoliveira marked this conversation as resolved.
Show resolved Hide resolved
{
geometry_msgs::PointStamped point_msgs;
miguelriemoliveira marked this conversation as resolved.
Show resolved Hide resolved
point_msgs.header.stamp = ros::Time::now();
point_msgs.point.x = pix_x;
point_msgs.point.y = pix_y;
publisher_->publish(point_msgs);
}
}
}
return QObject::eventFilter(obj, event);
}

void MouseClick::setDimensions(int img_width, int img_height, int win_width, int win_height)
{
img_width_ = img_width;
img_height_ = img_height;
win_width_ = win_width;
win_height_ = win_height;
has_dimensions_ = true;
}

void MouseClick::setTopic(const QString& image_topic)
{
// Build the click full topic name based on the image topic.
topic_ = image_topic.toStdString().append("/mouse_click");

std::string error;
if (ros::names::validate((const std::string)topic_, error))
{
is_topic_name_ok_ = true;
}
else
{
is_topic_name_ok_ = false;
}
}

void MouseClick::updateTopic(const QString& image_topic)
{
unpublish();
setTopic(image_topic);
publish();
}

} // namespace rviz
61 changes: 61 additions & 0 deletions src/rviz/image/mouse_click.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#ifndef RVIZ_MOUSE_CLICK_H
#define RVIZ_MOUSE_CLICK_H

#include <QObject>

#ifndef Q_MOC_RUN // See: https://bugreports.qt-project.org/browse/QTBUG-22829
#include <iostream>
#include <string>
#include <boost/shared_ptr.hpp>

#include <QMouseEvent>

#include "ros/ros.h"
#include "geometry_msgs/PointStamped.h"
#include "std_msgs/String.h"

#include "rviz/rviz_export.h"
#endif


namespace rviz
{
/** @brief Class for capturing mouse clicks.
*
* This class handles mouse clicking functionalities integrated into the ImageDisplay.
* It uses a qt event filter to capture mouse clicks, handles image resizing and checks if the click was
* inside or outside the image. It also scales the pixel coordinates to get them w.r.t. the image (not
* the window) size. Mouse clicks image pixel coordinates are published as ros geometry_msgs
* PointStamped. The z component is ignored. The topic name where the mouse clicks are published is
* defined created after the subscribed image topic as: <image_topic>/mouse_click.
*/

class RVIZ_EXPORT MouseClick : QObject
{
public:
MouseClick();
~MouseClick();

void onInitialize(QObject* parent);

/** @brief ROS topic management. */
void publish();
void unpublish();

void setDimensions(int img_width, int img_height, int win_width, int win_height);
void setTopic(const QString& image_topic);
void updateTopic(const QString& image_topic);

private:
virtual bool eventFilter(QObject* obj, QEvent* event);

bool has_dimensions_;
int img_width_, img_height_, win_width_, win_height_; // To assess if the clicks are inside the image.
boost::shared_ptr<ros::NodeHandle> node_handle_;
boost::shared_ptr<ros::Publisher> publisher_;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to use pointers here?

Copy link
Contributor Author

@miguelriemoliveira miguelriemoliveira Jul 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its been some years since I've used cpp, but at the time I was using boost pointers over regular pointers whenever possible because I thought they were safer ... not sure if that makes sense tough.

I will set them as regular pointers.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point was to not use pointers at all for these, but regular object instances.

Copy link
Contributor Author

@miguelriemoliveira miguelriemoliveira Aug 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @rhaschke ,

However, for the publisher_ I think I need it to be a pointer so I can create a new publisher on a different topic in these methods:

https://github.com/miguelriemoliveira/rviz/blob/61974117adaf5f632800ccc906fe84d3204d5a93/src/rviz/image/mouse_click.cpp#L28-L40

If the publisher_ is a regular object I don't know how to do this ...

The node_handle_ must also be a pointer so it can be set on the mouse click constructor.

I will leave these two as a pointers for now...

std::string topic_;
bool is_topic_name_ok_;
};

} // namespace rviz
#endif