Skip to content

Commit 05a6b00

Browse files
committed
Feature/better object viz (#38)
* publish all labels in same array * clean up handling of visualization images w.r.t. sensors * use display config for tracking image also
1 parent 4ef58d6 commit 05a6b00

File tree

6 files changed

+137
-75
lines changed

6 files changed

+137
-75
lines changed

hydra_ros/include/hydra_ros/active_window/reconstruction_visualizer.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* -------------------------------------------------------------------------- */
3535
#pragma once
3636
#include <hydra/active_window/active_window_module.h>
37+
#include <hydra/input/sensor_map.h>
3738
#include <hydra_visualizer/adapters/mesh_color.h>
3839
#include <hydra_visualizer/color/colormap_utilities.h>
3940
#include <hydra_visualizer/utils/marker_group_pub.h>
@@ -51,6 +52,12 @@ namespace hydra {
5152

5253
class ReconstructionVisualizer : public ActiveWindowModule::Sink {
5354
public:
55+
struct SensorDisplay {
56+
using Config = DisplayConfig;
57+
const Config config;
58+
explicit SensorDisplay(const Config& config) : config(config) {}
59+
};
60+
5461
struct Config {
5562
std::string ns = "~/reconstruction";
5663
double min_weight = 0.0;
@@ -70,7 +77,7 @@ class ReconstructionVisualizer : public ActiveWindowModule::Sink {
7077
visualizer::RangeColormap::Config colormap;
7178
visualizer::CategoricalColormap::Config label_colormap;
7279
config::VirtualConfig<MeshColoring> mesh_coloring;
73-
DisplayConfig image_display;
80+
SensorMap<SensorDisplay>::Config sensor_displays;
7481
} const config;
7582

7683
explicit ReconstructionVisualizer(const Config& config);
@@ -90,6 +97,7 @@ class ReconstructionVisualizer : public ActiveWindowModule::Sink {
9097
MarkerGroupPub pubs_;
9198
rclcpp::Publisher<kimera_pgmo_msgs::msg::Mesh>::SharedPtr active_mesh_pub_;
9299
rclcpp::Publisher<geometry_msgs::msg::PoseStamped>::SharedPtr pose_pub_;
100+
SensorMap<SensorDisplay> sensor_displays_;
93101
ianvs::RosPublisherGroup<sensor_msgs::msg::Image> image_pubs_;
94102
ianvs::RosPublisherGroup<sensor_msgs::msg::PointCloud2> cloud_pubs_;
95103
const visualizer::RangeColormap colormap_;

hydra_ros/include/hydra_ros/frontend/object_visualizer.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
#include <ianvs/lazy_publisher_group.h>
3838
#include <kimera_pgmo/mesh_delta.h>
3939

40-
#include <visualization_msgs/msg/marker.hpp>
40+
#include <visualization_msgs/msg/marker_array.hpp>
4141

4242
namespace hydra {
4343

@@ -68,7 +68,7 @@ class ObjectVisualizer : public MeshSegmenter::Sink {
6868

6969
protected:
7070
ianvs::NodeHandle nh_;
71-
ianvs::RosPublisherGroup<visualization_msgs::msg::Marker> pubs_;
71+
ianvs::RosPublisherGroup<visualization_msgs::msg::MarkerArray> pubs_;
7272

7373
private:
7474
inline static const auto registration_ =

hydra_ros/include/hydra_ros/utils/input_data_to_messages.h

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,46 +33,59 @@
3333
* purposes notwithstanding any copyright notation herein.
3434
* -------------------------------------------------------------------------- */
3535
#pragma once
36+
#include <hydra_visualizer/color/colormap_utilities.h>
3637
#include <spark_dsg/color.h>
3738

3839
#include <functional>
40+
#include <limits>
3941

42+
#include <opencv2/core/mat.hpp>
4043
#include <sensor_msgs/msg/image.hpp>
4144
#include <sensor_msgs/msg/point_cloud2.hpp>
4245

4346
namespace hydra {
4447

4548
struct InputData;
46-
using CmapFunc = std::function<spark_dsg::Color(uint32_t)>;
49+
using CmapFunc = std::function<spark_dsg::Color(cv::Mat, int, int)>;
4750

4851
struct DisplayConfig {
4952
float width_scale = 1.0f;
5053
float height_scale = 1.0f;
54+
float overlay_alpha = 0.5f;
55+
float min_distance = -1.0f;
56+
float max_distance = -1.0f;
57+
visualizer::RangeColormap::Config distance_colormap;
5158
};
5259

5360
void declare_config(DisplayConfig& config);
5461

62+
/**
63+
* @brief Turn an image into a message, optionally applying display config
64+
*/
65+
sensor_msgs::msg::Image::SharedPtr convertImage(const std_msgs::msg::Header& header,
66+
const cv::Mat& img,
67+
const DisplayConfig& config);
68+
5569
/**
5670
* @brief Make a colored image for the current labels in the input data
5771
*/
5872
sensor_msgs::msg::Image::SharedPtr makeImage(const std_msgs::msg::Header& header,
59-
const InputData& sensor_data,
73+
const cv::Mat& img_in,
6074
const CmapFunc& colormap,
6175
const DisplayConfig& config = {});
6276

6377
/**
64-
* @brief Copy the current depth image to ros
78+
* @brief Make a colored image for the current labels in the input data
6579
*/
66-
sensor_msgs::msg::Image::SharedPtr makeDepthImage(const std_msgs::msg::Header& header,
67-
const InputData& sensor_data,
68-
const DisplayConfig& config = {});
80+
sensor_msgs::msg::Image::SharedPtr makeOverlayImage(const std_msgs::msg::Header& header,
81+
const cv::Mat& img_in,
82+
const cv::Mat& color_in,
83+
const CmapFunc& colormap,
84+
const DisplayConfig& config = {});
6985

70-
/**
71-
* @brief Copy the current range image to ros
72-
*/
73-
sensor_msgs::msg::Image::SharedPtr makeRangeImage(const std_msgs::msg::Header& header,
74-
const InputData& sensor_data,
75-
const DisplayConfig& config = {});
86+
sensor_msgs::msg::Image::SharedPtr makeDistImage(const std_msgs::msg::Header& header,
87+
const cv::Mat& img_in,
88+
const DisplayConfig& config = {});
7689

7790
/**
7891
* @brief Convert the input pointcloud to a ROS type

hydra_ros/src/active_window/reconstruction_visualizer.cpp

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ void declare_config(ReconstructionVisualizer::Config& config) {
9999
field(config.filter_points_by_range, "filter_points_by_range");
100100
field(config.colormap, "colormap");
101101
field(config.label_colormap, "label_colormap");
102-
field(config.image_display, "image_display");
102+
field(config.sensor_displays, "sensor_displays");
103103
config.mesh_coloring.setOptional();
104104
field(config.mesh_coloring, "mesh_coloring");
105105
}
@@ -110,6 +110,7 @@ ReconstructionVisualizer::ReconstructionVisualizer(const Config& config)
110110
pubs_(nh_),
111111
active_mesh_pub_(nh_.create_publisher<kimera_pgmo_msgs::msg::Mesh>("mesh", 1)),
112112
pose_pub_(nh_.create_publisher<geometry_msgs::msg::PoseStamped>("pose", 10)),
113+
sensor_displays_(config.sensor_displays),
113114
image_pubs_(nh_),
114115
cloud_pubs_(nh_),
115116
colormap_(config.colormap),
@@ -182,24 +183,36 @@ void ReconstructionVisualizer::call(uint64_t timestamp_ns,
182183
std_msgs::msg::Header header;
183184
header.stamp = rclcpp::Time(output.timestamp_ns);
184185
header.frame_id = GlobalInfo::instance().getFrames().map;
185-
186186
const auto sensor_name = output.sensor_data->getSensor().name;
187+
188+
DisplayConfig display_config;
189+
const auto display = sensor_displays_.get(sensor_name);
190+
if (display) {
191+
display_config = display->config;
192+
}
193+
187194
image_pubs_.publish(sensor_name + "/labels", [&]() {
188-
return makeImage(
195+
return makeOverlayImage(
189196
header,
190-
*output.sensor_data,
191-
[this](uint32_t label) { return label_colormap_(label); },
192-
config.image_display);
193-
});
194-
image_pubs_.publish(sensor_name + "/depth", [&]() {
195-
return makeDepthImage(header, *output.sensor_data, config.image_display);
197+
output.sensor_data->label_image,
198+
output.sensor_data->color_image,
199+
[this](const cv::Mat& img, int r, int c) {
200+
return label_colormap_(img.at<int32_t>(r, c));
201+
},
202+
display_config);
196203
});
197204
image_pubs_.publish(sensor_name + "/range", [&]() {
198-
return makeRangeImage(header, *output.sensor_data, config.image_display);
205+
return makeDistImage(header, output.sensor_data->range_image, display_config);
199206
});
200207
cloud_pubs_.publish(sensor_name + "/pointcloud", [&]() {
201208
return makeCloud(header, *output.sensor_data, config.filter_points_by_range);
202209
});
210+
211+
if (!output.sensor_data->depth_image.empty()) {
212+
image_pubs_.publish(sensor_name + "/depth", [&]() {
213+
return makeDistImage(header, output.sensor_data->depth_image, display_config);
214+
});
215+
}
203216
}
204217
} // namespace hydra
205218

hydra_ros/src/frontend/object_visualizer.cpp

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
namespace hydra {
4343

4444
using visualization_msgs::msg::Marker;
45+
using visualization_msgs::msg::MarkerArray;
4546

4647
void declare_config(ObjectVisualizer::Config& config) {
4748
using namespace config;
@@ -61,30 +62,21 @@ std::string ObjectVisualizer::printInfo() const { return config::toString(config
6162

6263
void ObjectVisualizer::call(uint64_t timestamp_ns,
6364
const kimera_pgmo::MeshDelta& delta,
64-
const std::vector<size_t>& active,
65+
const std::vector<size_t>& /* active */,
6566
const LabelIndices& label_indices) const {
66-
pubs_.publish("active_vertices", [&]() {
67-
auto msg = std::make_unique<Marker>();
68-
msg->header.stamp = rclcpp::Time(timestamp_ns);
69-
msg->header.frame_id = GlobalInfo::instance().getFrames().odom;
70-
msg->ns = "active_vertices";
71-
msg->id = 0;
72-
fillMarkerFromCloud(delta, active, *msg);
73-
return msg;
74-
});
67+
pubs_.publish("object_vertices", [&]() {
68+
auto msg_arr = std::make_unique<MarkerArray>();
69+
for (const auto& [label, indices] : label_indices) {
70+
auto& msg = msg_arr->markers.emplace_back();
71+
msg.header.stamp = rclcpp::Time(timestamp_ns);
72+
msg.header.frame_id = GlobalInfo::instance().getFrames().odom;
73+
msg.ns = "label_vertices_" + std::to_string(label);
74+
msg.id = 0;
75+
fillMarkerFromCloud(delta, indices, msg);
76+
}
7577

76-
for (const auto& id_label_pair : label_indices) {
77-
pubs_.publish("object_vertices/label" + std::to_string(id_label_pair.first), [&]() {
78-
const auto& [label, indices] = id_label_pair;
79-
auto msg = std::make_unique<Marker>();
80-
msg->header.stamp = rclcpp::Time(timestamp_ns);
81-
msg->header.frame_id = GlobalInfo::instance().getFrames().odom;
82-
msg->ns = "label_vertices_" + std::to_string(label);
83-
msg->id = 0;
84-
fillMarkerFromCloud(delta, indices, *msg);
85-
return msg;
86-
});
87-
}
78+
return msg_arr;
79+
});
8880
}
8981

9082
void ObjectVisualizer::fillMarkerFromCloud(const kimera_pgmo::MeshDelta& delta,

hydra_ros/src/utils/input_data_to_messages.cpp

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ PointField makeField(const std::string& name,
6060
return field;
6161
}
6262

63-
cv::Mat showImage(const cv::Mat& input, const DisplayConfig& config) {
63+
cv::Mat resizeImage(const cv::Mat& input, const DisplayConfig& config) {
6464
if (config.width_scale == 1.0f && config.height_scale == 1.0f) {
6565
return input;
6666
}
@@ -82,49 +82,85 @@ void declare_config(DisplayConfig& config) {
8282
name("DisplayConfig");
8383
field(config.width_scale, "width_scale");
8484
field(config.height_scale, "height_scale");
85+
field(config.overlay_alpha, "overlay_alpha");
86+
field(config.min_distance, "min_distance", "m");
87+
field(config.max_distance, "max_distance", "m");
88+
field(config.distance_colormap, "distance_colormap", "m");
89+
// overlay == 0.0 doesn't make sense, but overlay == 1.0 does
90+
checkInRange(config.overlay_alpha, 0.0f, 1.0f, "overlay_alpha", false, true);
8591
}
8692

87-
Image::SharedPtr makeImage(const std_msgs::msg::Header& header,
88-
const InputData& sensor_data,
89-
const CmapFunc& colormap,
90-
const DisplayConfig& config) {
91-
const auto& labels = sensor_data.label_image;
93+
Image::SharedPtr convertImage(const std_msgs::msg::Header& header,
94+
const cv::Mat& img,
95+
const DisplayConfig& config) {
9296
cv_bridge::CvImagePtr msg(new cv_bridge::CvImage());
9397
msg->header = header;
9498
msg->encoding = "rgb8";
95-
cv::Mat img(labels.rows, labels.cols, CV_8UC3);
96-
for (int r = 0; r < labels.rows; ++r) {
97-
for (int c = 0; c < labels.cols; ++c) {
99+
msg->image = resizeImage(img, config);
100+
return msg->toImageMsg();
101+
}
102+
103+
Image::SharedPtr makeImage(const std_msgs::msg::Header& header,
104+
const cv::Mat& img_in,
105+
const CmapFunc& colormap,
106+
const DisplayConfig& config) {
107+
cv::Mat img(img_in.rows, img_in.cols, CV_8UC3);
108+
for (int r = 0; r < img_in.rows; ++r) {
109+
for (int c = 0; c < img_in.cols; ++c) {
98110
auto pixel = img.ptr<uint8_t>(r, c);
99-
const auto color = colormap(labels.at<int>(r, c));
111+
const auto color = colormap(img_in, r, c);
100112
*pixel = color.r;
101113
*(pixel + 1) = color.g;
102114
*(pixel + 2) = color.b;
103115
}
104116
}
105117

106-
msg->image = showImage(img, config);
107-
return msg->toImageMsg();
118+
return convertImage(header, img, config);
108119
}
109120

110-
Image::SharedPtr makeDepthImage(const std_msgs::msg::Header& header,
111-
const InputData& sensor_data,
112-
const DisplayConfig& config) {
113-
cv_bridge::CvImagePtr msg(new cv_bridge::CvImage());
114-
msg->header = header;
115-
msg->encoding = "32FC1";
116-
msg->image = showImage(sensor_data.depth_image, config);
117-
return msg->toImageMsg();
121+
Image::SharedPtr makeOverlayImage(const std_msgs::msg::Header& header,
122+
const cv::Mat& img_in,
123+
const cv::Mat& color_in,
124+
const CmapFunc& colormap,
125+
const DisplayConfig& config) {
126+
if (color_in.empty()) {
127+
return makeImage(header, img_in, colormap, config);
128+
}
129+
130+
cv::Mat img(img_in.rows, img_in.cols, CV_8UC3);
131+
for (int r = 0; r < img_in.rows; ++r) {
132+
for (int c = 0; c < img_in.cols; ++c) {
133+
auto pixel = img.ptr<uint8_t>(r, c);
134+
const auto c_in = colormap(img_in, r, c);
135+
const auto c_cv = color_in.at<cv::Vec3b>(r, c);
136+
const auto color =
137+
c_in.blend(spark_dsg::Color(c_cv[0], c_cv[1], c_cv[2]), config.overlay_alpha);
138+
139+
*pixel = color.r;
140+
*(pixel + 1) = color.g;
141+
*(pixel + 2) = color.b;
142+
}
143+
}
144+
145+
return convertImage(header, img, config);
118146
}
119147

120-
Image::SharedPtr makeRangeImage(const std_msgs::msg::Header& header,
121-
const InputData& sensor_data,
122-
const DisplayConfig& config) {
123-
cv_bridge::CvImagePtr msg(new cv_bridge::CvImage());
124-
msg->header = header;
125-
msg->encoding = "32FC1";
126-
msg->image = showImage(sensor_data.range_image, config);
127-
return msg->toImageMsg();
148+
Image::SharedPtr makeDistImage(const std_msgs::msg::Header& header,
149+
const cv::Mat& distances,
150+
const DisplayConfig& config) {
151+
double min_v = 0.0;
152+
double max_v = std::numeric_limits<double>::infinity();
153+
cv::minMaxIdx(distances, &min_v, &max_v);
154+
const float v_min = config.min_distance >= 0.0f ? config.min_distance : min_v;
155+
const float v_max = config.max_distance >= 0.0f ? config.max_distance : max_v;
156+
const visualizer::RangeColormap cmap(config.distance_colormap);
157+
return makeImage(
158+
header,
159+
distances,
160+
[&](const cv::Mat& img, int r, int c) {
161+
return cmap(img.at<float>(r, c), v_min, v_max);
162+
},
163+
config);
128164
}
129165

130166
// TODO(nathan) pcl_ros would avoid this but it causes compile issues (and would also

0 commit comments

Comments
 (0)