From b9c66d5b7a649951e629b35fc8787ced14772b4f Mon Sep 17 00:00:00 2001 From: Dan Griffith Date: Thu, 17 Jul 2025 13:46:46 -0400 Subject: [PATCH 1/6] initial commit --- .../semantic_inference/image_resizer.h | 62 ++++++++++++++ semantic_inference/src/image_resizer.cpp | 85 +++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 semantic_inference/include/semantic_inference/image_resizer.h create mode 100644 semantic_inference/src/image_resizer.cpp diff --git a/semantic_inference/include/semantic_inference/image_resizer.h b/semantic_inference/include/semantic_inference/image_resizer.h new file mode 100644 index 0000000..fcc6ba6 --- /dev/null +++ b/semantic_inference/include/semantic_inference/image_resizer.h @@ -0,0 +1,62 @@ +/* ----------------------------------------------------------------------------- + * BSD 3-Clause License + * + * Copyright (c) 2021-2024, Massachusetts Institute of Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * * -------------------------------------------------------------------------- */ + +#pragma once + +#include + +namespace semantic_inference { + +struct ImageResizer { + struct Config { + int width = -1; + int height = -1; + } const config; + + ImageResizer(); + explicit ImageResizer(const Config& config); + ImageResizer(const ImageResizer& other); + ImageResizer& operator=(const ImageResizer& other); + + cv::Mat resizeForModelInput(const cv::Mat& input) const; + // @brief Resize an RGB image to the target width and height prior to passing into a segmentation model. + // + // @param original input image. + + cv::Mat restoreToOriginal(const cv::Mat& input, const cv::Size& target) const; + // @brief Resize an image of (semantic) labels back to the size of the original image. + // + // @param original input (label) image. +}; + +void declare_config(ImageResizer::Config& config); + +} // namespace semantic_inference diff --git a/semantic_inference/src/image_resizer.cpp b/semantic_inference/src/image_resizer.cpp new file mode 100644 index 0000000..351d22d --- /dev/null +++ b/semantic_inference/src/image_resizer.cpp @@ -0,0 +1,85 @@ +/* ----------------------------------------------------------------------------- + * BSD 3-Clause License + * + * Copyright (c) 2021-2024, Massachusetts Institute of Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * * -------------------------------------------------------------------------- */ + +#include "semantic_inference/image_resizer.h" +#include "semantic_inference/logging.h" + +#include +#include +#include +#include + + +namespace semantic_inference { + +void declare_config(ImageResizer::Config& config) { + using namespace config; + name("ImageResizer::Config"); + config::field(config.width, "width", "pixels"); + config::field(config.height, "height", "pixels"); +} + +ImageResizer::ImageResizer() : ImageResizer(Config{}) {} + +ImageResizer::ImageResizer(const Config& config) + : config(config::checkValid(config)) {} + +ImageResizer::ImageResizer(const ImageResizer& other) + : config(other.config) {} + +ImageResizer& ImageResizer::operator=(const ImageResizer& other) { + const_cast(config) = other.config; + return *this; +} + +cv::Mat ImageResizer::resizeForModelInput(const cv::Mat& input) const +{ + // if either width or height is negative, then no op. Note defaults are both -1 + if (config.width < 0 || config.height < 0) + return input; + + cv::Mat scaled; + cv::resize(input, scaled, cv::Size(config.width, config.height), 0.0, 0.0, cv::INTER_AREA); + return scaled; +} + +cv::Mat ImageResizer::restoreToOriginal(const cv::Mat& input, const cv::Size& target) const +{ + if (config.width < 0 || config.height < 0) + return input; + + cv::Mat scaled; + cv::resize(input, scaled, target, 0, 0, cv::INTER_NEAREST); + return scaled; +} + + +} // namespace semantic_inference \ No newline at end of file From b2ee677f03cc02af50404fc1e202a820365b04d5 Mon Sep 17 00:00:00 2001 From: Dan Griffith Date: Wed, 18 Mar 2026 15:58:31 -0400 Subject: [PATCH 2/6] adds image_resizer to build/nodelet --- semantic_inference/CMakeLists.txt | 2 +- semantic_inference_ros/src/segmentation_nodelet.cpp | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/semantic_inference/CMakeLists.txt b/semantic_inference/CMakeLists.txt index ffa619d..878e0fa 100644 --- a/semantic_inference/CMakeLists.txt +++ b/semantic_inference/CMakeLists.txt @@ -36,7 +36,7 @@ configure_file(cmake/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/semantic_inference_ add_library( ${PROJECT_NAME} src/image_recolor.cpp src/image_rotator.cpp src/image_utilities.cpp src/logging.cpp - src/model_config.cpp src/segmenter.cpp + src/model_config.cpp src/segmenter.cpp src/image_resizer.cpp ) target_link_libraries(${PROJECT_NAME} PUBLIC config_utilities::config_utilities ${OpenCV_LIBRARIES}) target_include_directories( diff --git a/semantic_inference_ros/src/segmentation_nodelet.cpp b/semantic_inference_ros/src/segmentation_nodelet.cpp index 32539b6..f3a2883 100644 --- a/semantic_inference_ros/src/segmentation_nodelet.cpp +++ b/semantic_inference_ros/src/segmentation_nodelet.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,7 @@ class SegmentationNode : public rclcpp::Node { struct Config { Segmenter::Config segmenter; WorkerConfig worker; + ImageResizer::Config image_resizer; ImageRotator::Config image_rotator; bool show_config = true; bool show_output_config = false; @@ -99,6 +101,7 @@ class SegmentationNode : public rclcpp::Node { OutputPublisher output_pub_; ImageRotator image_rotator_; + ImageResizer image_resizer_; ianvs::ImageSubscription sub_; }; @@ -121,6 +124,7 @@ void declare_config(SegmentationNode::Config& config) { name("SegmentationNode::Config"); field(config.segmenter, "segmenter"); field(config.worker, "worker"); + field(config.image_resizer, "image_resizer"); field(config.image_rotator, "image_rotator"); field(config.show_config, "show_config"); field(config.show_output_config, "show_output_config"); @@ -134,6 +138,7 @@ SegmentationNode::SegmentationNode(const rclcpp::NodeOptions& options) config::fromCLI(options.arguments(), "output")), output_pub_(output_config, *this), image_rotator_(config.image_rotator), + image_resizer_(config.image_resizer), sub_(*this) { logging::Logger::addSink("ros", std::make_shared(get_logger())); logging::setConfigUtilitiesLogger(); @@ -155,6 +160,7 @@ SegmentationNode::SegmentationNode(const rclcpp::NodeOptions& options) throw e; } + worker_ = std::make_unique( config.worker, [this](const auto& msg) { runSegmentation(msg); }, @@ -192,7 +198,9 @@ void SegmentationNode::runSegmentation(const Image::ConstSharedPtr& msg) { << (img_ptr->image.type() == CV_8UC3 ? "yes" : "no"); const auto start = std::chrono::steady_clock::now(); - const auto rotated = image_rotator_.rotate(img_ptr->image); + cv::Mat scaled_image = image_resizer_.resizeForModelInput(img_ptr->image); + const auto rotated = image_rotator_.rotate(scaled_image); + const auto result = segmenter_->infer(rotated); if (!result) { SLOG(ERROR) << "failed to run inference!"; @@ -201,7 +209,8 @@ void SegmentationNode::runSegmentation(const Image::ConstSharedPtr& msg) { } const auto derotated = image_rotator_.derotate(result.labels); - output_pub_.publish(img_ptr->header, derotated, img_ptr->image); + const auto descaled = image_resizer_.restoreToOriginal(derotated, img_ptr->image.size()); + output_pub_.publish(img_ptr->header, descaled, img_ptr->image); const auto end = std::chrono::steady_clock::now(); const auto elapsed = From 2818d4d516b75d7f420a63e0d111bdc5fe5da6d9 Mon Sep 17 00:00:00 2001 From: Dan Griffith Date: Tue, 5 Aug 2025 10:14:14 -0400 Subject: [PATCH 3/6] initial commit. --- .../tests/utest_image_resizer.cpp | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 semantic_inference/tests/utest_image_resizer.cpp diff --git a/semantic_inference/tests/utest_image_resizer.cpp b/semantic_inference/tests/utest_image_resizer.cpp new file mode 100644 index 0000000..6e8c5ec --- /dev/null +++ b/semantic_inference/tests/utest_image_resizer.cpp @@ -0,0 +1,117 @@ +/* ----------------------------------------------------------------------------- + * BSD 3-Clause License + * + * Copyright (c) 2021-2024, Massachusetts Institute of Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * * -------------------------------------------------------------------------- */ + +#include +#include + +#include +#include + +namespace semantic_inference { + +TEST(ImageResizer, ResizeForModelInput) { + cv::Mat input(cv::Size(500, 500), CV_8UC3); + cv::randu(input, 0, 255); + + { // Expect output is unchanged if height is -1 + ImageResizer::Config config; + config.width = 200; + config.height = -1; + const ImageResizer resizer(config); + + cv::Mat output = resizer.resizeForModelInput(input); + EXPECT_EQ(input.size(), output.size()); + EXPECT_EQ(input.type(), output.type()); + + cv::Mat diff; + cv::compare(input, output, diff, cv::CmpTypes::CMP_NE); + EXPECT_TRUE(cv::countNonZero(diff.reshape(1)) == 0); + } + + { // Expect output is unchanged if width is -1 + ImageResizer::Config config; + config.width = -1; + config.height = 200; + const ImageResizer resizer(config); + + cv::Mat output = resizer.resizeForModelInput(input); + EXPECT_EQ(input.size(), output.size()); + EXPECT_EQ(input.type(), output.type()); + + cv::Mat diff; + cv::compare(input, output, diff, cv::CmpTypes::CMP_NE); + EXPECT_TRUE(cv::countNonZero(diff.reshape(1)) == 0); + } + + { // Expect output is unchanged with default configuration + ImageResizer::Config config; + const ImageResizer resizer(config); + + cv::Mat output = resizer.resizeForModelInput(input); + EXPECT_EQ(input.size(), output.size()); + EXPECT_EQ(input.type(), output.type()); + + cv::Mat diff; + cv::compare(input, output, diff, cv::CmpTypes::CMP_NE); + EXPECT_TRUE(cv::countNonZero(diff.reshape(1)) == 0); + } + + { // Expect output size is changed to configuration + ImageResizer::Config config; + config.width = 300; + config.height = 200; + const ImageResizer resizer(config); + + cv::Mat output = resizer.resizeForModelInput(input); + EXPECT_EQ(output.size(), cv::Size(config.width, config.height)); + } +} + +TEST(ImageResizer, RestoreToOriginal) { + cv::Mat input(cv::Size(300, 200), CV_16SC1); + cv::randu(input, 0, 32); + + ImageResizer::Config config; + config.width = 300; + config.height = 200; + const ImageResizer resizer(config); + + int original_width = 600; + int original_height = 400; + + cv::Mat output = + resizer.restoreToOriginal(input, cv::Size(original_width, original_height)); + + EXPECT_EQ(output.size(), cv::Size(original_width, original_height)); + EXPECT_EQ(input.type(), output.type()); +} + +} // namespace semantic_inference \ No newline at end of file From 52f2de618883e1ef1a7fc346c81e7ee2d66b8526 Mon Sep 17 00:00:00 2001 From: Dan Griffith Date: Tue, 5 Aug 2025 10:15:04 -0400 Subject: [PATCH 4/6] adds resizing tests --- semantic_inference/tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semantic_inference/tests/CMakeLists.txt b/semantic_inference/tests/CMakeLists.txt index 635c905..3ea29b5 100644 --- a/semantic_inference/tests/CMakeLists.txt +++ b/semantic_inference/tests/CMakeLists.txt @@ -2,6 +2,6 @@ find_package(GTest REQUIRED) include(GoogleTest) enable_testing() -add_executable(test_${PROJECT_NAME} utest_image_utilities.cpp utest_main.cpp) +add_executable(test_${PROJECT_NAME} utest_image_utilities.cpp utest_image_resizer.cpp utest_main.cpp) target_link_libraries(test_${PROJECT_NAME} PRIVATE ${PROJECT_NAME} GTest::gtest_main) gtest_add_tests(TARGET test_${PROJECT_NAME}) From 92d4dec66ba6dbf70103bf555a5353af26c2586e Mon Sep 17 00:00:00 2001 From: Dan Griffith Date: Tue, 5 Aug 2025 10:57:12 -0400 Subject: [PATCH 5/6] add extra line at eof --- semantic_inference/src/image_resizer.cpp | 2 +- semantic_inference/tests/utest_image_resizer.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/semantic_inference/src/image_resizer.cpp b/semantic_inference/src/image_resizer.cpp index 351d22d..8c3d67e 100644 --- a/semantic_inference/src/image_resizer.cpp +++ b/semantic_inference/src/image_resizer.cpp @@ -82,4 +82,4 @@ cv::Mat ImageResizer::restoreToOriginal(const cv::Mat& input, const cv::Size& ta } -} // namespace semantic_inference \ No newline at end of file +} // namespace semantic_inference diff --git a/semantic_inference/tests/utest_image_resizer.cpp b/semantic_inference/tests/utest_image_resizer.cpp index 6e8c5ec..e805f43 100644 --- a/semantic_inference/tests/utest_image_resizer.cpp +++ b/semantic_inference/tests/utest_image_resizer.cpp @@ -114,4 +114,4 @@ TEST(ImageResizer, RestoreToOriginal) { EXPECT_EQ(input.type(), output.type()); } -} // namespace semantic_inference \ No newline at end of file +} // namespace semantic_inference From c2e556e2a796bdc434acb95d0546e48f5d76651c Mon Sep 17 00:00:00 2001 From: Dan Griffith Date: Tue, 16 Jun 2026 08:12:07 -0400 Subject: [PATCH 6/6] apply clang formatting --- .../semantic_inference/image_resizer.h | 3 +- semantic_inference/src/image_resizer.cpp | 38 +++++++++---------- .../src/segmentation_nodelet.cpp | 6 +-- 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/semantic_inference/include/semantic_inference/image_resizer.h b/semantic_inference/include/semantic_inference/image_resizer.h index fcc6ba6..ad5f7ac 100644 --- a/semantic_inference/include/semantic_inference/image_resizer.h +++ b/semantic_inference/include/semantic_inference/image_resizer.h @@ -47,7 +47,8 @@ struct ImageResizer { ImageResizer& operator=(const ImageResizer& other); cv::Mat resizeForModelInput(const cv::Mat& input) const; - // @brief Resize an RGB image to the target width and height prior to passing into a segmentation model. + // @brief Resize an RGB image to the target width and height prior to passing into a + // segmentation model. // // @param original input image. diff --git a/semantic_inference/src/image_resizer.cpp b/semantic_inference/src/image_resizer.cpp index 8c3d67e..aa54f66 100644 --- a/semantic_inference/src/image_resizer.cpp +++ b/semantic_inference/src/image_resizer.cpp @@ -30,13 +30,14 @@ * * -------------------------------------------------------------------------- */ #include "semantic_inference/image_resizer.h" -#include "semantic_inference/logging.h" #include #include #include + #include +#include "semantic_inference/logging.h" namespace semantic_inference { @@ -49,37 +50,32 @@ void declare_config(ImageResizer::Config& config) { ImageResizer::ImageResizer() : ImageResizer(Config{}) {} -ImageResizer::ImageResizer(const Config& config) - : config(config::checkValid(config)) {} +ImageResizer::ImageResizer(const Config& config) : config(config::checkValid(config)) {} -ImageResizer::ImageResizer(const ImageResizer& other) - : config(other.config) {} +ImageResizer::ImageResizer(const ImageResizer& other) : config(other.config) {} ImageResizer& ImageResizer::operator=(const ImageResizer& other) { const_cast(config) = other.config; return *this; } -cv::Mat ImageResizer::resizeForModelInput(const cv::Mat& input) const -{ - // if either width or height is negative, then no op. Note defaults are both -1 - if (config.width < 0 || config.height < 0) - return input; +cv::Mat ImageResizer::resizeForModelInput(const cv::Mat& input) const { + // if either width or height is negative, then no op. Note defaults are both -1 + if (config.width < 0 || config.height < 0) return input; - cv::Mat scaled; - cv::resize(input, scaled, cv::Size(config.width, config.height), 0.0, 0.0, cv::INTER_AREA); - return scaled; + cv::Mat scaled; + cv::resize( + input, scaled, cv::Size(config.width, config.height), 0.0, 0.0, cv::INTER_AREA); + return scaled; } -cv::Mat ImageResizer::restoreToOriginal(const cv::Mat& input, const cv::Size& target) const -{ - if (config.width < 0 || config.height < 0) - return input; +cv::Mat ImageResizer::restoreToOriginal(const cv::Mat& input, + const cv::Size& target) const { + if (config.width < 0 || config.height < 0) return input; - cv::Mat scaled; - cv::resize(input, scaled, target, 0, 0, cv::INTER_NEAREST); - return scaled; + cv::Mat scaled; + cv::resize(input, scaled, target, 0, 0, cv::INTER_NEAREST); + return scaled; } - } // namespace semantic_inference diff --git a/semantic_inference_ros/src/segmentation_nodelet.cpp b/semantic_inference_ros/src/segmentation_nodelet.cpp index f3a2883..44df48b 100644 --- a/semantic_inference_ros/src/segmentation_nodelet.cpp +++ b/semantic_inference_ros/src/segmentation_nodelet.cpp @@ -32,8 +32,8 @@ #include #include #include -#include #include +#include #include #include @@ -160,7 +160,6 @@ SegmentationNode::SegmentationNode(const rclcpp::NodeOptions& options) throw e; } - worker_ = std::make_unique( config.worker, [this](const auto& msg) { runSegmentation(msg); }, @@ -209,7 +208,8 @@ void SegmentationNode::runSegmentation(const Image::ConstSharedPtr& msg) { } const auto derotated = image_rotator_.derotate(result.labels); - const auto descaled = image_resizer_.restoreToOriginal(derotated, img_ptr->image.size()); + const auto descaled = + image_resizer_.restoreToOriginal(derotated, img_ptr->image.size()); output_pub_.publish(img_ptr->header, descaled, img_ptr->image); const auto end = std::chrono::steady_clock::now();