diff --git a/.fish/activate.fish b/.fish/activate.fish new file mode 100755 index 0000000..aadfcdf --- /dev/null +++ b/.fish/activate.fish @@ -0,0 +1,75 @@ +# Self-contained environment: ros2-pass-2 +# Exported on: Thu Nov 13 06:58:18 PM CET 2025 +# Original environment from: /home/wessel/.config/fish/environments/configs/ros2-pass-2 + +# ROS2 development environment (requires distrobox) +# Environment: ros2-pass-2 + +# First check if running inside distrobox +if not test -f /run/.containerenv; and test -z "$CONTAINER_ID" + echo (set_color red)"This ROS2 environment should only be run inside a distrobox container"(set_color normal) + return 1 +end + +# Check if a previous initialization has occurred +if test -n "$__ENV_INITIALIZED" + echo (set_color yellow)"Environment already initialized"(set_color normal) + return 0 +end + +# Mark as initialized (only after distrobox check passes) +set -gx __ENV_INITIALIZED "1" +set -gx CURRENT_ENV "ros2-pass-2" +# Source ROS2 setup files using bass +if type -q bass + bass source /opt/ros/jazzy/setup.bash + if test -f ./install/setup.bash + bass source ./install/setup.bash + end +else + echo (set_color red)"Error: bass is required for ROS2 environment. Install with: fisher install edc/bass"(set_color normal) + return 1 +end + +# Set environment variable for the prompt prefix +set -gx ROS2_ACTIVE 1 + +# Save the original prompt function if it exists +# Only save if we don't already have a backup or if current prompt is not from an environment +if not functions -q __env_orig_prompt + if functions -q fish_prompt + functions -c fish_prompt __env_orig_prompt + else + function __env_orig_prompt + echo -n (whoami)'@'(prompt_hostname)' '(set_color $fish_color_cwd)(prompt_pwd)(set_color normal)'> ' + end + end +else + # If we already have a backup, we're switching environments + # No need to create a new backup +end + +# Define new prompt with ROS2 prefix +function fish_prompt + echo -n (set_color magenta)'(ros2-pass-2)'(set_color normal)' ' + __env_orig_prompt +end + +# ROS2 aliases and functions +alias cb="colcon build" +alias cbe="colcon build && env deactivate && cd ." +alias cbs="colcon build --symlink-install" +alias cbt="colcon build --packages-select" +alias ct="colcon test" +alias ctr="colcon test-result" + +echo (set_color green)"Activated ROS2 environment: ros2-pass-2"(set_color normal) + +# Custom deactivation function +function __env_custom_deactivate + # Remove ROS2-specific variables and aliases + set -e ROS2_ACTIVE + functions -e cb cbs cbt ct ctr 2>/dev/null + + echo (set_color blue)"ROS2 environment cleanup completed"(set_color normal) +end diff --git a/.fish/readme.norg b/.fish/readme.norg new file mode 100644 index 0000000..059556d --- /dev/null +++ b/.fish/readme.norg @@ -0,0 +1,60 @@ +* Exported Fish Environment: ros2-pass-2 +This directory contains a self-contained fish environment. + +** Files Structure + @code + .fish/ + |-- activate.fish + |-- readme.norg + |-- bin/ + @end + +** Usage + +*** Automatic Activation (Recommended) + The environment will automatically activate when you `cd` into this directory + if your Fish shell is configured with the auto-activation script. + + @code fish + function check_and_source_activate + if test -f (pwd)/.fish/activate.fish + source (pwd)/.fish/activate.fish + elif test -f (pwd)/activate.fish + source (pwd)/activate.fish + end + end + + function cd + builtin cd $argv && check_and_source_activate + end + @end + +*** Manual Activation + To manually activate the environment, run from the project root: + @code bash + source ./.fish/activate.fish + @end + +*** Deactivation + To deactivate the environment, run: + @code bash + env deactivate + @end + + Or simply `cd` to a different directory if using auto-activation. + +** What This Environment Provides + - Prompt showing the environment name + - Environment-specific aliases and functions + - Custom environment variables + - Automatic cleanup when deactivated + +** Requirements + - Fish shell + - `bass` plugin (`fisher install edc/bass`) for compatibility with bash scripts + +** Sharing + This environment is completely self-contained. You can: + - Copy this directory to another machine + - Share it with others + - Version control it with your project (add .fish/ to your repo) \ No newline at end of file diff --git a/Assignement - image reader.pdf b/Assignement - image reader.pdf new file mode 100644 index 0000000..24b9a62 Binary files /dev/null and b/Assignement - image reader.pdf differ diff --git a/src/image_reader_wessel_pkg/CMakeLists.txt b/src/image_reader_wessel_pkg/CMakeLists.txt new file mode 100644 index 0000000..45ac9ca --- /dev/null +++ b/src/image_reader_wessel_pkg/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.8) +project(image_reader_wessel_pkg) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(sensor_msgs REQUIRED) +find_package(cv_bridge REQUIRED) +find_package(OpenCV REQUIRED) + +# Add executable +add_executable(image_reader_node + src/Main.cpp + src/nodes/ImageReader.cpp +) + +# Link dependencies +ament_target_dependencies(image_reader_node + rclcpp + sensor_msgs + cv_bridge + OpenCV +) + +# Install targets +install(TARGETS + image_reader_node + DESTINATION lib/${PROJECT_NAME} +) + +ament_package() diff --git a/src/image_reader_wessel_pkg/package.xml b/src/image_reader_wessel_pkg/package.xml new file mode 100644 index 0000000..bb3a4f9 --- /dev/null +++ b/src/image_reader_wessel_pkg/package.xml @@ -0,0 +1,23 @@ + + + + image_reader_wessel_pkg + 0.0.0 + Image reader package with OpenCV processing + wessel + TODO: License declaration + + ament_cmake + + rclcpp + sensor_msgs + cv_bridge + libopencv-dev + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/image_reader_wessel_pkg/src/Main.cpp b/src/image_reader_wessel_pkg/src/Main.cpp new file mode 100644 index 0000000..0a7394f --- /dev/null +++ b/src/image_reader_wessel_pkg/src/Main.cpp @@ -0,0 +1,15 @@ +#include + +#include "rclcpp/rclcpp.hpp" +#include "nodes/ImageReader.hpp" + +int main(int argc,char *argv[]) { + rclcpp::init(argc,argv); + + auto node = std::make_shared(); + + rclcpp::spin(node); + rclcpp::shutdown(); + + return 0; +} \ No newline at end of file diff --git a/src/image_reader_wessel_pkg/src/nodes/ImageReader.cpp b/src/image_reader_wessel_pkg/src/nodes/ImageReader.cpp new file mode 100644 index 0000000..494d4e9 --- /dev/null +++ b/src/image_reader_wessel_pkg/src/nodes/ImageReader.cpp @@ -0,0 +1,145 @@ +#include "ImageReader.hpp" + +#include +#include + +namespace assignments::two::image_reader { + +ImageReaderNode::ImageReaderNode() : Node("image_reader_node") { + circle_color_.r = this->declare_parameter("circle_color_r", 255); + circle_color_.g = this->declare_parameter("circle_color_g", 0); + circle_color_.b = this->declare_parameter("circle_color_b", 0); + + RCLCPP_INFO(this->get_logger(), "circle_color_=(r%d, g%d, b%d)", + circle_color_.r, circle_color_.g, circle_color_.b); + + // Subscribe to image topic + image_subscriber_ = this->create_subscription( + "/image_raw", + 10, + std::bind( + &ImageReaderNode::image_callback, + this, + std::placeholders::_1 + ) + ); + + // Publisher for processed image + image_publisher_ = this->create_publisher("/image_processed", 10); + + RCLCPP_INFO(this->get_logger(), "initialized"); +} + +void ImageReaderNode::image_callback(const sensor_msgs::msg::Image::SharedPtr msg) { + RCLCPP_INFO_ONCE(this->get_logger(), "'/image_raw': received image, processing"); + + try { + // handle both RGB and BGR + cv_bridge::CvImagePtr cv_ptr; + if (msg->encoding == sensor_msgs::image_encodings::RGB8) { + cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::RGB8); + cv::cvtColor(cv_ptr->image, cv_ptr->image, cv::COLOR_RGB2BGR); + } else { + cv_ptr = cv_bridge::toCvCopy(msg, sensor_msgs::image_encodings::BGR8); + } + + ImageMetadata img; + img.canvas = cv_ptr->image; + img.width = img.canvas.cols; + img.height = img.canvas.rows; + + process_top_left_quadrant_white(img); + process_top_right_quadrant_circle(img); + process_bottom_left_quadrant_remove_blue(img); + process_bottom_right_quadrant_edge_detection(img); + + draw_division_lines(img); + + cv_ptr->image = img.canvas; + image_publisher_->publish(*cv_ptr->toImageMsg()); + } catch (cv_bridge::Exception& e) { + RCLCPP_ERROR(this->get_logger(), "cv_bridge exception: %s", e.what()); + } +} + +void ImageReaderNode::draw_division_lines(ImageMetadata& img) { + int half_width = img.width / 2; + int half_height = img.height / 2; + + cv::line( + img.canvas, + cv::Point(half_width, 0), + cv::Point(half_width, img.height), + cv::Scalar(255, 255, 255), + 2 + ); + + cv::line( + img.canvas, + cv::Point(0, half_height), + cv::Point(img.width, half_height), + cv::Scalar(255, 255, 255), + 2 + ); +} + +void ImageReaderNode::process_top_left_quadrant_white(ImageMetadata& img) { + int half_width = img.width / 2; + int half_height = img.height / 2; + + cv::Mat top_left = img.canvas(cv::Rect(0, 0, half_width, half_height)); + top_left.setTo(cv::Scalar(255, 255, 255)); +} + +void ImageReaderNode::process_top_right_quadrant_circle(ImageMetadata& img) { + int half_width = img.width / 2; + int half_height = img.height / 2; + + cv::Mat top_right = img.canvas(cv::Rect(half_width, 0, img.width - half_width, half_height)); + int radius = std::min(top_right.cols, top_right.rows) / 4; + cv::Point center(top_right.cols / 2, top_right.rows / 2); + + cv::circle( + top_right, + center, + radius, + cv::Scalar(circle_color_.b, circle_color_.g, circle_color_.r), + -1 + ); +} + +void ImageReaderNode::process_bottom_left_quadrant_remove_blue(ImageMetadata& img) { + int half_width = img.width / 2; + int half_height = img.height / 2; + cv::Mat bottom_left = + img.canvas(cv::Rect(0, half_height, half_width, img.height - half_height)); + + for (int y = 0; y < bottom_left.rows; y++) { + for (int x = 0; x < bottom_left.cols; x++) { + bottom_left.at(y, x)[0] = 0; // Blue channel + } + } +} + +void ImageReaderNode::process_bottom_right_quadrant_edge_detection(ImageMetadata& img) { + int half_width = img.width / 2; + int half_height = img.height / 2; + + cv::Mat bottom_right = + img.canvas( + cv::Rect( + half_width, + half_height, + img.width - half_width, + img.height - half_height + ) + ); + + cv::Mat gray, edges; + + cv::cvtColor(bottom_right, gray, cv::COLOR_BGR2GRAY); + cv::Canny(gray, edges, 50, 150); + cv::cvtColor(edges, bottom_right, cv::COLOR_GRAY2BGR); +} + +} // namespace assignments::two::image_reader diff --git a/src/image_reader_wessel_pkg/src/nodes/ImageReader.hpp b/src/image_reader_wessel_pkg/src/nodes/ImageReader.hpp new file mode 100644 index 0000000..edb1565 --- /dev/null +++ b/src/image_reader_wessel_pkg/src/nodes/ImageReader.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "rclcpp/rclcpp.hpp" +#include "sensor_msgs/msg/image.hpp" +#include + +namespace assignments::two::image_reader { + +struct RGBColors { + int r; + int g; + int b; +}; + +struct ImageMetadata { + cv::Mat canvas; + int width; + int height; +}; + +class ImageReaderNode : public rclcpp::Node { +public: + ImageReaderNode(); + +private: + rclcpp::Subscription::SharedPtr image_subscriber_; + rclcpp::Publisher::SharedPtr image_publisher_; + + RGBColors circle_color_; + + void image_callback(const sensor_msgs::msg::Image::SharedPtr msg); + + void draw_division_lines(ImageMetadata& img); + void process_top_left_quadrant_white(ImageMetadata& img); + void process_top_right_quadrant_circle(ImageMetadata& img); + void process_bottom_left_quadrant_remove_blue(ImageMetadata& img); + void process_bottom_right_quadrant_edge_detection(ImageMetadata& img); +}; + +} // namespace assignments::two::image_reader \ No newline at end of file diff --git a/src/launch_wessel_pkg/CMakeLists.txt b/src/launch_wessel_pkg/CMakeLists.txt new file mode 100644 index 0000000..91e76ed --- /dev/null +++ b/src/launch_wessel_pkg/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.8) +project(launch_wessel_pkg) + +find_package(ament_cmake REQUIRED) + +install( + DIRECTORY launch rviz + DESTINATION share/${PROJECT_NAME} +) + +ament_package() diff --git a/src/launch_wessel_pkg/launch/image_reader.launch.xml b/src/launch_wessel_pkg/launch/image_reader.launch.xml new file mode 100644 index 0000000..39daf5b --- /dev/null +++ b/src/launch_wessel_pkg/launch/image_reader.launch.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/src/launch_wessel_pkg/package.xml b/src/launch_wessel_pkg/package.xml new file mode 100644 index 0000000..61923fe --- /dev/null +++ b/src/launch_wessel_pkg/package.xml @@ -0,0 +1,15 @@ + + + + launch_wessel_pkg + 0.0.0 + TODO: Package description + Wessel T + TODO: License declaration + + ament_cmake + + + ament_cmake + + diff --git a/src/launch_wessel_pkg/rviz/image_processor_config.rviz b/src/launch_wessel_pkg/rviz/image_processor_config.rviz new file mode 100644 index 0000000..65a172d --- /dev/null +++ b/src/launch_wessel_pkg/rviz/image_processor_config.rviz @@ -0,0 +1,131 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + Splitter Ratio: 0.5 + Tree Height: 1255 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Goal Pose1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 + - Class: rviz_common/Time + Experimental: false + Name: Time + SyncMode: 0 + SyncSource: "" +Visualization Manager: + Class: "" + Displays: + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: Image + Normalize Range: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /image_processed + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: map + Frame Rate: 30 + Name: root + Tools: + - Class: rviz_default_plugins/Interact + Hide Inactive Objects: true + - Class: rviz_default_plugins/MoveCamera + - Class: rviz_default_plugins/Select + - Class: rviz_default_plugins/FocusCamera + - Class: rviz_default_plugins/Measure + Line color: 128; 128; 0 + - Class: rviz_default_plugins/SetInitialPose + Covariance x: 0.25 + Covariance y: 0.25 + Covariance yaw: 0.06853891909122467 + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /goal_pose + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: + Depth: 5 + Durability Policy: Volatile + History Policy: Keep Last + Reliability Policy: Reliable + Value: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 10 + Enable Stereo Rendering: + Stereo Eye Separation: 0.05999999865889549 + Stereo Focal Distance: 1 + Swap Stereo Eyes: false + Value: false + Focal Point: + X: 0 + Y: 0 + Z: 0 + Focal Shape Fixed Size: true + Focal Shape Size: 0.05000000074505806 + Invert Z Axis: false + Name: Current View + Near Clip Distance: 0.009999999776482582 + Pitch: 0.785398006439209 + Target Frame: + Value: Orbit (rviz) + Yaw: 0.785398006439209 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1436 + Hide Left Dock: false + Hide Right Dock: false + Image: + collapsed: false + QMainWindow State: 000000ff00000000fd0000000400000000000001560000056ffc0200000008fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c00610079007301000000130000056f000000c600fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261000000010000032c0000056ffc0200000004fb0000000a0049006d00610067006501000000130000056f0000001600fffffffb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003a000005040000009e00fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004ae0000003efc0100000002fb0000000800540069006d00650000000000000004ae000002e800fffffffb0000000800540069006d00650100000000000004500000000000000000000000200000056f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730000000000ffffffff0000000000000000 + Selection: + collapsed: false + Time: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1198 + X: 30 + Y: 54 diff --git a/src/usb_cam_wessel_pkg/CMakeLists.txt b/src/usb_cam_wessel_pkg/CMakeLists.txt new file mode 100644 index 0000000..1e05b48 --- /dev/null +++ b/src/usb_cam_wessel_pkg/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.8) +project(usb_cam_wessel_pkg) + +find_package(ament_cmake REQUIRED) + +install( + DIRECTORY config/ + DESTINATION share/${PROJECT_NAME}/config +) + +ament_package() diff --git a/src/usb_cam_wessel_pkg/config/params.yaml b/src/usb_cam_wessel_pkg/config/params.yaml new file mode 100644 index 0000000..31331a8 --- /dev/null +++ b/src/usb_cam_wessel_pkg/config/params.yaml @@ -0,0 +1,23 @@ +/usb_cam: + ros__parameters: + video_device: "/dev/video0" + framerate: 30.0 + io_method: "mmap" + frame_id: "camera" + pixel_format: "mjpeg2rgb" + color_format: "yuv422p" + image_width: 640 + image_height: 480 + camera_name: "usb_cam" + camera_info_url: "" + brightness: -1 + contrast: -1 + saturation: -1 + sharpness: -1 + gain: -1 + auto_white_balance: true + white_balance: 4000 + autoexposure: true + exposure: 100 + autofocus: false + focus: -1 diff --git a/src/usb_cam_wessel_pkg/package.xml b/src/usb_cam_wessel_pkg/package.xml new file mode 100644 index 0000000..00e0de5 --- /dev/null +++ b/src/usb_cam_wessel_pkg/package.xml @@ -0,0 +1,15 @@ + + + + usb_cam_wessel_pkg + 0.0.0 + USB camera package for ROS2 + wessel + TODO: License declaration + + ament_cmake + + + ament_cmake + +