Skip to content

Commit b9b474a

Browse files
add specialized subscriber for labels as colors (#72)
* add specialized subscriber for labels as colors * make default label configurable * also add color conversion utility node
1 parent 1a204f6 commit b9b474a

4 files changed

Lines changed: 284 additions & 13 deletions

File tree

hydra_ros/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ endif()
1212
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
1313

1414
find_package(ament_cmake REQUIRED)
15+
find_package(CLI11 REQUIRED)
1516
find_package(cv_bridge REQUIRED)
1617
find_package(geometry_msgs REQUIRED)
1718
find_package(gflags REQUIRED)
@@ -92,6 +93,9 @@ ament_target_dependencies(
9293
add_executable(hydra_ros_node app/hydra_node.cpp)
9394
target_link_libraries(hydra_ros_node ${PROJECT_NAME} ${gflags_LIBRARIES})
9495

96+
add_executable(color_to_label_node app/color_to_label_node.cpp)
97+
target_link_libraries(color_to_label_node ${PROJECT_NAME} cv_bridge::cv_bridge CLI11::CLI11)
98+
9599
rclcpp_components_register_node(
96100
${PROJECT_NAME}
97101
PLUGIN
@@ -113,7 +117,7 @@ install(
113117
LIBRARY DESTINATION lib
114118
RUNTIME DESTINATION lib/${PROJECT_NAME}
115119
)
116-
install(TARGETS hydra_ros_node RUNTIME DESTINATION lib/${PROJECT_NAME})
120+
install(TARGETS color_to_label_node hydra_ros_node RUNTIME DESTINATION lib/${PROJECT_NAME})
117121
install(
118122
PROGRAMS app/dsg_file_publisher
119123
app/dsg_republisher
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/* -----------------------------------------------------------------------------
2+
* Copyright 2022 Massachusetts Institute of Technology.
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 are met:
7+
*
8+
* 1. Redistributions of source code must retain the above copyright notice,
9+
* this list of conditions and the following disclaimer.
10+
*
11+
* 2. Redistributions in binary form must reproduce the above copyright notice,
12+
* this list of conditions and the following disclaimer in the documentation
13+
* and/or other materials provided with the distribution.
14+
*
15+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
*
26+
* Research was sponsored by the United States Air Force Research Laboratory and
27+
* the United States Air Force Artificial Intelligence Accelerator and was
28+
* accomplished under Cooperative Agreement Number FA8750-19-2-1000. The views
29+
* and conclusions contained in this document are those of the authors and should
30+
* not be interpreted as representing the official policies, either expressed or
31+
* implied, of the United States Air Force or the U.S. Government. The U.S.
32+
* Government is authorized to reproduce and distribute reprints for Government
33+
* purposes notwithstanding any copyright notation herein.
34+
* -------------------------------------------------------------------------- */
35+
#include <hydra/common/semantic_color_map.h>
36+
#include <ianvs/spin_functions.h>
37+
38+
#include <filesystem>
39+
40+
#include <CLI/CLI.hpp>
41+
#include <cv_bridge/cv_bridge.hpp>
42+
#include <cv_bridge/cv_mat_sensor_msgs_image_type_adapter.hpp>
43+
#include <sensor_msgs/msg/image.hpp>
44+
45+
namespace hydra {
46+
47+
using sensor_msgs::msg::Image;
48+
using ImageAdapter = rclcpp::TypeAdapter<cv_bridge::ROSCvMatContainer, Image>;
49+
50+
struct ColorToLabelNode : public rclcpp::Node {
51+
explicit ColorToLabelNode(const std::filesystem::path& colormap_path,
52+
size_t queue_size = 1);
53+
54+
void callback(const std_msgs::msg::Header& header, const cv::Mat& msg);
55+
56+
int32_t default_label;
57+
std::unique_ptr<SemanticColorMap> colormap;
58+
rclcpp::Publisher<Image>::SharedPtr pub;
59+
rclcpp::Subscription<ImageAdapter>::SharedPtr sub;
60+
};
61+
62+
ColorToLabelNode::ColorToLabelNode(const std::filesystem::path& colormap_path,
63+
size_t queue_size)
64+
: Node("color_to_label_node"),
65+
default_label(-1),
66+
colormap(SemanticColorMap::fromCsv(colormap_path)),
67+
pub(create_publisher<Image>("labels", queue_size)) {
68+
const auto cb = [this](const cv_bridge::ROSCvMatContainer& container) {
69+
callback(container.header(), container.cv_mat());
70+
};
71+
72+
sub = create_subscription<ImageAdapter>("colors", queue_size, cb);
73+
}
74+
75+
void ColorToLabelNode::callback(const std_msgs::msg::Header& header,
76+
const cv::Mat& img) {
77+
if (!colormap) {
78+
RCLCPP_ERROR_STREAM(get_logger(), "Colormap is required!");
79+
return;
80+
}
81+
82+
if (img.empty() || img.channels() != 3) {
83+
RCLCPP_ERROR_STREAM(get_logger(), "Failed to decode color image to semantics!");
84+
return;
85+
}
86+
87+
cv::Mat labels(img.size(), CV_32SC1);
88+
for (int r = 0; r < img.rows; ++r) {
89+
for (int c = 0; c < img.cols; ++c) {
90+
const auto& pixel = img.at<cv::Vec3b>(r, c);
91+
const spark_dsg::Color color(pixel[0], pixel[1], pixel[2]);
92+
labels.at<int32_t>(r, c) =
93+
colormap->getLabelFromColor(color).value_or(default_label);
94+
}
95+
}
96+
97+
cv_bridge::CvImage to_pub(header, "32SC1", labels);
98+
const auto msg = to_pub.toImageMsg();
99+
pub->publish(*msg);
100+
}
101+
102+
} // namespace hydra
103+
104+
struct AppArgs {
105+
void add_to_app(CLI::App& app);
106+
107+
std::filesystem::path colormap_path;
108+
int32_t default_label = -1;
109+
};
110+
111+
void AppArgs::add_to_app(CLI::App& app) {
112+
app.add_option("colormap_path", colormap_path)
113+
->required()
114+
->check(CLI::ExistingPath)
115+
->description("Colormap to use");
116+
app.add_option("-l,--default-label", default_label);
117+
}
118+
119+
int main(int argc, char* argv[]) {
120+
CLI::App app("Utility node to convert labels stored by color to integer values");
121+
argv = app.ensure_utf8(argv);
122+
app.allow_extras();
123+
app.get_formatter()->column_width(50);
124+
125+
AppArgs args;
126+
args.add_to_app(app);
127+
try {
128+
app.parse(argc, argv);
129+
} catch (const CLI::ParseError& e) {
130+
return app.exit(e);
131+
}
132+
133+
rclcpp::init(argc, argv);
134+
{ // scope for node
135+
hydra::ColorToLabelNode remapper(args.colormap_path);
136+
ianvs::NodeHandle nh(remapper);
137+
ianvs::spinAndWait(nh);
138+
}
139+
140+
rclcpp::shutdown();
141+
return 0;
142+
}

hydra_ros/include/hydra_ros/input/image_receiver.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* purposes notwithstanding any copyright notation herein.
3434
* -------------------------------------------------------------------------- */
3535
#pragma once
36+
#include <hydra/common/semantic_color_map.h>
3637
#include <ianvs/node_handle.h>
3738
#include <message_filters/subscriber.h>
3839
#include <message_filters/sync_policies/approximate_time.h>
@@ -104,6 +105,25 @@ struct LabelSubscriber {
104105
std::shared_ptr<FilterSub<sensor_msgs::msg::Image>> impl_;
105106
};
106107

108+
struct ColormappedLabelSubscriber {
109+
public:
110+
using MsgType = sensor_msgs::msg::Image;
111+
using Filter = message_filters::SimpleFilter<MsgType>;
112+
113+
ColormappedLabelSubscriber();
114+
explicit ColormappedLabelSubscriber(ianvs::NodeHandle nh, uint32_t queue_size = 1);
115+
virtual ~ColormappedLabelSubscriber();
116+
117+
Filter& getFilter() const;
118+
void setColormap(const SemanticColorMap* colormap, int32_t default_label);
119+
void fillInput(const sensor_msgs::msg::Image& img, ImageInputPacket& packet) const;
120+
121+
private:
122+
int32_t default_label_;
123+
const SemanticColorMap* colormap_;
124+
std::shared_ptr<FilterSub<sensor_msgs::msg::Image>> impl_;
125+
};
126+
107127
struct FeatureSubscriber {
108128
public:
109129
using MsgType = semantic_inference_msgs::msg::FeatureImage;
@@ -213,6 +233,8 @@ class ClosedSetImageReceiver : public ImageReceiverImpl<LabelSubscriber> {
213233
virtual ~ClosedSetImageReceiver() = default;
214234
};
215235

236+
void declare_config(ClosedSetImageReceiver::Config& config);
237+
216238
class OpenSetImageReceiver : public ImageReceiverImpl<FeatureSubscriber> {
217239
public:
218240
struct Config : RosDataReceiver::Config {};
@@ -222,4 +244,24 @@ class OpenSetImageReceiver : public ImageReceiverImpl<FeatureSubscriber> {
222244

223245
void declare_config(OpenSetImageReceiver::Config& config);
224246

247+
class ColormappedLabelImageReceiver
248+
: public ImageReceiverImpl<ColormappedLabelSubscriber> {
249+
public:
250+
struct Config : RosDataReceiver::Config {
251+
//! Path to colormap CSV to use to remap colors to labels
252+
std::filesystem::path colormap_path;
253+
//! Label value to use when value is unknown
254+
int32_t default_label = -1;
255+
} const config;
256+
257+
ColormappedLabelImageReceiver(const Config& config, const std::string& sensor_name);
258+
virtual ~ColormappedLabelImageReceiver() = default;
259+
bool initImpl() override;
260+
261+
private:
262+
std::unique_ptr<SemanticColorMap> colormap_;
263+
};
264+
265+
void declare_config(ColormappedLabelImageReceiver::Config& config);
266+
225267
} // namespace hydra

hydra_ros/src/input/image_receiver.cpp

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include "hydra_ros/input/image_receiver.h"
3636

3737
#include <config_utilities/config.h>
38+
#include <config_utilities/types/path.h>
39+
#include <config_utilities/validation.h>
3840
#include <glog/logging.h>
3941

4042
#include <cv_bridge/cv_bridge.hpp>
@@ -120,6 +122,58 @@ void LabelSubscriber::fillInput(const Image& img, ImageInputPacket& packet) cons
120122
}
121123
}
122124

125+
ColormappedLabelSubscriber::ColormappedLabelSubscriber()
126+
: default_label_(-1), colormap_(nullptr) {}
127+
128+
ColormappedLabelSubscriber::ColormappedLabelSubscriber(ianvs::NodeHandle nh,
129+
uint32_t queue_size)
130+
: default_label_(-1),
131+
colormap_(nullptr),
132+
impl_(std::make_shared<FilterSub<Image>>(nh, "semantic/image_raw", queue_size)) {}
133+
134+
ColormappedLabelSubscriber::~ColormappedLabelSubscriber() = default;
135+
136+
ColormappedLabelSubscriber::Filter& ColormappedLabelSubscriber::getFilter() const {
137+
return *CHECK_NOTNULL(impl_);
138+
}
139+
140+
void ColormappedLabelSubscriber::setColormap(const SemanticColorMap* colormap,
141+
int32_t default_label) {
142+
colormap_ = colormap;
143+
default_label_ = default_label;
144+
}
145+
146+
void ColormappedLabelSubscriber::fillInput(const Image& img,
147+
ImageInputPacket& packet) const {
148+
if (!colormap_) {
149+
LOG(ERROR) << "Colormap not set for subscriber!";
150+
return;
151+
}
152+
153+
cv::Mat colors;
154+
try {
155+
colors = cv_bridge::toCvCopy(img)->image;
156+
} catch (const cv_bridge::Exception& e) {
157+
LOG(ERROR) << "Failed to convert label image: " << e.what();
158+
return;
159+
}
160+
161+
if (colors.empty() || colors.channels() != 3) {
162+
LOG(ERROR) << "Failed to decode color image to semantics!";
163+
return;
164+
}
165+
166+
packet.labels = cv::Mat(colors.size(), CV_32SC1);
167+
for (int r = 0; r < colors.rows; ++r) {
168+
for (int c = 0; c < colors.cols; ++c) {
169+
const auto& pixel = colors.at<cv::Vec3b>(r, c);
170+
const spark_dsg::Color color(pixel[0], pixel[1], pixel[2]);
171+
packet.labels.at<int32_t>(r, c) =
172+
colormap_->getLabelFromColor(color).value_or(default_label_);
173+
}
174+
}
175+
}
176+
123177
FeatureSubscriber::FeatureSubscriber() = default;
124178

125179
FeatureSubscriber::FeatureSubscriber(ianvs::NodeHandle nh, uint32_t queue_size)
@@ -148,12 +202,6 @@ void FeatureSubscriber::fillInput(const MsgType& msg, ImageInputPacket& packet)
148202
}
149203
}
150204

151-
void declare_config(RGBDImageReceiver::Config& config) {
152-
using namespace config;
153-
name("RGBDImageReceiver::Config");
154-
base<RosDataReceiver::Config>(config);
155-
}
156-
157205
RGBDImageReceiver::RGBDImageReceiver(const Config& config,
158206
const std::string& sensor_name)
159207
: RosDataReceiver(config, sensor_name) {}
@@ -180,26 +228,56 @@ void RGBDImageReceiver::callback(const sensor_msgs::msg::Image::ConstSharedPtr&
180228
queue.push(packet);
181229
}
182230

183-
void declare_config(ClosedSetImageReceiver::Config& config) {
231+
void declare_config(RGBDImageReceiver::Config& config) {
184232
using namespace config;
185-
name("ClosedSetImageReceiver::Config");
233+
name("RGBDImageReceiver::Config");
186234
base<RosDataReceiver::Config>(config);
187235
}
188236

189237
ClosedSetImageReceiver::ClosedSetImageReceiver(const Config& config,
190238
const std::string& sensor_name)
191239
: ImageReceiverImpl<LabelSubscriber>(config, sensor_name) {}
192240

193-
void declare_config(OpenSetImageReceiver::Config& config) {
241+
void declare_config(ClosedSetImageReceiver::Config& config) {
194242
using namespace config;
195-
name("OpenSetImageReceiver::Config");
196-
base<hydra::RosDataReceiver::Config>(config);
243+
name("ClosedSetImageReceiver::Config");
244+
base<RosDataReceiver::Config>(config);
197245
}
198246

199247
OpenSetImageReceiver::OpenSetImageReceiver(const Config& config,
200248
const std::string& sensor_name)
201249
: ImageReceiverImpl<FeatureSubscriber>(config, sensor_name) {}
202250

251+
void declare_config(OpenSetImageReceiver::Config& config) {
252+
using namespace config;
253+
name("OpenSetImageReceiver::Config");
254+
base<hydra::RosDataReceiver::Config>(config);
255+
}
256+
257+
ColormappedLabelImageReceiver::ColormappedLabelImageReceiver(const Config& config,
258+
const std::string& name)
259+
: ImageReceiverImpl<ColormappedLabelSubscriber>(config, name),
260+
config(config::checkValid(config)),
261+
colormap_(SemanticColorMap::fromCsv(config.colormap_path)) {
262+
CHECK(colormap_) << "Colormap required!";
263+
}
264+
265+
bool ColormappedLabelImageReceiver::initImpl() {
266+
using Base = ImageReceiverImpl<ColormappedLabelSubscriber>;
267+
const auto ret = Base::initImpl();
268+
semantic_sub_.setColormap(colormap_.get(), config.default_label);
269+
return ret;
270+
}
271+
272+
void declare_config(ColormappedLabelImageReceiver::Config& config) {
273+
using namespace config;
274+
name("ColormappedLabelImageReceiver::Config");
275+
base<hydra::RosDataReceiver::Config>(config);
276+
field<Path::Absolute>(config.colormap_path, "colormap_path");
277+
field(config.default_label, "default_label");
278+
check<Path::Exists>(config.colormap_path, "colormap_path");
279+
}
280+
203281
namespace {
204282

205283
static const auto no_semantic_registration =
@@ -220,6 +298,11 @@ static const auto open_registration =
220298
OpenSetImageReceiver::Config,
221299
std::string>("OpenSetImageReceiver");
222300

223-
} // namespace
301+
static const auto color_registration =
302+
config::RegistrationWithConfig<hydra::DataReceiver,
303+
ColormappedLabelImageReceiver,
304+
ColormappedLabelImageReceiver::Config,
305+
std::string>("ColormappedLabelImageReceiver");
224306

307+
} // namespace
225308
} // namespace hydra

0 commit comments

Comments
 (0)