diff --git a/.gitignore b/.gitignore index 796c68107..262810ef5 100644 --- a/.gitignore +++ b/.gitignore @@ -154,3 +154,7 @@ dmypy.json # docker files /docker/.vscode-server /docker/.zsh_history + +# colcon build stuff (I think) +/install +/log diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..c416832dc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "src/src/pointgrey_camera_driver"] + path = src/src/pointgrey_camera_driver + url = https://github.com/ros-drivers/pointgrey_camera_driver +[submodule "src/pointgrey_camera_driver"] + path = src/pointgrey_camera_driver + url = https://github.com/ros-drivers/pointgrey_camera_driver diff --git a/rktl_autonomy/CMakeLists.txt b/rktl_autonomy/CMakeLists.txt deleted file mode 100644 index f53b3d4bf..000000000 --- a/rktl_autonomy/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(rktl_autonomy) - -# Find catkin macros -find_package(catkin REQUIRED) - -# install python module(s) -catkin_python_setup() - -# generates cmake config files and set variables for installation -catkin_package() - -# Tests -if(CATKIN_ENABLE_TESTING) - find_package(roslaunch REQUIRED) - roslaunch_add_file_check(launch/cartpole) - roslaunch_add_file_check(launch/rocket_league) - find_package(rostest REQUIRED) - add_rostest(test/test_step.test) -endif() diff --git a/rktl_autonomy/README.md b/rktl_autonomy/README.md deleted file mode 100644 index 51c4f2b67..000000000 --- a/rktl_autonomy/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# rktl_autonomy - -This package provides all code required to train a neural network to drive the -car and interface it into the existing control structure. It also provides -equivalent interfaces for two other environments (snake game and cartpole) for -verification purposes. diff --git a/rktl_autonomy/YAMLEditor/.gitignore b/rktl_autonomy/YAMLEditor/.gitignore deleted file mode 100644 index c4194cfa6..000000000 --- a/rktl_autonomy/YAMLEditor/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Java -bin/ -.classpath -.settings - -# macOS metadata file -.DS_Store diff --git a/rktl_autonomy/YAMLEditor/.project b/rktl_autonomy/YAMLEditor/.project deleted file mode 100644 index 94ee56c1e..000000000 --- a/rktl_autonomy/YAMLEditor/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - YAMLEditor - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/rktl_autonomy/YAMLEditor/README.md b/rktl_autonomy/YAMLEditor/README.md deleted file mode 100644 index c97d0314c..000000000 --- a/rktl_autonomy/YAMLEditor/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# YAMLEditor - -## Unpack into a table, edit, and repack two YAML arrays - -Intended for use with reward values - -Built jar in `jars/` diff --git a/rktl_autonomy/YAMLEditor/jars/YAMLEditor.jar b/rktl_autonomy/YAMLEditor/jars/YAMLEditor.jar deleted file mode 100644 index bb3ed6c7d..000000000 Binary files a/rktl_autonomy/YAMLEditor/jars/YAMLEditor.jar and /dev/null differ diff --git a/rktl_autonomy/YAMLEditor/src/com/purduearc/yamleditor/Main.java b/rktl_autonomy/YAMLEditor/src/com/purduearc/yamleditor/Main.java deleted file mode 100644 index 67809ae9a..000000000 --- a/rktl_autonomy/YAMLEditor/src/com/purduearc/yamleditor/Main.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.purduearc.yamleditor; - -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.Arrays; -import java.util.regex.Pattern; - -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.JTextField; -import javax.swing.event.TableModelEvent; -import javax.swing.event.TableModelListener; -import javax.swing.table.DefaultTableModel; -/** - * Unpack into a table, edit, and repack two YAML arrays - * - * @author Campbell McClendon - * - * @version 1.0 - */ -public class Main { - private static final Rectangle scrollPaneBounds = new Rectangle(2, 44, 496, 426); - private static final String[] titles = new String[] {"ID", "WIN", "LOSE"}; - private static String[][] data = null; - public static void main(String[] args) { - JFrame frame = new JFrame("YAML Editor"); - frame.setResizable(false); - frame.setSize(500, 500); - - JTextField winInputField = new JTextField("[1, 2, 3, 4, 5]", 50); - winInputField.setBounds(2, 2, 100, 20); - - JTextField loseInputField = new JTextField("[6, 7, 8, 9, 10]"); - loseInputField.setBounds(104, 2, 100, 20); - - JLabel inputLabel = new JLabel("Input"); - inputLabel.setBounds(106, 24, 100, 20); - - data = new String[][] {{"0", "1", "2"}, {"1", "3", "4"}}; - DefaultTableModel tableModel = new DefaultTableModel(data, titles); - JTable table = new JTable(tableModel); - table.setFillsViewportHeight(true); - tableModel.addTableModelListener(new TableModelListener() { - @Override - public void tableChanged(TableModelEvent e) { - if (table.isEditing()) { - data[table.getSelectedRow()][table.getSelectedColumn()] = (String) table.getValueAt(table.getSelectedRow(), table.getSelectedColumn()); - } - } - }); - - JLabel errorLabel = new JLabel(""); - errorLabel.setBounds(225, 2, 100, 20); - - JScrollPane scrollPane = new JScrollPane(table); - scrollPane.setBounds(scrollPaneBounds); - - JTextField winOutputField = new JTextField(50); - winOutputField.setBounds(296, 2, 100, 20); - winOutputField.setEditable(false); - - JTextField loseOutputField = new JTextField(50); - loseOutputField.setBounds(398, 2, 100, 20); - loseOutputField.setEditable(false); - - JLabel outputLabel = new JLabel("Output"); - outputLabel.setBounds(298, 24, 100, 20); - - JButton unpackButton = new JButton("Unpack Arrays"); - unpackButton.setBounds(2, 24, 100, 20); - unpackButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - try { - String col1Text = clip(winInputField.getText().replaceAll("\\s+","")); - String[] col1ByID = col1Text.split(Pattern.quote(",")); - if (col1ByID.length < 2) { - System.err.println("Malformed win input data"); - errorLabel.setText("ERROR"); - return; - } - String col2Text = clip(loseInputField.getText().replaceAll("\\s+","")); - String[] col2ByID = col2Text.split(Pattern.quote(",")); - if (col2ByID.length < 2) { - System.err.println("Malformed lose input data"); - errorLabel.setText("ERROR"); - return; - } - if (col1ByID.length != col2ByID.length) { - System.err.println("Win and lose arrays must be of same length"); - errorLabel.setText("ERROR"); - return; - } - String[][] newData = new String[col1ByID.length][2]; - for (int i = 0; i < col1ByID.length; i++) { - newData[i][0] = col1ByID[i]; - newData[i][1] = col2ByID[i]; - } - String[][] dataWithID = new String[newData.length][newData[0].length + 1]; - for (int id = 0; id < dataWithID.length; id++) { - dataWithID[id][0] = "" + id; - for (int i = 1; i < dataWithID[id].length; i++) { - dataWithID[id][i] = newData[id][i - 1]; - } - } - data = dataWithID; - tableModel.setDataVector(dataWithID, titles); - errorLabel.setText(""); - } catch (Exception exception) { - errorLabel.setText("ERROR"); - exception.printStackTrace(); - } - } - }); - - JButton repackButton = new JButton("Repack Arrays"); - repackButton.setBounds(398, 24, 100, 20); - repackButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - try { - String[][] dataWithoutID = new String[data.length][]; - for (int x = 0; x < data.length; x++) { - String[] line = new String[data[x].length - 1]; - for (int y = 1; y < data[x].length; y++) { - line[y - 1] = data[x][y]; - } - dataWithoutID[x] = line; - } - String[] winValues = new String[dataWithoutID.length]; - String[] loseValues = new String[dataWithoutID.length]; - for (int i = 0; i < dataWithoutID.length; i++) { - winValues[i] = dataWithoutID[i][0]; - loseValues[i] = dataWithoutID[i][1]; - } - winOutputField.setText(Arrays.deepToString(winValues).replaceAll("\\s+","")); - loseOutputField.setText(Arrays.deepToString(loseValues).replaceAll("\\s+","")); - } catch (Exception exception) { - errorLabel.setText("ERROR"); - exception.printStackTrace(); - } - } - }); - - frame.add(winInputField); - frame.add(loseInputField); - frame.add(inputLabel); - frame.add(unpackButton); - frame.add(errorLabel); - frame.add(winOutputField); - frame.add(loseOutputField); - frame.add(outputLabel); - frame.add(repackButton); - frame.add(scrollPane); - - frame.setLayout(null); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setVisible(true); - } - /** - * Clips the first and last characters off of a string - * - * @param str input string - * @return str without first and last characters - */ - private static String clip(String str) { - if (str.charAt(0) == (char) '[') { - if (str.charAt(str.length() - 1) == (char) ']') return str.substring(1, str.length() - 1); - else return str.substring(1, str.length()); - } else if (str.charAt(str.length() - 1) == (char) ']') return str.substring(0, str.length() - 1); - else return str; - } -} diff --git a/rktl_autonomy/config/rocket_league.yaml b/rktl_autonomy/config/rocket_league.yaml deleted file mode 100644 index 7f6cb85f9..000000000 --- a/rktl_autonomy/config/rocket_league.yaml +++ /dev/null @@ -1,35 +0,0 @@ -reward: - # reward to be given each frame - # constant: 0.0 - # # reward as a function of squared distance from the ball - # ball_dist_sq: -0.5 - # # reward as a function of squared distance from the ball to the goal - # goal_dist_sq: -1.0 - # reward given when car changes velocity direction - direction_change: 0 - # reward given when the episode ends with the car scoring the proper goal - win: [100, 200, 300] - # reward given when the episode ends with the car scoring on the wrong goal - loss: [100, 100, 100] - # reward given each frame when the car is in reverse - # reverse: -25 - # # reward given each frame when the car is near the walls - # walls: - # # actual reward value - # value: -50 - # # threshold distance (meters) - # threshold: 0.25 - -# duration when the episode will be terminated. Unit is seconds (sim time) -max_episode_time: 15 - -log: - base_dir: "~/catkin_ws/data/rocket_league/" - # frequency to save progress plot. Unit is episodes - plot_freq: 50 - # variables to display in progress plot - basic: - - "duration" - - "goals" - advanced: - - "net_reward" diff --git a/rktl_autonomy/config/snake.yaml b/rktl_autonomy/config/snake.yaml deleted file mode 100644 index ca4aaee7f..000000000 --- a/rktl_autonomy/config/snake.yaml +++ /dev/null @@ -1,32 +0,0 @@ -reward: - # reward to be given when the snake dies. This should be zero or negative - death: 0.0 - # reward to be given for each goal achieved. This should be positive - goal: 50.0 - # reward to be given based off squared distance to goal. - # - normalized for field length - # - normalized for each second of gameplay - # - should be zero or negative (to promote being close to goal) - distance: 0.0 - # reward to be given each frame - # - normalized for each second of gameplay - # - could be negative to promote getting goals quickly - # - could be positive to offset distance penalty - constant: 0.0 - -max_episode_time: 30.0 - -control: - max_linear_velocity: 3.0 - max_angular_velocity: 3.0 - -log: - base_dir: "~/catkin_ws/data/snake/" - # frequency to save progress plot. Unit is episodes - plot_freq: 25 - # variables to include in progress plot - basic: - - duration - advanced: - - score - - net_reward \ No newline at end of file diff --git a/rktl_autonomy/launch/README.md b/rktl_autonomy/launch/README.md deleted file mode 100644 index 802c1e97e..000000000 --- a/rktl_autonomy/launch/README.md +++ /dev/null @@ -1,157 +0,0 @@ -# Launch Files - -These are [.launch files](https://wiki.ros.org/roslaunch/XML) that can be run -using [roslaunch](https://wiki.ros.org/roslaunch): - -```shell -roslaunch rktl_autonomy -``` - -```{contents} Launch Files in the package -:depth: 2 -:backlinks: top -:local: true -``` - ---- - -## cartpole_train.launch - -This launch file starts the training a model for the cartpole environment. Wraps -the [`cartpole_env`](../nodes/README.md#cartpole-env) node. For more info, -refer to its documentation. - -### Launch Arguments - -- `agent_name` (default: `rocket_league_agent`): Name of the agent. -- `render` (default: `false`): If true, a window for the current simulation will - be shown. -- `rate` (value: `30.0`): Rate at which the simulation runs, in steps per second. - -### Nodes - -- `cartpole_env`: [`cartpole_env`](../nodes/README.md#cartpole-env) node in - the [`rktl_autonomy`](../README.md) package. - ---- - -## rocket_league_agent.launch - -This launch file starts the agent for the rocket league environment. Wraps -the [`rocket_league_agent`](../nodes/README.md#rocket-league-agent) node. For -more info, refer to its documentation. - -### Launch Arguments - -- `plot_log` (default: `false`): If true, a plot will be generated. -- `weights_dir` (default: `'~/catkin_ws/data/rocket_league/'`): Folder - containing the weights of pre-trained models. -- `weights_name` (default: `'model'`): Which of the models to load, from the - directory defined in `weights_dir`. - -### Nodes - -- `rocket_league_agent`: - [`rocket_league_agent`](../nodes/README.md#rocket-league-agent) node from - the [`rktl_autonomy`](../README.md) package. -- `plotter`: [`plotter`](../nodes/README.md#plotter) node from the - [`rktl_autonomy`](../README.md) package. Only run if the `plot_log` argument - is set to `true`. - ---- - -## rocket_league_train.launch - -This launch file starts the training a model for the rocket league environment. -Wraps the [`rocket_league_agent`](../nodes/README.md#rocket-league-agent) node -and runs the simulator. - -Loads a training environment for and starts training a model. - -### Launch Arguments - -- `plot_log` (default: `true`): If true, a plot will be generated. -- `agent_name` (default: `rocket_league_agent`): Name of the agent. -- `render` (default: `false`): If true, a window for the current simulation will - be shown. -- `sim_mode` (default: `'ideal'`): Simulation mode for training. -- `rate` (value: `10.0`): Rate at which the simulation runs, in steps per - second. Cannot be overridden. -- `agent_type` (value: `'none'`): Disables the loading of an existing agent. - Cannot be overridden. - -### Nodes - -- `plotter`: [`plotter`](../nodes/README.md#plotter) node from the - [`rktl_autonomy`](../README.md) package. Only run if the `plot_log` argument - is set to `true`. - -### Included Launch Files - -- `rktl_perception camera.launch` (Included twice, once with `camera_name` set - to `cam2` and once with `camera_name` set to `cam3`) -- `rktl_launch rocket_league_sim.launch`: Loads Parameters from - `config/rocket_league.yaml` in the [`rktl_autonomy`](../README.md) package. - Also sets `~log` to `$(arg agent_name)/log`. - ---- - -## snake_eval.launch - -This launch file starts the agent for the snake environment. Wraps the -[`snake_agent`](../nodes/README.md#snake-agent) node. For more info, refer to -its documentation. - -### Launch Arguments - -- `plot_log` (default: `false`): If true, a plot will be generated. -- `render` (default: `true`): If true, a window for the current simulation will - be shown. -- `weights` (default: `'~/catkin_ws/data/snake/weights`'): Folder - containing the weights of pre-trained models. -- `rate` (value: `10.0`): Rate at which the simulation runs, in steps per - second. Cannot be overridden. -- `snake_size` (value: `7`): Initial number of segments for the snake. Cannot be - overridden. -- `arena_size` (value: `10`): Size of the arena the snake operates in. Cannot be - overridden. - -### Nodes - -- `snake_env`: `snake_node` node from the `snakesim` package. -- `snake_agent`: [`snake_agent`](../nodes/README.md#snake-agent) node from the - [`rktl_autonomy`](../README.md) package. -- `plotter`: [`plotter`](../nodes/README.md#plotter) node from the - [`rktl_autonomy`](../README.md) package. Only run if the `plot_log` argument - is set to `true`. - ---- - -## snake_train.launch - -This launch file starts the training a model for the snake environment. Wraps -the [`snake_agent`](../nodes/README.md#snake-agent) node. For more info, -refer to its documentation. - - -### Launch Arguments - -- `plot_log` (default: `false`): If true, a plot will be generated. -- `agent_name` (default: `'snake_agent'`): Name of the agent. -- `render` (default: `false`): If true, a window for the current simulation will - be shown. -- `rate` (value: `10.0`): Rate at which the simulation runs, in steps per - second. Cannot be overridden. -- `snake_size` (value: `7`): Initial number of segments for the snake. Cannot be - overridden. -- `arena_size` (value: `10`): Size of the arena the snake operates in. Cannot be - overridden. - -### Nodes - -- `snake_env`: `snake_node` node from the `snakesim` package. -- `plotter`: [`plotter`](../nodes/README.md#plotter) node from the - [`rktl_autonomy`](../README.md) package. Only run if the `plot_log` argument - is set to `true`. - ---- diff --git a/rktl_autonomy/launch/cartpole/cartpole_train.launch b/rktl_autonomy/launch/cartpole/cartpole_train.launch deleted file mode 100644 index 0c251f65e..000000000 --- a/rktl_autonomy/launch/cartpole/cartpole_train.launch +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/rktl_autonomy/launch/rocket_league/rocket_league_agent.launch b/rktl_autonomy/launch/rocket_league/rocket_league_agent.launch deleted file mode 100644 index 2d7327415..000000000 --- a/rktl_autonomy/launch/rocket_league/rocket_league_agent.launch +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/rktl_autonomy/launch/rocket_league/rocket_league_train.launch b/rktl_autonomy/launch/rocket_league/rocket_league_train.launch deleted file mode 100644 index cf7a34195..000000000 --- a/rktl_autonomy/launch/rocket_league/rocket_league_train.launch +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rktl_autonomy/launch/snake/snake_eval.launch b/rktl_autonomy/launch/snake/snake_eval.launch deleted file mode 100644 index 091e2f415..000000000 --- a/rktl_autonomy/launch/snake/snake_eval.launch +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rktl_autonomy/launch/snake/snake_train.launch b/rktl_autonomy/launch/snake/snake_train.launch deleted file mode 100644 index a80095c3a..000000000 --- a/rktl_autonomy/launch/snake/snake_train.launch +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/rktl_autonomy/nodes/README.md b/rktl_autonomy/nodes/README.md deleted file mode 100644 index 02b1a911a..000000000 --- a/rktl_autonomy/nodes/README.md +++ /dev/null @@ -1,168 +0,0 @@ -# Nodes - -These are the [ROS Nodes](http://wiki.ros.org/Nodes) provided by this package. -Most likely, you will use a launch file to run these. You can also run them -by using `rosrun`: - -```shell -rosrun rktl_autonomy -``` - -```{contents} ROS Nodes in the package -:depth: 2 -:backlinks: top -:local: true -``` - ---- - -## cartpole_env - -This node creates a connection between the CartPole-v0 environment from OpenAI's -Gym library and ROS for testing and simulation purposes. It subscribes to the -`cartpole/action` topic, which receives a message containing an action (an -integer) that is taken by the agent. It also publishes messages to the -`cartpole/observation`, `cartpole/reward`, and `cartpole/done` topics, which -contain the current observation (a Float32MultiArray), the current reward (a -Float32), and a Boolean indicating whether the episode is done or not, -respectively. - -### Subscribed Topics - -- `cartpole/action` ([std_msgs/Int32](/std_msgs/html/msg/Int32.html#http://)): - The action that should be performed. - -### Published Topics - -- `cartpole/observation` ([std_msgs/Float32MultiArray](/std_msgs/html/msg/Float32MultiArray.html#http://)): - Observation of the system at any given point. -- `cartpole/reward` ([std_msgs/Float32](/std_msgs/html/msg/Float32.html#http://)): - Reward for training. -- `cartpole/done` ([std_msgs/Bool](/std_msgs/html/msg/Bool.html#http://)): - Boolean representing the whether or not the simulation is complete. - ---- - -## plotter - -This node plots the progress of a machine learning algorithm. The progress is -subscribed to via the `diagnostic_msgs/DiagnosticStatus` message. The node uses -the `mpl_toolkits` library to plot the progress of the training, which is then -saved as a PNG image file. - -### Subscribed Topics - -- `~log` ([diagnostic_msgs/DiagnosticStatus](/diagnostic_msgs/html/msg/DiagnosticStatus.html#http://)): - Log for the training algorithm. - -### Parameters - -- `~log/base_dir` (string): The base directory where the training log files are - stored. -- `~log/plot_freq` (int, default: `25`): The frequency of plotting, i.e., how - often the node updates the plot with new data. -- `~log/basic` (list, default: `['duration']`): A list of basic variables to - plot. These variables have only one line on the plot. -- `~log/advanced` (list, default: `['net_reward']`): A list of advanced - variables to plot. These variables have three lines on the plot: the - average, the minimum, and the maximum. -- `~frequency` (float, default: `1/30`): Frequency of the main simulation loop. - ---- - -## rocket_league_agent - -This node runs the agent trained for the Rocket League project. It uses the -`RocketLeagueInterface` class in `rocket_league_interface.py`. - -### Subscribed Topics - -- `cars/car0/odom` ([nav_msgs/Odometry](/nav_msgs/html/msg/Odometry.html#http://)): - Odometry data of the car's position and velocity. -- `ball/odom` ([nav_msgs/Odometry](/nav_msgs/html/msg/Odometry.html#http://)): - Odometry data of the car's position and velocity. -- `match_status` ([rktl_msgs/MatchStatus](/rktl_msgs/html/msg/MatchStatus.html#http://)): - Match status data containing the current score of the game. - -### Published Topics - -- `/cars/car0/command` ([rktl_msgs/ControlCommand](/rktl_msgs/html/msg/ControlCommand.html#http://)): - ontrol commands for the car. - -### Parameters - -- `~weights` (string): The location of weights for where the model was saved. -- `/cars/throttle/max_speed` (float): Maximum velocity for the car, in meters - per second. -- `/cars/steering/max_throw` (float): Maximum steering angle for the car, in - meters per second. -- `~action_space/velocity/min` (float): Optional. Action space override for - velocity for the car, in meters per second. -- `~action_space/velocity/max` (float): Optional. Action space override for - velocity for the car, in meters per second. -- `~action_space/curvature/min` (float): Optional. Action space override for - steering angle for the car, in meters per second. -- `~action_space/curvature/max` (float): Optional. Action space override for - steering angle for the car, in meters per second. -- `/cars/length` (float): Length of the car, in meters. -- `/field/width` (float): Width of the field, in meters. -- `/field/length` (float): Length of the field, in meters. -- `~observation/goal_depth` (float, default, `0.075`): Depth of the goal, in - meters. -- `~observation/velocity/max_abs` (float, default, `3.0`): Max absolute velocity - for observation. (default is 3.0) -- `~observation/angular_velocity/max_abs` (float, default: `2π`): Max absolute - angular velocity for observation. (default is 2*pi) -- `~max_episode_time` (float, default: `30.0`): Maximum time in seconds for an - episode. -- `~reward/constant` (float, default: `0.0`): Constant reward for each step. -- `~reward/ball_dist_sq` (float, default: `0.0`): Reward for getting closer to - the ball. -- `~reward/goal_dist_sq` (float, default: `0.0`): Reward for getting closer to - the goal. -- `~reward/direction_change` (float, default: `0.0`): Reward for changing the - direction. -- `~reward/win` (float, default: `100.0`): Reward for winning. -- `~reward/loss` (float, default: `0.0`): Reward for losing. -- `~reward/reverse`: (float, default: `0.0`)Reward for reversing. -- `~reward/walls/value` (float, default: `0.0`): Reward for hitting walls. -- `~reward/walls/threshold` (float, default: `0.0`): Distance from the wall at - which reward is given. - ---- - -## snake_agent - -This node runs the agent trained for the snake environment. It uses the -`SnakeInterface` class in `snake_interface.py`. - -### Subscribed Topics - -- `snake/pose` ([geometry_msgs/PoseArray](/geometry_msgs/html/msg/PoseArray.html#http://)): - Pose of the snake parts. -- `snake/goal` ([geometry_msgs/PointStamped](/geometry_msgs/html/msg/PointStamped.html#http://)): - Location of the apple. -- `snake/score` ([std_msgs/Int32](/std_msgs/html/msg/Int32.html#http://)): - Current Score. -- `snake/active` ([std_msgs/Bool](/std_msgs/html/msg/Bool.html#http://)): - Whether or not the snake is active. - -### Published Topics - -- `snake/cmd_vel` ([geometry_msgs/Twist](/geometry_msgs/html/msg/Twist.html#http://)): - Command for the snake to follow. - -### Parameters - -- `~num_segments` (int, default: 7): Initial number of segments of the snake. -- `~field_size` (float, default: 10): Size of the field. -- `~control/max_angular_velocity` (float, default: 3.0): Max angular velocity. -- `~control/max_linear_velocity` (float, default: 3.0): Max velocity. -- `~reward/death` (float, default: 0.0): Penalty for death. -- `~reward/goal` (float, default: 50.0): Reward for eating an apple. -- `~reward/distance` (float, default: 0.0): Reward for distance traveled. -- `~reward/constant` (float, default: 0.0): Constant reward. -- `~max_episode_time` (float, default: 30.0): Maximum time in seconds for an - episode. - ---- diff --git a/rktl_autonomy/nodes/cartpole_env b/rktl_autonomy/nodes/cartpole_env deleted file mode 100755 index cd3e18057..000000000 --- a/rktl_autonomy/nodes/cartpole_env +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -"""CartPole-v0 environment with ROS bindings for testing. -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -# ROS -import rospy -from std_msgs.msg import Int32, Float32, Float32MultiArray, Bool -from std_srvs.srv import Empty, EmptyResponse - -import gym -from threading import Lock - -class CartPole(object): - def __init__(self): - rospy.init_node('cart_pole_node') - self.RENDER = rospy.get_param('~render', False) - - self.env = gym.make('CartPole-v0') - self.observation = self.env.reset() - self.reward=0 - self.done=0 - self.action=None - self.lock = Lock() - - # Publishers - self.obs_pub = rospy.Publisher('cartpole/observation', Float32MultiArray, queue_size=1) - self.reward_pub = rospy.Publisher('cartpole/reward', Float32, queue_size=1) - self.done_pub = rospy.Publisher('cartpole/done', Bool, queue_size=1) - - # Subscribers - rospy.Subscriber('cartpole/action', Int32, self.cartpole_action) - - # Services - rospy.Service('cartpole/reset', Empty, self.cartpole_reset) - - # Timers - rospy.Timer(rospy.Duration(rospy.get_param('~frequency', 1/30)), self.cartpole_step) - rospy.spin() - - def cartpole_step(self,event=None): - """Step time once.""" - with self.lock: - if self.action is not None and self.done == False: - self.observation, self.reward, self.done, __ = self.env.step(self.action) - - self.obs_pub.publish(None, self.observation) - self.reward_pub.publish(self.reward) - self.done_pub.publish(self.done) - - if self.RENDER: - self.env.render() - - def cartpole_action(self, action_msg): - """Callback for actions.""" - self.action = action_msg.data - - def cartpole_reset(self, __): - """Reset the environment.""" - with self.lock: - self.observation=self.env.reset() - self.reward=0 - self.done=0 - self.action=None - return EmptyResponse() - -if __name__ == "__main__": - CartPole() diff --git a/rktl_autonomy/nodes/rocket_league_agent b/rktl_autonomy/nodes/rocket_league_agent deleted file mode 100755 index 1192bb9a3..000000000 --- a/rktl_autonomy/nodes/rocket_league_agent +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -"""Real-time evaluation of the agent trained for the Rocket League project. -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -from rktl_autonomy import RocketLeagueInterface -from stable_baselines3 import PPO -from os.path import expanduser -import rospy - -# create interface (and init ROS) -env = RocketLeagueInterface(eval=True) - -# load the model -weights = expanduser(rospy.get_param('~weights')) -model = PPO.load(weights) - -# evaluate in real-time -obs = env.reset() -while True: - action, __ = model.predict(obs) - try: - obs, __, __, __ = env.step(action) - except rospy.ROSInterruptException: - exit() diff --git a/rktl_autonomy/nodes/snake_agent b/rktl_autonomy/nodes/snake_agent deleted file mode 100755 index f760d1b8a..000000000 --- a/rktl_autonomy/nodes/snake_agent +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -"""Real-time evaluation of the agent trained on the snake environment. -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -from rktl_autonomy import SnakeInterface -from stable_baselines3 import PPO -from os.path import expanduser -import rospy - -# create interface (and init ROS) -env = SnakeInterface(eval=True) - -# load the model -weights = expanduser(rospy.get_param('~weights')) -model = PPO.load(weights) - -# evaluate in real-time -obs = env.reset() -while True: - action, __ = model.predict(obs) - try: - obs, __, __, __ = env.step(action) - except rospy.ROSInterruptException: - exit() diff --git a/rktl_autonomy/package.xml b/rktl_autonomy/package.xml deleted file mode 100644 index c023f6e6c..000000000 --- a/rktl_autonomy/package.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - rktl_autonomy - 0.0.0 - Deep reinforcement learning for ARC Rocket League project - Autonomous Robotics Club of Purdue - BSD 3 Clause - http://www.purduearc.com - James Baxter - Meenakshi McNamara - Haddy Alchaer - Reuben Georgi - - catkin - rosunit - rospy - geometry_msgs - std_msgs - std_srvs - snakesim - rktl_msgs - rktl_launch - \ No newline at end of file diff --git a/rktl_autonomy/scripts/train_cartpole.py b/rktl_autonomy/scripts/train_cartpole.py deleted file mode 100755 index 37b491168..000000000 --- a/rktl_autonomy/scripts/train_cartpole.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -"""Training script for the CartPoleInterface for testing. -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -from rktl_autonomy import CartpoleInterface -from stable_baselines3 import PPO -import gym, time - -def show_progress(model, episodes=5): - env = gym.make("CartPole-v0") - obs = env.reset() - episodes = 0 - while episodes < 5: - action, __ = model.predict(obs, deterministic=True) - obs, __, done, __ = env.step(action) - env.render() - time.sleep(0.01) - if done: - obs = env.reset() - episodes += 1 - env.close() - -env = CartpoleInterface() -model = PPO("MlpPolicy", env, verbose=1) - -print("showing untrained performance") -show_progress(model) - -print("training on 10k steps") -model.learn(total_timesteps=10000) - -print("showing trained performance") -show_progress(model) diff --git a/rktl_autonomy/scripts/train_snake.py b/rktl_autonomy/scripts/train_snake.py deleted file mode 100755 index db7ad527f..000000000 --- a/rktl_autonomy/scripts/train_snake.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python3 -"""Training script for the Snake game in ARC tutorials. -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -from rktl_autonomy import SnakeInterface -from stable_baselines3 import PPO -from stable_baselines3.common.vec_env import SubprocVecEnv -from stable_baselines3.common.env_util import make_vec_env -from stable_baselines3.common.logger import configure -from stable_baselines3.common.callbacks import CheckpointCallback -from os.path import expanduser -import uuid - -if __name__ == '__main__': # this is required due to forking processes - run_id = str(uuid.uuid4()) # ALL running environments must share this - - env = make_vec_env(SnakeInterface, env_kwargs={'run_id':run_id}, - n_envs=16, vec_env_cls=SubprocVecEnv) - model = PPO("MlpPolicy", env) - - # log training progress as CSV - log_dir = expanduser(f'~/catkin_ws/data/snake/{run_id}') - logger = configure(log_dir, ["stdout", "csv", "log"]) - model.set_logger(logger) - - # log model weights - freq = 300 # (time steps in a SINGLE environment) - callback = CheckpointCallback(save_freq=freq, save_path=log_dir) - - # run training - steps = 50000 # 50k (timesteps accross ALL environments) - print(f"training on {steps} steps") - model.learn(total_timesteps=steps, callback=callback) - - # save final weights - print("done training") - model.save(log_dir + "/final_weights") - env.close() # this must be done to clean up other processes diff --git a/rktl_autonomy/setup.py b/rktl_autonomy/setup.py deleted file mode 100644 index d036274eb..000000000 --- a/rktl_autonomy/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python3 - -from distutils.core import setup -from catkin_pkg.python_setup import generate_distutils_setup - -setup_args = generate_distutils_setup( - packages=['rktl_autonomy'], - package_dir={'': 'src'} -) - -setup(**setup_args) \ No newline at end of file diff --git a/rktl_autonomy/src/rktl_autonomy/cartpole_direct_interface.py b/rktl_autonomy/src/rktl_autonomy/cartpole_direct_interface.py deleted file mode 100644 index 2025273b8..000000000 --- a/rktl_autonomy/src/rktl_autonomy/cartpole_direct_interface.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -Cartpole is a very simple environment included within OpenAI Gym. There is not -much of a reason to use it, except for verifying the functionality of this -library. Two different interfaces are provided, `CartpoleInterface` and -`CartpoleDirectInterface`. The former uses the `ROSInterface` class and the -latter directly owns the Gym environment. To verify the `ROSInterface` worked, -it was confirmed that they both behave the same way. - -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -# package -from rktl_autonomy import ROSInterface - -# ROS -import rospy - -# System -import gym - -class CartpoleDirectInterface(ROSInterface): - """ROS interface for the cartpole game.""" - def __init__(self, eval=False, launch_file=['rktl_autonomy', 'cartpole_train.launch'], launch_args=[], run_id=None): - super().__init__(node_name='cartpole_agent', eval=eval, launch_file=launch_file, launch_args=launch_args, run_id=run_id) - - self._RENDER = rospy.get_param('~render', False) - - self._env = gym.make('CartPole-v0') - self._state = None - - @property - def action_space(self): - """The Space object corresponding to valid actions.""" - return self._env.action_space - - @property - def observation_space(self): - """The Space object corresponding to valid observations.""" - return self._env.observation_space - - def _reset_env(self): - """Reset environment for a new training episode.""" - self._state = (self._env.reset(), 0, False, {}) - - def _reset_self(self): - """Reset internally for a new episode.""" - # Does not apply for direct interface - pass - - def _has_state(self): - """Determine if the new state is ready.""" - return self._state is not None - - def _clear_state(self): - """Clear state variables / flags in preparation for new ones.""" - self._state = None - - def _get_state(self): - """Get state tuple (observation, reward, done, info).""" - assert self._has_state() - return self._state - - def _publish_action(self, action): - """Publish an action to the ROS network.""" - assert action >= 0 and action < self.action_space.n - if self._RENDER: - self._env.render() - self._state = self._env.step(action) diff --git a/rktl_autonomy/src/rktl_autonomy/cartpole_interface.py b/rktl_autonomy/src/rktl_autonomy/cartpole_interface.py deleted file mode 100755 index cf3bdc5e5..000000000 --- a/rktl_autonomy/src/rktl_autonomy/cartpole_interface.py +++ /dev/null @@ -1,113 +0,0 @@ -""" -Cartpole is a very simple environment included within OpenAI Gym. There is not -much of a reason to use it, except for verifying the functionality of this -library. Two different interfaces are provided, `CartpoleInterface` and -`CartpoleDirectInterface`. The former uses the `ROSInterface` class and the -latter directly owns the Gym environment. To verify the `ROSInterface` worked, -it was confirmed that they both behave the same way. - -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -# package -from rktl_autonomy import ROSInterface -from gym.spaces import Discrete, Box - -# ROS -import rospy -from std_msgs.msg import Int32, Float32, Float32MultiArray, Bool -from std_srvs.srv import Empty - -# System -import numpy as np -from enum import IntEnum, unique, auto -from math import pi - -@unique -class CartpoleActions(IntEnum): - """Possible actions for deep learner.""" - LEFT = 0 - RIGHT = auto() - SIZE = auto() - -class CartpoleInterface(ROSInterface): - """ROS interface for the cartpole game.""" - def __init__(self, eval=False, launch_file=['rktl_autonomy', 'cartpole_train.launch'], launch_args=[], run_id=None): - super().__init__(node_name='cartpole_agent', eval=eval, launch_file=launch_file, launch_args=launch_args, run_id=run_id) - - # Publishers - self._action_pub = rospy.Publisher('cartpole/action', Int32, queue_size=1) - self._reset_srv = rospy.ServiceProxy('cartpole/reset', Empty) - - # State variables - self._obs = None - self._reward = None - self._done = None - - # Subscribers - rospy.Subscriber('cartpole/observation', Float32MultiArray, self._obs_cb) - rospy.Subscriber('cartpole/done', Bool, self._done_cb) - rospy.Subscriber('cartpole/reward', Float32, self._reward_cb) - - @property - def action_space(self): - """The Space object corresponding to valid actions.""" - return Discrete(CartpoleActions.SIZE) - - @property - def observation_space(self): - """The Space object corresponding to valid observations.""" - return Box(low=-pi, high=pi, shape=(4,), dtype=np.float32) - - def _reset_env(self): - """Reset environment for a new training episode.""" - self._reset_srv.call() - - def _reset_self(self): - """Reset internally for a new episode.""" - self._clear_state() - - def _has_state(self): - """Determine if the new state is ready.""" - return ( - self._obs is not None and - self._reward is not None and - self._done is not None) - - def _clear_state(self): - """Clear state variables / flags in preparation for new ones.""" - self._obs = None - self._reward = None - self._done = None - - def _get_state(self): - """Get state tuple (observation, reward, done, info).""" - assert self._has_state() - return (self._obs, self._reward, self._done, {}) - - def _publish_action(self, action): - """Publish an action to the ROS network.""" - assert action >= 0 and action < self.action_space.n - self._action_pub.publish(action) - - def _obs_cb(self, obs_msg): - """Callback for observation of game.""" - assert len(obs_msg.data) == self.observation_space.shape[0] - self._obs = np.asarray(obs_msg.data, dtype=np.float32) - with self._cond: - self._cond.notify_all() - - def _reward_cb(self, reward_msg): - """Callback for reward of game.""" - self._reward = reward_msg.data - with self._cond: - self._cond.notify_all() - - def _done_cb(self, done_msg): - """Callback for if episode is done.""" - self._done = done_msg.data - with self._cond: - self._cond.notify_all() diff --git a/rktl_autonomy/src/rktl_autonomy/env_counter.py b/rktl_autonomy/src/rktl_autonomy/env_counter.py deleted file mode 100644 index c77dba7d7..000000000 --- a/rktl_autonomy/src/rktl_autonomy/env_counter.py +++ /dev/null @@ -1,9 +0,0 @@ -class EnvCounter: - def __init__(self): - self.counter = 0 - - def count_env(self): - self.counter += 1 - - def get_current_counter(self): - return self.counter diff --git a/rktl_autonomy/src/rktl_autonomy/snake_interface.py b/rktl_autonomy/src/rktl_autonomy/snake_interface.py deleted file mode 100755 index 255f35a30..000000000 --- a/rktl_autonomy/src/rktl_autonomy/snake_interface.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -This provides an interface to the snake game created in the -[ARC Tutorials](https://github.com/purdue-arc/arc_tutorials/tree/snake_dqn). -This was largely to gain experience in training reinforcement learning agents -and there isn't much need for it anymore. It may be useful for testing new types -of agents or onboarding new members. - -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -# package -from rktl_autonomy import ROSInterface -from gym.spaces import Discrete, Box - -# ROS -import rospy -from geometry_msgs.msg import Twist, PoseArray, PointStamped -from std_msgs.msg import Int32, Bool -from std_srvs.srv import Empty - -# System -import numpy as np -from tf.transformations import euler_from_quaternion -from enum import IntEnum, unique, auto -from math import pi - -@unique -class SnakeActions(IntEnum): - """Possible actions for deep learner.""" - FORWARD = 0 - LEFT = auto() - RIGHT = auto() - SIZE = auto() - -class SnakeInterface(ROSInterface): - """ROS interface for the snake game.""" - def __init__(self, eval=False, launch_file=['rktl_autonomy', 'snake_train.launch'], launch_args=[], run_id=None): - super().__init__(node_name='snake_agent', eval=eval, launch_file=launch_file, launch_args=launch_args, run_id=run_id) - - # Constants - self._NUM_SEGMENTS = rospy.get_param('~num_segments', 7) - self._FIELD_SIZE = rospy.get_param('~field_size', 10) - self._ANGULAR_VELOCITY = rospy.get_param('~control/max_angular_velocity', 3.0) - self._LINEAR_VELOCITY = rospy.get_param('~control/max_linear_velocity', 3.0) - self._DEATH_REWARD = rospy.get_param('~reward/death', 0.0) - self._GOAL_REWARD = rospy.get_param('~reward/goal', 50.0) - self._DISTANCE_REWARD = rospy.get_param('~reward/distance', 0.0) - self._CONSTANT_REWARD = rospy.get_param('~reward/constant', 0.0) - self._MAX_TIME = rospy.get_param('~max_episode_time', 30.0) - - # Publishers - self._action_pub = rospy.Publisher('snake/cmd_vel', Twist, queue_size=1) - self._reset_srv = rospy.ServiceProxy('snake/reset', Empty) - - # State variables - self._pose = None - self._goal = None - self._score = None - self._alive = None - self._prev_time = None - self._start_time = None - - # Subscribers - rospy.Subscriber('snake/pose', PoseArray, self._pose_cb) - rospy.Subscriber('snake/goal', PointStamped, self._goal_cb) - rospy.Subscriber('snake/score', Int32, self._score_cb) - rospy.Subscriber('snake/active', Bool, self._alive_cb) - - @property - def action_space(self): - """The Space object corresponding to valid actions.""" - return Discrete(SnakeActions.SIZE) - - @property - def observation_space(self): - """The Space object corresponding to valid observations.""" - locations = 2*(1+self._NUM_SEGMENTS) - return Box( - low=np.array([-pi] + locations*[0], dtype=np.float32), - high=np.array([pi] + locations*[self._FIELD_SIZE], dtype=np.float32)) - - def _reset_env(self): - """Reset environment for a new training episode.""" - self._reset_srv.call() - - def _reset_self(self): - """Reset internally for a new episode.""" - self._clear_state() - self._prev_time = None - self._start_time = None - - def _has_state(self): - """Determine if the new state is ready.""" - return ( - self._pose is not None and - self._goal is not None and - self._score is not None and - self._alive is not None) - - def _clear_state(self): - """Clear state variables / flags in preparation for new ones.""" - self._pose = None - self._goal = None - self._score = None - self._alive = None - - def _get_state(self): - """Get state tuple (observation, reward, done, info).""" - assert self._has_state() - - # combine pose / goal for observation - pose = np.asarray(self._pose, dtype=np.float32) - goal = np.asarray(self._goal, dtype=np.float32) - observation = np.concatenate((pose, goal)) - - # Determine reward and if done - reward = 0 - done = False - - time = rospy.Time.now() - if self._prev_time is not None: - reward += (time - self._prev_time).to_sec() * self._CONSTANT_REWARD - dist_sq = np.sum(np.square(pose[1:3] - goal)) - norm_dist = dist_sq / (self._FIELD_SIZE ** 2) - reward += (time - self._prev_time).to_sec() * self._DISTANCE_REWARD * norm_dist - self._prev_time = time - - if self._prev_score is not None: - reward += self._GOAL_REWARD * (self._score - self._prev_score) - self._prev_score = self._score - - if not self._alive: - reward += self._DEATH_REWARD - done = True - - if self._start_time is None: - self._start_time = time - - if (time - self._start_time).to_sec() >= self._MAX_TIME: - done = True - - # info dict - info = {"score" : self._score} - - return (observation, reward, done, info) - - def _publish_action(self, action): - """Publish an action to the ROS network.""" - assert action >= 0 and action < self.action_space.n - - action_msg = Twist() - action_msg.linear.x = self._LINEAR_VELOCITY - if action == SnakeActions.LEFT: - action_msg.angular.z = self._ANGULAR_VELOCITY - if action == SnakeActions.RIGHT: - action_msg.angular.z = -self._ANGULAR_VELOCITY - - self._action_pub.publish(action_msg) - - def _pose_cb(self, pose_msg): - """Callback for poses of each segment of snake.""" - assert len(pose_msg.poses) == self._NUM_SEGMENTS - - yaw, __, __ = euler_from_quaternion(( - pose_msg.poses[0].orientation.x, - pose_msg.poses[0].orientation.y, - pose_msg.poses[0].orientation.z, - pose_msg.poses[0].orientation.w)) - - self._pose = tuple( - [yaw] + - [func(pose_msg.poses[i]) for i in range(self._NUM_SEGMENTS) - for func in ( - lambda pose: pose.position.x, - lambda pose: pose.position.y)]) - with self._cond: - self._cond.notify_all() - - def _goal_cb(self, goal_msg): - """Callback for location of goal.""" - self._goal = (goal_msg.point.x, goal_msg.point.y) - with self._cond: - self._cond.notify_all() - - def _score_cb(self, score_msg): - """Callback for score of game.""" - self._score = score_msg.data - with self._cond: - self._cond.notify_all() - - def _alive_cb(self, alive_msg): - """Callback for active state of snake.""" - self._alive = alive_msg.data - with self._cond: - self._cond.notify_all() diff --git a/rktl_control/CMakeLists.txt b/rktl_control/CMakeLists.txt deleted file mode 100644 index 1068e412b..000000000 --- a/rktl_control/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(rktl_control) - -# Find catkin macros -find_package(catkin REQUIRED) - -# generates cmake config files and set variables for installation -catkin_package() - -# Tests -if(CATKIN_ENABLE_TESTING) - find_package(roslaunch REQUIRED) - roslaunch_add_file_check(launch) - find_package(rostest REQUIRED) - add_rostest(test/test_sync.test) - add_rostest(test/test_mean_filter.test) - add_rostest(test/test_particle_filter.test) -endif() diff --git a/rktl_control/README.md b/rktl_control/README.md deleted file mode 100644 index 85d73b3d3..000000000 --- a/rktl_control/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# rktl_control - -This package performs several jobs that allow it to move physical cars according -to commands from either the planning or autonomy code. - -Specifically it has three main components: - -- It filters the raw perception data using either a moving average filter or a - particle filter, while also producing estimated odometry (which includes - velocity in addition to the position given by the raw perception data) -- It has a closed loop controller which reduces the error between the desired - and estimated motion of the car by producing control signals. -- It has a hardware interface, which sends those control signals to the physical - cars. - -It also contains several MATLAB scripts that are useful in tuning and validating -the Python code used for this project. diff --git a/rktl_control/config/controller.yaml b/rktl_control/config/controller.yaml deleted file mode 100644 index 7e1330a65..000000000 --- a/rktl_control/config/controller.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# controller type: 'lead_lag', 'pid', or 'none' -controller_type: 'none' -# parameters for all controllers -limits: - throttle: - min: -1.5 - max: 1.5 -# parameters for none (open loop) -open_loop: - publish_early: true -# parameters for lead_lag (stable with 0.2 sec delay) -# lead: -# gain: 2.2696 -# alpha: -0.6146 -# beta: -0.7130 -# lag: -# alpha: -0.9608 -# beta: -0.9951 -# alternate lead_lag (with predictive control) -lead: - gain: 3.9229 - alpha: -0.2584 - beta: -0.9231 -lag: - alpha: -0.573 - beta: -0.9786 -# parameters for pid -pid: - kp: 1.0 - ki: 0.0 - kd: 0.0 - anti_windup: 0.0 - deadband: 0.0 \ No newline at end of file diff --git a/rktl_control/config/hardware_interface.yaml b/rktl_control/config/hardware_interface.yaml deleted file mode 100644 index 7a58fafe0..000000000 --- a/rktl_control/config/hardware_interface.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# hardware configuration -port: /dev/ttyACM0 - -# servo adjustments (uS) -car0: - throttle_throw: 90 - steering_left: 950 - steering_center: 1450 - steering_right: 1850 -car1: - throttle_throw: 90 - steering_left: 950 - steering_center: 1450 - steering_right: 1850 -car2: - throttle_throw: 90 - steering_left: 950 - steering_center: 1450 - steering_right: 1850 -car3: - throttle_throw: 90 - steering_left: 950 - steering_center: 1450 - steering_right: 1850 -car4: - throttle_throw: 90 - steering_left: 950 - steering_center: 1450 - steering_right: 1850 -car5: - throttle_throw: 90 - steering_left: 950 - steering_center: 1450 - steering_right: 1850 \ No newline at end of file diff --git a/rktl_control/config/mean_odom_filter.yaml b/rktl_control/config/mean_odom_filter.yaml deleted file mode 100644 index 8d1016727..000000000 --- a/rktl_control/config/mean_odom_filter.yaml +++ /dev/null @@ -1,6 +0,0 @@ -delay: - compensate: false - duration: 0.15 -buffer_size: - position: 3 - velocity: 3 \ No newline at end of file diff --git a/rktl_control/config/particle_odom_filter.yaml b/rktl_control/config/particle_odom_filter.yaml deleted file mode 100644 index bd4f6e3d5..000000000 --- a/rktl_control/config/particle_odom_filter.yaml +++ /dev/null @@ -1,30 +0,0 @@ -rate: 10.0 -supersampling: 2 -publish_particles: true -num_particles: 100 -resample_proportion: 0.05 -boundary_check: true -delay: - compensate: true - duration: 0.1 -allowable_latency: 1.25 -open_loop_limit: 10 -measurement_error: - location: 0.1 - orientation: 0.2 -generator_noise: - location: 0.5 - orientation: 0.75 - velocity: 0.1 - steering_angle: 0.05 -efforts: - enable: true - buffer_size: 15 - throttle: - noise: 0.2 - max: 1.0 - min: -1.0 - steering: - noise: 0.2 - max: 1.0 - min: -1.0 \ No newline at end of file diff --git a/rktl_control/config/pose_synchronizer.yaml b/rktl_control/config/pose_synchronizer.yaml deleted file mode 100644 index d4e8c7f52..000000000 --- a/rktl_control/config/pose_synchronizer.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# rate of the whole perception system (Hz) -rate: 10.0 - -# delay in seconds. Lower is better, but AprilTags needs to keep up -delay: 0.1 - -# should debug info on latency be published? -publish_latency: true - -# buffer size for each topic. -# 12 potential detections per update (4 x 30 hz cameras) -# delay of ~ 2 updates -buffer_size: 30 - -# topics to be synchronized -topics: - - cars/car0/pose - - ball/pose - -# if Z in the message corresponds to a confidence weight -use_weights: - - false - - true \ No newline at end of file diff --git a/rktl_control/firmware/hardware_interface/CarLink.cpp b/rktl_control/firmware/hardware_interface/CarLink.cpp deleted file mode 100644 index 9d699133b..000000000 --- a/rktl_control/firmware/hardware_interface/CarLink.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/********************************************************************* -Class implementation for a single car's radio link. -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -*********************************************************************/ - -#include "CarLink.hpp" - -CarLink::CarLink(ros::NodeHandle *const nh, const int car_num, const int pin) : - nh{nh}, - car_num{car_num}, - topic{String("car") + String(car_num) + String("/effort")}, - throttle_throw{0}, - steering_center{1500}, - steering_left{1500}, - steering_right{1500}, - enabled{false}, - ppm_out{RISING}, - effort_sub{topic.c_str(), &CarLink::effort_cb, this} { - ppm_out.write(THROTTLE_CHANNEL, THROTTLE_ZERO); - ppm_out.write(STEERING_CHANNEL, steering_center); - ppm_out.begin(pin); - this->nh->subscribe(effort_sub); -} - -CarLink::~CarLink() { - disable(); -} - -void CarLink::enable() { - enabled = true; -} - -void CarLink::disable() { - ppm_out.write(THROTTLE_CHANNEL, THROTTLE_ZERO); - enabled = false; -} - -bool CarLink::update_params() { - if (nh->connected() - && nh->getParam((String("~car") + String(car_num) + String("/throttle_throw")).c_str(), &throttle_throw) - && nh->getParam((String("~car") + String(car_num) + String("/steering_center")).c_str(), &steering_center) - && nh->getParam((String("~car") + String(car_num) + String("/steering_left")).c_str(), &steering_left) - && nh->getParam((String("~car") + String(car_num) + String("/steering_right")).c_str(), &steering_right)) { - return true; - } else { - throttle_throw = 0; - steering_center = 1500; - steering_left = 1500; - steering_right = 1500; - return false; - } -} - -void CarLink::effort_cb(const rktl_msgs::ControlEffort& effort) { - // throttle - if (enabled) { - ppm_out.write(THROTTLE_CHANNEL, THROTTLE_ZERO + throttle_throw * effort.throttle); - } else { - ppm_out.write(THROTTLE_CHANNEL, THROTTLE_ZERO); - } - - // steering - if (effort.steering > 0) { - ppm_out.write(STEERING_CHANNEL, steering_center + (steering_left - steering_center) * effort.steering); - } else if (effort.steering < 0) { - ppm_out.write(STEERING_CHANNEL, steering_center + (steering_center - steering_right) * effort.steering); - } else { - ppm_out.write(STEERING_CHANNEL, steering_center); - } -} diff --git a/rktl_control/firmware/hardware_interface/CarLink.hpp b/rktl_control/firmware/hardware_interface/CarLink.hpp deleted file mode 100644 index 691dd4a6b..000000000 --- a/rktl_control/firmware/hardware_interface/CarLink.hpp +++ /dev/null @@ -1,44 +0,0 @@ -/********************************************************************* -Class definition for a single car's radio link. -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -*********************************************************************/ - -#pragma once - -#include - -#include -#include - -class CarLink { - public: - CarLink(ros::NodeHandle *const nh, const int car_num, const int pin); - ~CarLink(); - - void enable(); - void disable(); - bool update_params(); - - private: - const int THROTTLE_ZERO = 1500; - const int THROTTLE_CHANNEL = 1; - const int STEERING_CHANNEL = 2; - - ros::NodeHandle *const nh; - const int car_num; - const String topic; - - int throttle_throw; - int steering_center; - int steering_left; - int steering_right; - bool enabled; - - PulsePositionOutput ppm_out; - ros::Subscriber effort_sub; - - void effort_cb(const rktl_msgs::ControlEffort& effort); -}; diff --git a/rktl_control/nodes/topic_delay b/rktl_control/nodes/topic_delay deleted file mode 100755 index 67e97cd16..000000000 --- a/rktl_control/nodes/topic_delay +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python3 -""" -Delay an arbitrary ROS topic without modifying message - -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -import rospy -import roslib.message -from collections import deque -from time import sleep -import sys - - -class Delay(object): - """Class to delay arbitrary messages.""" - - def __init__(self): - # get args - assert len(sys.argv) >= 5 - input_name = sys.argv[1] - output_name = sys.argv[2] - topic_type = sys.argv[3] - delay = float(sys.argv[4]) - - # get class of type - msg_class = roslib.message.get_message_class(topic_type) - assert msg_class is not None - - # create node - rospy.init_node('delay', anonymous=True) - pub = rospy.Publisher(output_name, msg_class, queue_size=1) - rospy.Subscriber(input_name, msg_class, self.msg_cb) - self.buffer = deque() - - while not rospy.is_shutdown(): - if len(self.buffer) > 0: - time, msg = self.buffer.popleft() - wait = delay - (rospy.Time.now() - time).to_sec() - if wait > 0: - sleep(wait) - pub.publish(msg) - else: - sleep(delay/2) - - def msg_cb(self, msg): - """Callback for enqueuing new message""" - self.buffer.append((rospy.Time.now(), msg)) - - -if __name__ == "__main__": - Delay() diff --git a/rktl_control/package.xml b/rktl_control/package.xml deleted file mode 100644 index 0d704140c..000000000 --- a/rktl_control/package.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - rktl_control - 0.0.0 - Velocity controller for the ARC Rocket League project. - Autonomous Robotics Club of Purdue - BSD 3 Clause - http://www.purduearc.com - James Baxter - Justin Lee - Darren Lie - Robert Ketler - AJ Ernandes - - catkin - rosunit - rospy - std_msgs - nav_msgs - geometry_msgs - rosserial_arduino - joy - topic_tools - \ No newline at end of file diff --git a/rktl_control/scripts/analyze_step.m b/rktl_control/scripts/analyze_step.m deleted file mode 100644 index 7c23f0fc3..000000000 --- a/rktl_control/scripts/analyze_step.m +++ /dev/null @@ -1,51 +0,0 @@ -% Analyze step responses to perform a system ID - -% Collect bag files of effort and odom -% Run the following bash command in the folder containing responses: -% for bag in *.bag; do -% rostopic echo -b $bag -p effort/throttle > "$bag"_effort.csv -% rostopic echo -b $bag -p odom/twist/twist/linear/x > "$bag"_velocity.csv -% rostopic echo -b $bag -p pose_sync/pose/pose/position > "$bag"_position.csv -% done - -close all -clear - -% Location to get bag files from -bagdir = "../../../../data/bags/step_responses"; - -files = dir(bagdir); -for i = 1:numel(files) - file = files(i); - if ~endsWith(file.name, '.bag') - continue - end - - % read in data - effort = readtable(strcat(file.folder, '/', file.name, '_effort.csv')); - velocity = readtable(strcat(file.folder, '/', file.name, '_velocity.csv')); - position = readtable(strcat(file.folder, '/', file.name, '_position.csv')); - - % convert time from ns since epoch to seconds since start of bag - t0 = min(effort.x_time(1), velocity.x_time(1)); - effort.x_time = (effort.x_time - t0) * 10^-9; - velocity.x_time = (velocity.x_time - t0) * 10^-9; - position.x_time = (position.x_time - t0) * 10^-9; - - % manually calculate velocity from position to compare to filter output - xy = [position.field_x position.field_y]; - delta_xy = [0 0; xy(2:end, :) - xy(1:end-1, :)]; - dt = position.x_time(2) - position.x_time(1); - v_raw = sqrt(sum(delta_xy.^2 / dt, 2)); - v_smooth = smoothdata(v_raw); - - figure - plot(effort.x_time, effort.field), hold - plot(velocity.x_time, velocity.field) - plot(position.x_time, v_raw, ':') - plot(position.x_time, v_smooth, ':') - xlabel("Time, seconds") - ylabel("Effort | Velocity, m/s") - legend("effor", "filter velocity", "raw velocity", "smoothed velocity") - title(file.name, 'Interpreter', 'none') -end diff --git a/rktl_control/scripts/bicycle_model.m b/rktl_control/scripts/bicycle_model.m deleted file mode 100644 index 18b923650..000000000 --- a/rktl_control/scripts/bicycle_model.m +++ /dev/null @@ -1,25 +0,0 @@ -% Use MATLAB sybolic eq toolbox to solve for the equation used to -% linearize the bicycle model - -close all -clear - -syms DELTA_T CAR_LENGTH % constants -syms x y theta v_rear psi a_rear % input state - -% intermediate calculations -beta = atan(tan(psi)/2); -v_body = v_rear/cos(beta); -curvature = 2*sin(beta)/CAR_LENGTH; - -% next state calculations -next_x = x + v_body*cos(theta+beta)*DELTA_T; -next_y = y + v_body*sin(theta+beta)*DELTA_T; -next_theta = theta + curvature*v_body*DELTA_T; -next_v_rear = v_rear + a_rear*DELTA_T; - -% compute jacobian -state = [x; y; theta; v_rear; psi; a_rear]; -next_state = [next_x; next_y; next_theta; next_v_rear; psi; a_rear] -F = jacobian(next_state, state) - diff --git a/rktl_control/scripts/compare_filters.m b/rktl_control/scripts/compare_filters.m deleted file mode 100644 index ca61fc990..000000000 --- a/rktl_control/scripts/compare_filters.m +++ /dev/null @@ -1,312 +0,0 @@ -% Experiment with different types of filters (ekf, ukf, particle) -clear -close all - -%% Constants -% timing -global DELTA_T -DURATION = 15; % duration of data, sec -DELTA_T = 0.1; % step size of filter, sec -TRUTH_DELTA_T = 0.001; % step size of ground truth simulation, sec - -STEPS = DURATION/DELTA_T; -TRUTH_STEPS = DURATION/TRUTH_DELTA_T; - -% uncertainties -ORIGIN_LOC_STD_DEV = 0.1; % uncertainty of initial location, m -ORIGIN_DIR_STD_DEV = deg2rad(10); % uncertainty of initial heading, rad - -global MEAS_LOC_STD_DEV MEAS_DIR_STD_DEV -MEAS_LOC_STD_DEV = 0.05; % uncertainty of location measurement, m -MEAS_DIR_STD_DEV = deg2rad(5); % uncertainty of heading measurment, rad - -PROC_PSI_STD_DEV = deg2rad(1.5); % process noise for steering angle, rad -PROC_ACL_STD_DEV = 0.2; % process noise for rear wheel acceleration, m/s2 -NUM_PARTICLES = 1000; % particles in filter - -% physical properties of car -global CAR_LENGTH MAX_SPEED THROTTLE_TAU STEERING_THROW STEERING_RATE -CAR_LENGTH = .01; % wheel to wheel dist, m -MAX_SPEED = 1; % max speed, m/s -THROTTLE_TAU = 0.25; % time constant for changing speed, sec -STEERING_THROW = deg2rad(15); % center-side steering throw, rad -STEERING_RATE = deg2rad(30); % ROC steering, rad per sec - -%% Generate randomized control vectors -% generate an initial control (throttle; steering) -% throttle uniform between 0.5 and 1.0 -% steering uniform between -1.0 and 1.0 -control = rand(2, STEPS).*[0.5; 2] + [0.5; -1]; - -%% Generate resulting ground truth data -% pre-allocate matrix to hold output -% each column is full state (x, y, theta, v_rear, psi) -% new column for each time step -ground_truth = zeros(5, TRUTH_STEPS); - -% generate initial state -% random position, zero velocity, centered steering -state = randn(5, 1).*[ORIGIN_LOC_STD_DEV; ORIGIN_LOC_STD_DEV; ORIGIN_DIR_STD_DEV; 0; 0]; - -j = 1; -for i = 1:TRUTH_STEPS - % add to raw_data - ground_truth(:,i) = state; - - % step - state = simulate(state, control(:,j), TRUTH_DELTA_T); - - % update index to control vector - if (mod(i, TRUTH_STEPS/STEPS) == 0) - j = j + 1; - end -end - -%% Calculate resulting measurement data -% each column is measurement (x, y, theta) -% new column for each time step -measurements = ground_truth(1:3, 1:(TRUTH_STEPS/STEPS):end); - -% add artificial noise -measurements = measurements + randn(size(measurements)).*[MEAS_LOC_STD_DEV; MEAS_LOC_STD_DEV; MEAS_DIR_STD_DEV]; - -%% Create inputs to filters -% initial guesses -state = zeros(6, 1); -cov = diag([ORIGIN_LOC_STD_DEV, ORIGIN_LOC_STD_DEV, ORIGIN_DIR_STD_DEV, 0.01, deg2rad(1), 0.01]).^2; - -% measurement noise -R = diag([MEAS_LOC_STD_DEV; MEAS_LOC_STD_DEV; MEAS_DIR_STD_DEV]); - -% process noise -Q0 = diag([0, 0, 0, 0, PROC_PSI_STD_DEV, PROC_ACL_STD_DEV]).^2; -F = state_jacobian_func(zeros(6,1)); -Q = F*Q0*F'; - -%% Create filters -% EKF (additive) -ekf = extendedKalmanFilter(@state_func, @meas_func, state); -ekf.StateTransitionJacobianFcn = @state_jacobian_func; -ekf.MeasurementJacobianFcn = @meas_jacobian_func; -ekf.StateCovariance = cov; -ekf.MeasurementNoise = R; -ekf.ProcessNoise = Q; - -% UKF (additive) -ukf = unscentedKalmanFilter(@state_func, @meas_func, state); -ukf.StateCovariance = cov; -ukf.MeasurementNoise = R; -ukf.ProcessNoise = Q; - -% Particle filter -pf = particleFilter(@particle_state_func, @particle_meas_func); -initialize(pf, NUM_PARTICLES, zeros(5,1), cov(1:5,1:5)); - -%% Pre-allocate output -ekf_state = zeros(6, STEPS); -ukf_state = zeros(6, STEPS); -pf_state = zeros(5, STEPS); - -%% Run filters -for i = 1:DURATION/DELTA_T - %EKF - [ekf_state(:,i), ~] = correct(ekf, measurements(:,i)); - F = state_jacobian_func(ekf_state(:,i)); - ekf.ProcessNoise = F*Q0*F'; - predict(ekf); - - % UKF - [ukf_state(:,i), ~] = correct(ukf, measurements(:,i)); - F = state_jacobian_func(ukf_state(:,i)); - ukf.ProcessNoise = F*Q0*F'; - predict(ukf); - - % Particle - pf_state(:,i) = correct(pf, measurements(:,i)); - predict(pf); -end - -%% Plotting -% time vectors -truth_time = 0:TRUTH_DELTA_T:(DURATION-TRUTH_DELTA_T); -time = 0:DELTA_T:(DURATION-DELTA_T); - -% convert to odom -truth_odom = state_to_odom(ground_truth); -ekf_odom = state_to_odom(ekf_state); -ukf_odom = state_to_odom(ukf_state); -pf_odom = state_to_odom(pf_state); - -% XY plot -figure -plot(truth_odom(1,:), truth_odom(2,:)) -hold, grid on -plot(ekf_odom(1,:), ekf_odom(2,:), '--') -plot(ukf_odom(1,:), ukf_odom(2,:), '--') -plot(pf_odom(1,:), pf_odom(2,:), '--') -plot(measurements(1,:), measurements(2,:), 'x:') -plot(truth_odom(1,1), truth_odom(2,1), '*b') -legend(["Ground Truth", "EKF", "UKF", "PF", "Measurements"], 'Location', 'Best') -title("XY tracking") -xlabel("x, m") -ylabel("y, m") - -% Odom tracking -figure -sgtitle("External state tracking") -for i = 1:6 - subplot(2,3,i) - plot(truth_time, truth_odom(i, :)) - hold, grid on - plot(time, ekf_odom(i,:), '--') - plot(time, ukf_odom(i,:), '--') - plot(time, pf_odom(i,:), '--') - if (i <= 3) - plot(time, measurements(i,:), 'x:') - legend(["Ground Truth", "EKF", "UKF", "PF", "Measurements"], 'Location', 'Best') - else - legend(["Ground Truth", "EKF", "UKF", "PF"], 'Location', 'Best') - end - xlabel("Time, sec") - switch i - case 1; title("X tracking"); ylabel("x, m") - case 2; title("Y tracking"); ylabel("y, m") - case 3; title("Heading tracking"); ylabel("\theta, rad") - case 4; title("Body forward velocity tracking"); ylabel("x', m/s") - case 5; title("Body lateral velocity tracking"); ylabel("y', m/s") - case 6; title("Angular velocity tracking"); ylabel("\omega, rad/sec") - end -end - -%% Helper functions -% transition functions (additive noise) for EKF, UFK -function next_state = state_func(state) - % using bicycle model, extrapolate future state - global CAR_LENGTH DELTA_T - - % unpack input - x = state(1); - y = state(2); - theta = state(3); - v_rear = state(4); - psi = state(5); - a_rear = state(6); - - % equations were derived in bicyle_model.m - % calculate predicted next state - next_state = [ - x + DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - y + DELTA_T*v_rear*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - theta + (DELTA_T*v_rear*tan(psi))/CAR_LENGTH; - v_rear + DELTA_T*a_rear; - psi; - a_rear - ]; -end - -function measurement = meas_func(state) - % basically just the H matrix as a function - measurement = state(1:3); -end - -% jacobian functions for EKF -function jacobian = state_jacobian_func(state) - global CAR_LENGTH DELTA_T - - % unpack input - x = state(1); - y = state(2); - theta = state(3); - v_rear = state(4); - psi = state(5); - a_rear = state(6); - - % equations were derived in bicyle_model.m - jacobian = [ - 1, 0, -DELTA_T*v_rear*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2), DELTA_T*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2), (DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*tan(psi)*(tan(psi)^2 + 1))/(4*(tan(psi)^2/4 + 1)^(1/2)) - (DELTA_T*v_rear*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/2 + 1/2))/(tan(psi)^2/4 + 1)^(1/2), 0; - 0, 1, DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2), DELTA_T*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2), (DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/2 + 1/2))/(tan(psi)^2/4 + 1)^(1/2) + (DELTA_T*v_rear*tan(psi)*sin(theta + atan(tan(psi)/2))*(tan(psi)^2 + 1))/(4*(tan(psi)^2/4 + 1)^(1/2)), 0; - 0, 0, 1, (DELTA_T*tan(psi))/CAR_LENGTH, (DELTA_T*v_rear*(tan(psi)^2 + 1))/CAR_LENGTH, 0; - 0, 0, 0, 1, 0, DELTA_T; - 0, 0, 0, 0, 1, 0; - 0, 0, 0, 0, 0, 1 - ]; -end - -function jacobian = meas_jacobian_func(~) - jacobian = [eye(3), zeros(3)]; -end - -% Functions for particle filter -function particles = particle_state_func(particles) - for i = 1:size(particles, 2) - % create random control noise - control = rand(2,1).*[0.5; 2] + [0.5; -1]; - % propagate - global DELTA_T - particles(:,i) = simulate(particles(:,i), control, DELTA_T); - end - % add a little more modeling noise -% particles = particles + diag([.001, .001, deg2rad(1), .01, deg2rad(5)])*randn(size(particles)); -end - -function probability = particle_meas_func(particles, measurement) - pred_meas = particles(1:3,:); - error = pred_meas - ones(size(pred_meas)).*measurement; - - global MEAS_DIR_STD_DEV MEAS_LOC_STD_DEV - sigma = [MEAS_DIR_STD_DEV; MEAS_DIR_STD_DEV; MEAS_LOC_STD_DEV]; - base = (sigma*sqrt(2*pi)).^2; - probs = base .* exp(-.5*(error./sigma).^2); - - probability = probs(1,:).*probs(2,:).*probs(3,:); -end - -function next_state = simulate(state, control, DELTA_T) - % simple bicycle model with control - global CAR_LENGTH MAX_SPEED THROTTLE_TAU STEERING_THROW STEERING_RATE - - % unpack input - x = state(1); - y = state(2); - theta = state(3); - v_rear = state(4); - psi = state(5); - - v_rear_ref = MAX_SPEED*control(1); - psi_ref = STEERING_THROW*control(2); - - % update rear wheel velocity using 1st order model - v_rear = (v_rear-v_rear_ref)*exp(-DELTA_T/THROTTLE_TAU) + v_rear_ref; - - % update steering angle using massless acceleration to a fixed rate - if abs(psi_ref - psi) < STEERING_RATE*DELTA_T - psi = psi_ref; - elseif psi_ref > psi - psi = psi + STEERING_RATE*DELTA_T; - else - psi = psi - STEERING_RATE*DELTA_T; - end - - % using bicycle model, extrapolate future state - next_state = [ - x + DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - y + DELTA_T*v_rear*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - theta + (DELTA_T*v_rear*tan(psi))/CAR_LENGTH; - v_rear; - psi - ]; -end - -function odom = state_to_odom(state) - global CAR_LENGTH - - beta = atan(tan(state(5,:))/2); - v_body = state(4,:)./cos(beta); - curvature = 2*sin(beta)/CAR_LENGTH; - odom = [ - state(1:3,:); - v_body.*cos(beta); - v_body.*sin(beta); - v_body.*curvature - ]; -end \ No newline at end of file diff --git a/rktl_control/scripts/controller_design.m b/rktl_control/scripts/controller_design.m deleted file mode 100644 index a05bb87e5..000000000 --- a/rktl_control/scripts/controller_design.m +++ /dev/null @@ -1,70 +0,0 @@ -% Use sys ID to build a controller -close all -clear - -% Plant constants -T = 0.4174; % Time constant, sec -K = 0.7756; % Static gain, m/s - -t_di = 0.15; % input delay, sec -t_do = 0.05; % input delay, sec - -% System contants -ts = 0.1; % Sample time, sec - -% Build plant -G = tf(K, [T 1], 'InputDelay', t_di, 'OutputDelay', t_do); -G_zoh = c2d(G, ts, 'zoh'); - -% various plots -% figure, step(G_zoh) -% figure, rlocus(G_zoh) -% figure, bode(G_zoh), grid - -% frequency domain, lead lag controller design -% Inputs -sse = 0.05; % steady state error. Percent error wrt velocity input -PM = 60; % desired phase margin. Degrees. Higher increases stability. 30 is suggested minimum -w0 = 4.0; % desired gain crossover frequency. Rad/s. Higher increases speed - -% Calculate required controller gain -Kc = (1/sse - 1)/K; - -% Find existing phase and gain at w0 -[m, p] = bode(G, w0); -phi_0 = p + 180; % deg -M0 = 20*log10(m * Kc); % dB - -% Lead controller -phi_m = PM - phi_0 + 5; % can tweak the 5 degree fudge factor if desired -gamma = (1+sind(phi_m)) / (1-sind(phi_m)); -wz_lead = w0 / sqrt(gamma); -wp_lead = w0 * sqrt(gamma); -G_lead = gamma * tf([1 wz_lead], [1 wp_lead]); - -% Lag controller -M_lead = 10*log10(gamma); -M_lag = -(M0 + M_lead); -Gamma = 10^(M_lag/-20); -wz_lag = w0 / 10; -wp_lag = wz_lag / Gamma; -G_lag = 1/Gamma * tf([1 wz_lag], [1 wp_lag]); - -% Full lead-lag controller -C = Kc * G_lead * G_lag; -% figure, margin(C*G) - -% simulate -figure, step(feedback(C*G, 1)), grid, hold - -% discretize -Cd_rect = c2d(C, ts, 'zoh'); -step(feedback(Cd_rect*G_zoh, 1)) - -Cd_trap = c2d(C, ts, 'tustin'); -step(feedback(Cd_trap*G_zoh, 1)) - -legend(["Analog Controller", "Rectangular Integration", "Trapezoidal Integration"]) - -% output -zpk(Cd_trap) diff --git a/rktl_control/scripts/ekf_demo.m b/rktl_control/scripts/ekf_demo.m deleted file mode 100644 index cf30f2574..000000000 --- a/rktl_control/scripts/ekf_demo.m +++ /dev/null @@ -1,262 +0,0 @@ -% Experiment with extended Kalman filter for car's bicycle model -clear -close all - -%% Constants -% timing -DURATION = 30; % duration of data, sec -FILTER_DELTA_T = 0.1; % step size of filter, sec -TRUTH_DELTA_T = 0.01; % step size of ground truth simulation, sec -% ^ THESE NEED TO BE INTEGER MULTIPLES - -% uncertainties -ORIGIN_LOC_STD_DEV = 0.1; % uncertainty of initial location, m -ORIGIN_DIR_STD_DEV = deg2rad(10); % uncertainty of initial heading, rad - -MEAS_LOC_STD_DEV = 0.05; % uncertainty of location measurement, m -MEAS_DIR_STD_DEV = deg2rad(5); % uncertainty of heading measurment, rad - -PROC_PSI_STD_DEV = deg2rad(0.75); % process noise for steering angle, rad -PROC_ACL_STD_DEV = 0.1; % process noise for rear wheel acceleration, m/s2 - -% physical properties of car -global CAR_LENGTH MAX_SPEED THROTTLE_TAU STEERING_THROW STEERING_RATE -CAR_LENGTH = .01; % wheel to wheel dist, m -MAX_SPEED = 1; % max speed, m/s -THROTTLE_TAU = 0.25; % time constant for changing speed, sec -STEERING_THROW = deg2rad(15); % center-side steering throw, rad -STEERING_RATE = deg2rad(30); % ROC steering, rad per sec - -%% Generate randomized ground truth data -% pre-allocate matrix to hold output -% each column is full state (x, y, theta, v_rear, psi) -% new column for each time step -ground_truth = zeros(5, DURATION/TRUTH_DELTA_T); - -% generate initial state -% random position, zero velocity, centered steering -state = randn(5, 1).*[ORIGIN_LOC_STD_DEV; ORIGIN_LOC_STD_DEV; ORIGIN_DIR_STD_DEV; 0; 0]; - -% generate an initial control (throttle; steering) -% throttle uniform between 0.5 and 1.0 -% steering uniform between -1.0 and 1.0 -control = rand(2, 1).*[0.5; 2] + [0.5; -1]; - -for i = 1:size(ground_truth,2) - % add to raw_data - ground_truth(:,i) = state; - - % update random control with same frequency as filter - if (mod(i, FILTER_DELTA_T/TRUTH_DELTA_T) == 0) - control = rand(2, 1).*[0.5; 2] + [0.5; -1]; - end - - % step - state = simulate(state, control, TRUTH_DELTA_T); -end - -%% Calculate resulting measurement data -% each column is measurement (x, y, theta) -% new column for each time step -measurements = ground_truth(1:3, 1:(FILTER_DELTA_T/TRUTH_DELTA_T):end); - -% add artificial noise -measurements = measurements + randn(size(measurements)).*[MEAS_LOC_STD_DEV; MEAS_LOC_STD_DEV; MEAS_DIR_STD_DEV]; - -%% Paramters for EKF -% process noise -% assume steering and rear wheel velocity are independent -% projecting v_rear, psi noise onto model requires linearization done at each time step -Q0 = eye(6).*[0; 0; 0; 0; PROC_PSI_STD_DEV; PROC_ACL_STD_DEV].^2; - -% observation matrix (can only see position, not internal state) -H = [eye(3), zeros(3, 3)]; - -% measurement uncertainty -% assume independence between x, y, theta -R = eye(3).*[MEAS_LOC_STD_DEV; MEAS_LOC_STD_DEV; MEAS_DIR_STD_DEV].^2; - -%% Initial guess -state = zeros(6, 1); - -% covariance -% assume independence between all -% pick something small for v_rear, psi, a_rear -cov = eye(6).*[ORIGIN_LOC_STD_DEV; ORIGIN_LOC_STD_DEV; ORIGIN_DIR_STD_DEV; 0.01; deg2rad(1); 0.01].^2; - -%% Process via Kalman filter -% pre-allocate matrices to hold output -estimates = zeros(6, size(measurements,2)); - -% each page corresponds to time step -covariances = zeros(6, 6, size(measurements,2)); - -for i = 1:size(measurements,2) - % Add to outputs - estimates(:,i) = state; - covariances(:,:,i) = cov; - - % Extrapolate - [next_state, F] = extrapolate(state, FILTER_DELTA_T); - next_cov = F*cov*F'+ F*Q0*F'; - - % Kalman gain - gain = next_cov*H'/(H*next_cov*H'+R); - - % Update - state = next_state + gain*(measurements(:,i) - H*next_state); - cov = (eye(6)-gain*H)*next_cov*(eye(6)-gain*H)' + gain*R*gain'; -end - -%% Calculate odometry output -beta = atan(tan(ground_truth(5,:))/2); -v_body = ground_truth(4,:)/cos(beta); -curvature = 2*sin(beta)/CAR_LENGTH; -odom_true = [ - ground_truth(1:3,:); - v_body*cos(beta); - v_body*sin(beta); - v_body*curvature -]; -truth_time = 0:TRUTH_DELTA_T:(DURATION-TRUTH_DELTA_T); - -beta = atan(tan(estimates(5,:))/2); -v_body = estimates(4,:)/cos(beta); -curvature = 2*sin(beta)/CAR_LENGTH; -odom_est = [ - estimates(1:3,:); - v_body*cos(beta); - v_body*sin(beta); - v_body*curvature -]; -filter_time = 0:FILTER_DELTA_T:(DURATION-FILTER_DELTA_T); - -%% Graphical output -figure -plot(ground_truth(1,:), ground_truth(2,:)) -hold, grid on -plot(estimates(1,:), estimates(2,:), '--') -plot(measurements(1,:), measurements(2,:), '.') -plot(ground_truth(1,1), ground_truth(2,1), '*b') -legend(["Ground Truth", "Estimates", "Measurements"], 'Location', 'Best') -title("XY tracking") -xlabel("x, m") -ylabel("y, m") - -figure -sgtitle("Internal state tracking") -for i = 1:5 - subplot(2,3,i) - plot(truth_time, ground_truth(i, :)) - hold, grid on - plot(filter_time, estimates(i,:), '--') - if (i <= 3) - plot(filter_time, measurements(i,:), '.') - legend(["Ground Truth", "Estimates", "Measurements"], 'Location', 'Best') - else - legend(["Ground Truth", "Estimates"], 'Location', 'Best') - end - xlabel("Time, sec") - switch i - case 1; title("X tracking"); ylabel("x, m") - case 2; title("Y tracking"); ylabel("y, m") - case 3; title("Heading tracking"); ylabel("\theta, rad") - case 4; title("Rear wheel velocity tracking"); ylabel("v_{rear}, m/s") - case 5; title("Steering angle tracking"); ylabel("\psi, rad") - end -end - -figure -sgtitle("External state tracking") -for i = 1:6 - subplot(2,3,i) - plot(truth_time, odom_true(i, :)) - hold, grid on - plot(filter_time, odom_est(i,:), '--') - if (i <= 3) - plot(filter_time, measurements(i,:), '.') - legend(["Ground Truth", "Estimates", "Measurements"], 'Location', 'Best') - else - legend(["Ground Truth", "Estimates"], 'Location', 'Best') - end - xlabel("Time, sec") - switch i - case 1; title("X tracking"); ylabel("x, m") - case 2; title("Y tracking"); ylabel("y, m") - case 3; title("Heading tracking"); ylabel("\theta, rad") - case 4; title("Body forward velocity tracking"); ylabel("x', m/s") - case 5; title("Body lateral velocity tracking"); ylabel("y', m/s") - case 6; title("Angular velocity tracking"); ylabel("\omega, rad/sec") - end -end - -%% Helper functions -function [next_state, F] = extrapolate(state, DELTA_T) - % using bicycle model, extrapolate future state - global CAR_LENGTH - - % unpack input - x = state(1); - y = state(2); - theta = state(3); - v_rear = state(4); - psi = state(5); - a_rear = state(6); - - % equations were derived in bicyle_model.m - % calculate predicted next state - next_state = [ - x + DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - y + DELTA_T*v_rear*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - theta + (DELTA_T*v_rear*tan(psi))/CAR_LENGTH; - v_rear + DELTA_T*a_rear; - psi; - a_rear - ]; - - % calculate jacobian (linearization of this function about this point) - F = [ - 1, 0, -DELTA_T*v_rear*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2), DELTA_T*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2), (DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*tan(psi)*(tan(psi)^2 + 1))/(4*(tan(psi)^2/4 + 1)^(1/2)) - (DELTA_T*v_rear*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/2 + 1/2))/(tan(psi)^2/4 + 1)^(1/2), 0; - 0, 1, DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2), DELTA_T*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2), (DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/2 + 1/2))/(tan(psi)^2/4 + 1)^(1/2) + (DELTA_T*v_rear*tan(psi)*sin(theta + atan(tan(psi)/2))*(tan(psi)^2 + 1))/(4*(tan(psi)^2/4 + 1)^(1/2)), 0; - 0, 0, 1, (DELTA_T*tan(psi))/CAR_LENGTH, (DELTA_T*v_rear*(tan(psi)^2 + 1))/CAR_LENGTH, 0; - 0, 0, 0, 1, 0, DELTA_T; - 0, 0, 0, 0, 1, 0; - 0, 0, 0, 0, 0, 1 - ]; -end - -function next_state = simulate(state, control, DELTA_T) - % simple bicycle model with control - global CAR_LENGTH MAX_SPEED THROTTLE_TAU STEERING_THROW STEERING_RATE - - % unpack input - x = state(1); - y = state(2); - theta = state(3); - v_rear = state(4); - psi = state(5); - - v_rear_ref = MAX_SPEED*control(1); - psi_ref = STEERING_THROW*control(2); - - % update rear wheel velocity using 1st order model - v_rear = (v_rear-v_rear_ref)*exp(-DELTA_T/THROTTLE_TAU) + v_rear_ref; - - % update steering angle using massless acceleration to a fixed rate - if abs(psi_ref - psi) < STEERING_RATE*DELTA_T - psi = psi_ref; - elseif psi_ref > psi - psi = psi + STEERING_RATE*DELTA_T; - else - psi = psi - STEERING_RATE*DELTA_T; - end - - % using bicycle model, extrapolate future state - next_state = [ - x + DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - y + DELTA_T*v_rear*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - theta + (DELTA_T*v_rear*tan(psi))/CAR_LENGTH; - v_rear; - psi - ]; -end \ No newline at end of file diff --git a/rktl_control/scripts/gen_bicycle_unittest.m b/rktl_control/scripts/gen_bicycle_unittest.m deleted file mode 100644 index 9c8947efa..000000000 --- a/rktl_control/scripts/gen_bicycle_unittest.m +++ /dev/null @@ -1,56 +0,0 @@ -% Generate several bicycle model paths to verify accuracy of Python implementations - -% physical properties of car -global CAR_LENGTH MAX_SPEED THROTTLE_TAU STEERING_THROW STEERING_RATE -CAR_LENGTH = .01; % wheel to wheel dist, m -MAX_SPEED = 1; % max speed, m/s -THROTTLE_TAU = 0.25; % time constant for changing speed, sec -STEERING_THROW = deg2rad(15); % center-side steering throw, rad -STEERING_RATE = deg2rad(30); % ROC steering, rad per sec - -N_PARTICLES = 5; -DELTA_T = 0.1; - -control = rand(N_PARTICLES, 2)*2 + -1 -states = randn(N_PARTICLES, 5) -next_states = states; -for i = 1:N_PARTICLES - next_states(i,:) = simulate(states(i,:), control(i,:), DELTA_T); -end -next_states - -function next_state = simulate(state, control, DELTA_T) - % simple bicycle model with control - global CAR_LENGTH MAX_SPEED THROTTLE_TAU STEERING_THROW STEERING_RATE - - % unpack input - x = state(1); - y = state(2); - theta = state(3); - v_rear = state(4); - psi = state(5); - - v_rear_ref = MAX_SPEED*control(1); - psi_ref = STEERING_THROW*control(2); - - % update rear wheel velocity using 1st order model - v_rear = (v_rear-v_rear_ref)*exp(-DELTA_T/THROTTLE_TAU) + v_rear_ref; - - % update steering angle using massless acceleration to a fixed rate - if abs(psi_ref - psi) < STEERING_RATE*DELTA_T - psi = psi_ref; - elseif psi_ref > psi - psi = psi + STEERING_RATE*DELTA_T; - else - psi = psi - STEERING_RATE*DELTA_T; - end - - % using bicycle model, extrapolate future state - next_state = [ - x + DELTA_T*v_rear*cos(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - y + DELTA_T*v_rear*sin(theta + atan(tan(psi)/2))*(tan(psi)^2/4 + 1)^(1/2); - theta + (DELTA_T*v_rear*tan(psi))/CAR_LENGTH; - v_rear; - psi - ]; -end \ No newline at end of file diff --git a/rktl_game/CMakeLists.txt b/rktl_game/CMakeLists.txt deleted file mode 100644 index a3bcc1085..000000000 --- a/rktl_game/CMakeLists.txt +++ /dev/null @@ -1,41 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(rktl_game) - -## Compile as C++11, supported in ROS Kinetic and newer -# add_compile_options(-std=c++11) - -## Find catkin macros and libraries -## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) -## is used, also find other catkin packages -find_package(catkin REQUIRED COMPONENTS - roscpp - rospy - std_msgs -) - -################################### -## catkin specific configuration ## -################################### -## The catkin_package macro generates cmake config files for your package -## Declare things to be passed to dependent projects -## INCLUDE_DIRS: uncomment this if your package contains header files -## LIBRARIES: libraries you create in this project that dependent projects also need -## CATKIN_DEPENDS: catkin_packages dependent projects also need -## DEPENDS: system dependencies of this project that dependent projects also need -catkin_package( -# INCLUDE_DIRS include -# LIBRARIES rktl_game -# CATKIN_DEPENDS roscpp rospy std_msgs -# DEPENDS system_lib -) - -########### -## Build ## -########### - -## Specify additional locations of header files -## Your package locations should be listed before other locations -include_directories( -# include - ${catkin_INCLUDE_DIRS} -) \ No newline at end of file diff --git a/rktl_launch/CMakeLists.txt b/rktl_launch/CMakeLists.txt deleted file mode 100644 index 9a2aefa20..000000000 --- a/rktl_launch/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(rktl_launch) - -# find catkin so we can use our macros -find_package(catkin REQUIRED) - -# generate variables for installation -catkin_package() - -# Tests -if(CATKIN_ENABLE_TESTING) - find_package(roslaunch REQUIRED) - roslaunch_add_file_check(launch) -endif() diff --git a/rktl_launch/config/global_params.yaml b/rktl_launch/config/global_params.yaml deleted file mode 100644 index 2053ccd0c..000000000 --- a/rktl_launch/config/global_params.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# ALL UNITS IN SI - -# field dimensions -field: - width: 3 - # max: 3.5 - # min: 2.5 - length: 4.25 - # max: 5 - # min: 3.5 - wall_thickness: 0.25 - goal: - width: 1 - -# car dimensions and physical constants -ball: - radius: 0.0508 # (2 in) - -# car dimensions and physical constants -cars: - length: 0.12 # front to rear wheel center to center, meters - # min: 0.10 - # max: 0.30 - steering: - max_throw: 0.1826 # center to side, radians - # min: 0.16 - # max: 0.20 - rate: 0.9128 # rad/s - throttle: - max_speed: 2.3 # m/s - tau: 0.2 # time constant, seconds diff --git a/rktl_msgs/CMakeLists.txt b/rktl_msgs/CMakeLists.txt deleted file mode 100644 index 2a27b853b..000000000 --- a/rktl_msgs/CMakeLists.txt +++ /dev/null @@ -1,63 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(rktl_msgs) - -# find catkin so we can use our macros -find_package(catkin REQUIRED COMPONENTS - std_msgs - geometry_msgs - message_generation -) - -################################################ -## Declare ROS messages, services and actions ## -################################################ -## Generate messages in the 'msg' folder -add_message_files( - FILES - BezierPath.msg - BezierPathList.msg - ControlEffort.msg - ControlCommand.msg - MatchStatus.msg - Path.msg - Waypoint.msg - Score.msg -) - -## Generate services in the 'srv' folder -# add_service_files( -# FILES -# Service1.srv -# Service2.srv -# ) - -## Generate actions in the 'action' folder -# add_action_files( -# FILES -# Action1.action -# Action2.action -# ) - -## Generate added messages and services with any dependencies listed here -generate_messages( - DEPENDENCIES - std_msgs - geometry_msgs - rktl_msgs -) - -################################### -## catkin specific configuration ## -################################### -## The catkin_package macro generates cmake config files for your package -## Declare things to be passed to dependent projects -## INCLUDE_DIRS: uncomment this if your package contains header files -## LIBRARIES: libraries you create in this project that dependent projects also need -## CATKIN_DEPENDS: catkin_packages dependent projects also need -## DEPENDS: system dependencies of this project that dependent projects also need -catkin_package( -# INCLUDE_DIRS include -# LIBRARIES rktl_msgs - CATKIN_DEPENDS message_runtime -# DEPENDS system_lib -) diff --git a/rktl_msgs/package.xml b/rktl_msgs/package.xml deleted file mode 100644 index c9f30d2d0..000000000 --- a/rktl_msgs/package.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - rktl_msgs - 0.0.0 - Custom messages for ARC Rocket League Project - Autonomous Robotics Club of Purdue - BSD 3 Clause - https://www.purduearc.com - Arianna McNamara - James Baxter - - catkin - message_generation - message_runtime - geometry_msgs - std_msgs - diff --git a/rktl_perception/CMakeLists.txt b/rktl_perception/CMakeLists.txt deleted file mode 100644 index d5ae76ee4..000000000 --- a/rktl_perception/CMakeLists.txt +++ /dev/null @@ -1,129 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(rktl_perception) - -## Compile as C++11 -add_compile_options(-std=c++11) - -## Find catkin macros and libraries -find_package(catkin REQUIRED COMPONENTS - roscpp - pointgrey_camera_driver - image_proc - tf2 - tf2_ros - image_transport - cv_bridge - apriltag_ros - geometry_msgs - image_geometry -) - -## System dependencies -find_package(OpenCV REQUIRED - core - highgui -) -find_package(Eigen3 REQUIRED) - -catkin_package( - INCLUDE_DIRS include -# LIBRARIES rktl_perception - CATKIN_DEPENDS - roscpp - pointgrey_camera_driver - image_proc - tf2 - tf2_ros - image_transport - cv_bridge - apriltag_ros - DEPENDS OpenCV -) - -# Add include directories -include_directories( - include - ${catkin_INCLUDE_DIRS} - ${OpenCV_INCLUDE_DIRS} - ${EIGEN3_INCLUDE_DIR} -) - -# Build localizer node -add_executable(${PROJECT_NAME}_localizer_node - src/localizer_node.cpp - src/localizer.cpp -) -set_target_properties(${PROJECT_NAME}_localizer_node PROPERTIES - OUTPUT_NAME localizer - PREFIX "" -) -add_dependencies(${PROJECT_NAME}_localizer_node - ${${PROJECT_NAME}_EXPORTED_TARGETS} - ${catkin_EXPORTED_TARGETS} -) -target_link_libraries(${PROJECT_NAME}_localizer_node - ${catkin_LIBRARIES} - Eigen3::Eigen -) - -# Build ball_detection node -add_executable(${PROJECT_NAME}_ball_detection_node - src/ball_detection.cpp - src/ball_detection_node.cpp -) -set_target_properties(${PROJECT_NAME}_ball_detection_node PROPERTIES - OUTPUT_NAME ball_detection - PREFIX "" -) -add_dependencies(${PROJECT_NAME}_ball_detection_node - ${${PROJECT_NAME}_EXPORTED_TARGETS} - ${catkin_EXPORTED_TARGETS} - ${OpenCV_EXPORTED_TARGETS} -) -target_link_libraries(${PROJECT_NAME}_ball_detection_node - ${catkin_LIBRARIES} - ${OpenCV_LIBRARIES} -) - -# Build ball_color node -add_executable(${PROJECT_NAME}_ball_color_node - src/ball_color.cpp -) -set_target_properties(${PROJECT_NAME}_ball_color_node PROPERTIES - OUTPUT_NAME ball_color - PREFIX "" -) -add_dependencies(${PROJECT_NAME}_ball_color_node - ${${PROJECT_NAME}_EXPORTED_TARGETS} - ${catkin_EXPORTED_TARGETS} - ${OpenCV_EXPORTED_TARGETS} -) -target_link_libraries(${PROJECT_NAME}_ball_color_node - ${catkin_LIBRARIES} - ${OpenCV_LIBRARIES} -) - -# vis node -add_executable(${PROJECT_NAME}_focus_vis_node - src/focus_vis.cpp - src/focus_vis_node.cpp -) -set_target_properties(${PROJECT_NAME}_focus_vis_node PROPERTIES - OUTPUT_NAME focus_vis - PREFIX "" -) -add_dependencies(${PROJECT_NAME}_focus_vis_node - ${${PROJECT_NAME}_EXPORTED_TARGETS} - ${catkin_EXPORTED_TARGETS} - ${OpenCV_EXPORTED_TARGETS} -) -target_link_libraries(${PROJECT_NAME}_focus_vis_node - ${catkin_LIBRARIES} - ${OpenCV_LIBRARIES} -) - -# Tests -if(CATKIN_ENABLE_TESTING) - find_package(roslaunch REQUIRED) - roslaunch_add_file_check(launch) -endif() \ No newline at end of file diff --git a/rktl_perception/README.md b/rktl_perception/README.md deleted file mode 100644 index 5bf1500cb..000000000 --- a/rktl_perception/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# rktl_perception - -This packages contains all nodes, launch files, and configuration files -associated with the perception stack. diff --git a/rktl_perception/config/apriltag_settings.yaml b/rktl_perception/config/apriltag_settings.yaml deleted file mode 100644 index d971e57ec..000000000 --- a/rktl_perception/config/apriltag_settings.yaml +++ /dev/null @@ -1,10 +0,0 @@ -tag_family: 'tag25h9' -tag_border: 1 -tag_threads: 4 -tag_decimate: 1.0 -tag_blur: 0.0 -tag_refine_edges: 1 -tag_refine_decode: 0 -tag_refine_pose: 0 -tag_debug: 0 -publish_tf: false \ No newline at end of file diff --git a/rktl_perception/config/ball_settings.yaml b/rktl_perception/config/ball_settings.yaml deleted file mode 100644 index ef3cb0d9b..000000000 --- a/rktl_perception/config/ball_settings.yaml +++ /dev/null @@ -1,10 +0,0 @@ -publishThresh: true -min_hue: 50 -max_hue: 100 -min_sat: 70 -max_sat: 180 -min_vib: 40 -max_vib: 100 -min_size: 250 -erode: 2 -dilate: 8 \ No newline at end of file diff --git a/rktl_perception/config/cam0/calibration.yaml b/rktl_perception/config/cam0/calibration.yaml deleted file mode 100644 index 5b0dbcc46..000000000 --- a/rktl_perception/config/cam0/calibration.yaml +++ /dev/null @@ -1,20 +0,0 @@ -image_width: 1288 -image_height: 964 -camera_name: 15356310 -camera_matrix: - rows: 3 - cols: 3 - data: [610.4617133035794, 0, 648.8400790764666, 0, 612.8636563372359, 509.10981446149, 0, 0, 1] -distortion_model: plumb_bob -distortion_coefficients: - rows: 1 - cols: 5 - data: [-0.2432499228354474, 0.04650574966262223, -0.001403592205737197, -0.001846787629070493, 0] -rectification_matrix: - rows: 3 - cols: 3 - data: [1, 0, 0, 0, 1, 0, 0, 0, 1] -projection_matrix: - rows: 3 - cols: 4 - data: [437.3668518066406, 0, 642.4606144639438, 0, 0, 497.3519287109375, 519.6716765197198, 0, 0, 0, 1, 0] \ No newline at end of file diff --git a/rktl_perception/config/cam0/pointgrey.yaml b/rktl_perception/config/cam0/pointgrey.yaml deleted file mode 100644 index 8e515d209..000000000 --- a/rktl_perception/config/cam0/pointgrey.yaml +++ /dev/null @@ -1,6 +0,0 @@ -serial: 15356310 -frame_rate: 30 -auto_shutter: false -shutter_speed: 0.001 -auto_gain: false -gain: 25.0 diff --git a/rktl_perception/config/cam1/calibration.yaml b/rktl_perception/config/cam1/calibration.yaml deleted file mode 100644 index bb30a9a9a..000000000 --- a/rktl_perception/config/cam1/calibration.yaml +++ /dev/null @@ -1,20 +0,0 @@ -image_width: 1288 -image_height: 964 -camera_name: 15374749 -camera_matrix: - rows: 3 - cols: 3 - data: [614.57136917199, 0, 655.3078549706212, 0, 615.1667182505952, 527.5952225219263, 0, 0, 1] -distortion_model: plumb_bob -distortion_coefficients: - rows: 1 - cols: 5 - data: [-0.2139026302415871, 0.03446647780631319, 0.0003785624850231047, 0.001450676024114855, 0] -rectification_matrix: - rows: 3 - cols: 3 - data: [1, 0, 0, 0, 1, 0, 0, 0, 1] -projection_matrix: - rows: 3 - cols: 4 - data: [429.591796875, 0, 663.7421479902696, 0, 0, 510.9947814941406, 548.7979183761527, 0, 0, 0, 1, 0] \ No newline at end of file diff --git a/rktl_perception/config/cam1/pointgrey.yaml b/rktl_perception/config/cam1/pointgrey.yaml deleted file mode 100644 index 7b13e1a1d..000000000 --- a/rktl_perception/config/cam1/pointgrey.yaml +++ /dev/null @@ -1,6 +0,0 @@ -serial: 15374749 -frame_rate: 30 -auto_shutter: false -shutter_speed: 0.001 -auto_gain: false -gain: 25.0 diff --git a/rktl_perception/config/cam2/calibration.yaml b/rktl_perception/config/cam2/calibration.yaml deleted file mode 100644 index 561353f25..000000000 --- a/rktl_perception/config/cam2/calibration.yaml +++ /dev/null @@ -1,20 +0,0 @@ -image_width: 1288 -image_height: 964 -camera_name: 15374754 -camera_matrix: - rows: 3 - cols: 3 - data: [1126.08574591452, 0, 616.7375520125502, 0, 1116.255414571801, 525.7495338331287, 0, 0, 1] -distortion_model: plumb_bob -distortion_coefficients: - rows: 1 - cols: 5 - data: [-0.7942828618736387, 0.5381554120245144, 0.00207013009474492, 0.01022713459887638, 0] -rectification_matrix: - rows: 3 - cols: 3 - data: [1, 0, 0, 0, 1, 0, 0, 0, 1] -projection_matrix: - rows: 3 - cols: 4 - data: [827.6439208984375, 0, 634.490142294926, 0, 0, 908.0331420898438, 549.0852317258759, 0, 0, 0, 1, 0] \ No newline at end of file diff --git a/rktl_perception/config/cam2/pointgrey.yaml b/rktl_perception/config/cam2/pointgrey.yaml deleted file mode 100644 index f923d79ef..000000000 --- a/rktl_perception/config/cam2/pointgrey.yaml +++ /dev/null @@ -1,6 +0,0 @@ -serial: 15374754 -frame_rate: 30 -auto_shutter: false -shutter_speed: 0.001 -auto_gain: false -gain: 25.0 diff --git a/rktl_perception/config/cam3/calibration.yaml b/rktl_perception/config/cam3/calibration.yaml deleted file mode 100644 index 6bcbd4561..000000000 --- a/rktl_perception/config/cam3/calibration.yaml +++ /dev/null @@ -1,20 +0,0 @@ -image_width: 1288 -image_height: 964 -camera_name: 15374755 -camera_matrix: - rows: 3 - cols: 3 - data: [691.8297171319369, 0, 616.8642266971589, 0, 697.0148612025321, 517.5329039891342, 0, 0, 1] -distortion_model: plumb_bob -distortion_coefficients: - rows: 1 - cols: 5 - data: [-0.2678012034146836, 0.05194194294770087, 0.002831174538127116, 0.001537062305525107, 0] -rectification_matrix: - rows: 3 - cols: 3 - data: [1, 0, 0, 0, 1, 0, 0, 0, 1] -projection_matrix: - rows: 3 - cols: 4 - data: [478.2955932617188, 0, 611.6567255038535, 0, 0, 581.3926391601562, 538.9608447667997, 0, 0, 0, 1, 0] \ No newline at end of file diff --git a/rktl_perception/config/cam3/pointgrey.yaml b/rktl_perception/config/cam3/pointgrey.yaml deleted file mode 100644 index ad098a9ac..000000000 --- a/rktl_perception/config/cam3/pointgrey.yaml +++ /dev/null @@ -1,6 +0,0 @@ -serial: 15374755 -frame_rate: 30 -auto_shutter: false -shutter_speed: 0.001 -auto_gain: false -gain: 25.0 diff --git a/rktl_perception/config/tags.yaml b/rktl_perception/config/tags.yaml deleted file mode 100644 index 65097ca38..000000000 --- a/rktl_perception/config/tags.yaml +++ /dev/null @@ -1,24 +0,0 @@ -standalone_tags: - [ - { id: 10, size: 0.07 }, - { id: 11, size: 0.07 }, - { id: 12, size: 0.07 }, - { id: 13, size: 0.07 }, - ] -tag_bundles: - [ - { - name: 'field_origin', - layout: - [ - {id: 0, size: 0.2, x: -1.8081, y: 1.1742, z: -0.0308, qw: 0.9997, qx: 0.0099, qy: 0.0046, qz: 0.0197}, - {id: 1, size: 0.2, x: 0.0701, y: 1.3045, z: -0.0291, qw: 0.9998, qx: -0.0116, qy: -0.0007, qz: 0.0161}, - {id: 2, size: 0.2, x: 1.7377, y: 1.2590, z: -0.0244, qw: 0.9998, qx: 0.0085, qy: -0.0038, qz: 0.0152}, - {id: 3, size: 0.2, x: -1.1869, y: 0.0000, z: 0.0000, qw: 1.0000, qx: 0.0000, qy: 0.0000, qz: 0.0000}, - {id: 4, size: 0.2, x: 1.1869, y: 0.0138, z: -0.0036, qw: 0.9998, qx: 0.0047, qy: -0.0161, qz: 0.0089}, - {id: 5, size: 0.2, x: -1.7244, y: -1.2670, z: 0.0111, qw: 0.9998, qx: 0.0008, qy: -0.0024, qz: 0.0208}, - {id: 6, size: 0.2, x: 0.1233, y: -1.2090, z: 0.0012, qw: 0.9998, qx: 0.0023, qy: 0.0177, qz: 0.0000}, - {id: 7, size: 0.2, x: 1.8482, y: -1.0850, z: 0.0030, qw: 0.9999, qx: -0.0131, qy: -0.0009, qz: -0.0015} - ] - } - ] \ No newline at end of file diff --git a/rktl_perception/launch/README.md b/rktl_perception/launch/README.md deleted file mode 100644 index 9f3f3f8ac..000000000 --- a/rktl_perception/launch/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# Launch Files - -These are [.launch files](https://wiki.ros.org/roslaunch/XML) that can be run -using [roslaunch](https://wiki.ros.org/roslaunch): - -```shell -roslaunch rktl_perception -``` - -```{contents} Launch Files in this package -:depth: 2 -:backlinks: top -:local: true -``` - ---- - -## cal.launch - -Utility launch file that launches an interface for calibrating all cameras. It -is configured for a 9 squares by 7 squares chessboard pattern, where each square -is 26cm is length. - ---- - -## camera.launch - -Launch file containing everything that needs to be run for 1 camera. The -`camera_name` argument can be changed to easily change the camera used. - -### Launch Arguments - -- `load_manager` (default: true): If true, a nodelet manager is started. If a - nodelet manager is already running, set this to false to avoid any errors. -- `manager_name` (default: `camera_manager`): The name of the nodelet manager to - use for nodes that require it. -- `manager_threads` (default: `4`): Number of threads to use in the nodelet - manager, if started. -- `camera_name` (default: `cam0`): Name of the camera to launch the stack for. - -### Nodes - -- `cams/{camera_name}/{manager_name}`: [Nodelet](https://wiki.ros.org/nodelet) - manager. Only run if the `load_manager` argument is set to true. -- `cams/{camera_name}/{camera_name}`: [Nodelet](https://wiki.ros.org/nodelet) of type - [pointgrey_camera_driver/PointGreyCameraNodelet](https://wiki.ros.org/pointgrey_camera_driver). - Puts camera feed onto the ROS network. Loads parameters (including camera - serial number) from `rktl_perception/config/{camera_name}/pointgrey.yaml` - and calibration from `rktl_perception/config/{camera_name}/calibration.yaml`. -- `cams/{camera_name}/apriltag`: `apriltag_ros_continuous_node` node from the - [apriltag_ros](https://wiki.ros.org/apriltag_ros) package. Looks for - AprilTags in the camera feed and outputs the result to be used by other - nodes. Loads parameters from `rktl_perception/config/apriltag_settings.yaml` - and `rktl_perception/config/tags.yaml`. -- `localizer`: [`localizer`](../nodes/README.md#localizer) node from the - `rktl_perception` package. -- `pose_to_tf`: [`pose_to_tf`](../nodes/README.md#pose_to_tf) node from the - `rktl_perception` package. -- `ball_detection`: [`ball_detection`](../nodes/README.md#ball_detection) node - from the `rktl_perception` package. Loads parameters from - `rktl_perception/config/ball_settings.yaml`. - -### Included Launch Files - -- `image_proc image_proc.launch`: Basic processing stack (image rectification - for the provided camera). - ---- - -## color_picker.launch - -Utility launch file that opens a color picker used for fine tuning the parameters used for ball detection. - ---- - -## field_view.launch - -Utility launch file used for displaying the camera positions and feeds in RViz. - ---- - -## focus_assist.launch - -Utility launch file used launching the topics needed for focusing camera lenses. - ---- diff --git a/rktl_perception/launch/cal.launch b/rktl_perception/launch/cal.launch deleted file mode 100644 index 207d93c28..000000000 --- a/rktl_perception/launch/cal.launch +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/rktl_perception/launch/camera.launch b/rktl_perception/launch/camera.launch deleted file mode 100644 index 63dff35d5..000000000 --- a/rktl_perception/launch/camera.launch +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - '0,1,2,3,4,5,6,7': /origin/pose - '10': /cars/car0/pose - - - - - - - - - - - - diff --git a/rktl_perception/launch/color_picker.launch b/rktl_perception/launch/color_picker.launch deleted file mode 100644 index a29fdb9d4..000000000 --- a/rktl_perception/launch/color_picker.launch +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/rktl_perception/launch/field_view.launch b/rktl_perception/launch/field_view.launch deleted file mode 100644 index 99e562212..000000000 --- a/rktl_perception/launch/field_view.launch +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/rktl_perception/launch/focus_assist.launch b/rktl_perception/launch/focus_assist.launch deleted file mode 100644 index af0d3bcc1..000000000 --- a/rktl_perception/launch/focus_assist.launch +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/rktl_perception/nodes/README.md b/rktl_perception/nodes/README.md deleted file mode 100644 index eb86e76fe..000000000 --- a/rktl_perception/nodes/README.md +++ /dev/null @@ -1,175 +0,0 @@ -# ROS Nodes - -These are the [ROS Nodes](http://wiki.ros.org/Nodes) provided by this package. -Most likely, you will use a launch file to run these. You can also run them -by using `rosrun`: - -``` -rosrun rktl_perception -``` - -```{contents} ROS Nodes in this package -:depth: 2 -:backlinks: top -:local: true -``` - ---- - -## ball_detection - -C++ Node. Uses color thresholding to find a vector pointing from the camera in -the direction of the ball. - -### Subscribed Topics - -- `image_rect_color` ([image_transport Camera](https://wiki.ros.org/image_transport)): - Full-color camera feed. - -### Published Topics - -- `ball_vec` ([geometry_msgs/Vector3Stamped](/geometry_msgs/html/msg/Vector3Stamped.html#http://)): - The $x$ and $y$ components of the vector comprise the the unit vector which - passes from the camera center to the detected ball. The $z$ component is - the largest contour area detected (the size of the ball as it appears to - the camera). -- `threshold_img` ([image_transport Camera](https://wiki.ros.org/image_transport)): - The black-and-white output of the thresholding. Only published to if the - `publishThresh` parameter is set to true. - -### Parameters - -- `publishThresh` (bool, default: false): Whether or not to publish the image - after thresholding is applied. -- `minHue` (int, default: `60`): The lower value of the hue to threshold on a - 0 - 255 scale. -- `minSat` (int, default: `135`): The lower value of the saturation to - threshold on a 0 - 255 scale. -- `minVib` (int, default: `50`): The lower value of the vibrance to threshold - on a 0 - 255 scale. -- `maxHue` (int, default: `150`): The upper value of the hue to threshold on a - 0 - 255 scale. -- `maxSat` (int, default: `225`): The upper value of the saturation to - threshold on a 0 - 255 scale. -- `maxVib` (int, default: `255`): The upper value of the vibrance to threshold - on a 0 - 255 scale. - ---- - -## focus_vis - -C++ Node. Publishes a version of `image_color_rect` that has had Canny Edge -Detection applied. Useful when using a -[siemens star](https://en.wikipedia.org/wiki/Siemens_star) to get the focus of -the camera just right. - -### Subscribed Topics - -- `image_rect_color` ([image_transport Camera](https://wiki.ros.org/image_transport)): - Full-color camera feed. - -### Published Topics - -- `edge_dectection` ([image_transport Camera](https://wiki.ros.org/image_transport)): - The black-and-white output of the edge detection. - ---- - -## localizer - -C++ Node. Calculates the location of all objects in the cameras' view. -Republishes AprilTag and ball detections (received in "camera space") into a -common reference frame for all cameras (referred to as "world space"). The -measurements used to put the camera into world space are "averaged out" over a -series of measurements to reduce noise. - -Multiple topics are published to, given by the `pub_topics` parameter. This -parameter is interpreted as key/value pairs, where the key is an -[AprilTag](https://wiki.ros.org/apriltag_ros) ID or the IDs of an AprilTag -bundle and the value is the name of the topic that should be published to. In -the case of a bundle, the tag IDs are given in ascending order, delimited by -commas (e.g. `"0,1,2,3"`). - -For the inclined, "camera space" is defined as the space whose origin is defined -as the center of the camera, where the $x$-axis points to the right of the -image, the $y$-axis points towards the top of the image, and the camera looks -down the positive $z$-axis (this is the same convention that OpenCV uses). -"World space" is defined as the space whose origin is the center of the field, -where the $x$-axis points towards the "right-hand" goals, the $y$-axis points -towards the "top" sideline, and the $z$-axis points directly up. - -### Subscribed Topics - -- `tag_detections` ([apriltag_ros/AprilTagDetectionArray](/apriltag_ros/html/msg/AprilTagDetectionArray.html#http://)): - Output of an AprilTag detection node. The name of this topic can be easily - adjusted by modifying the `dectection_topic` parameter. -- `ball_vector` ([geometry_msgs/Vector3Stamped](/geometry_msgs/html/msg/Vector3Stamped.html#http://)): - Output of the [`ball_detection`](#ball-detection) node. The name of this - topic can be easily adjusted by modifying the `ball_sub_topic` parameter. - -### Published Topics - -- `{pub_topic}` ([geometry_msgs/PoseWithCovarianceStamped](/geometry_msgs/html/msg/PoseWithCovarianceStamped.html#http://)): - The position/orientation of the camera in world space. -- `{pub_topics.values}` ([geometry_msgs/PoseWithCovarianceStamped](/geometry_msgs/html/msg/PoseWithCovarianceStamped.html#http://)): - The position/orientation of the given objects in world space. `pub_topics` - is a dictionary (`key`/`value` pairs) where each detections of AprilTag - with ID `key` is published on the topic named `value`. - -### Parameters - -- `dectection_topic` (string, default: `"tag_detections"`): Name of topic - subscribed to, published to by an AprilTag detection node. -- `origin_id` (string, default: `"0"`): ID of the AprilTag or AprilTag bundle - that defines the origin of world space, i.e. the center of the field. -- `pub_topic` (string) Topic name to publish camera pose to. -- `pub_topics` (dictionary): `key`/`value` pairs where each detections of - AprilTag with ID `key` is published on the topic named `value`. -- `ball_sub_topic` (string, default: `"ball_vector"`): Name of topic - subscribed to, published to by a [`ball_detection`](#ball-detection) node. -- `ball_pub_topic` (string, default: `"ball_pos"`): Name of topic - published containing the position of the ball in world space. -- `ball_radius` (float, default: `0.05`): Radius of the ball, in meters. -- `buffer_size` (int, default: `10`): Number of elements used in the "averaging" - of the camera pose. -- `queue_size` (int, default: `100`): Size of the the queue for the publishers. - ---- - -## pose_to_tf - -Publishes an output of the [localizer](#localizer) node onto the [tf tree](https://wiki.ros.org/tf2). - -### Subscribed Topics - -- `pose` ([geometry_msgs/PoseWithCovarianceStamped](/geometry_msgs/html/msg/PoseWithCovarianceStamped.html#http://)): - Data to republish onto the TF tree. - -### Parameters - -- `~cam_frame_id` (string, default: `"cam0"`): Frame ID of the camera. - ---- - -## projector - -Create a depth map to project camera data onto ground plane. - -### Subscribed Topics - -- `projector` ([sensor_msgs/CameraInfo](/sensor_msgs/html/msg/CameraInfo.html#http://)): - Camera Info, containing the projection matrix used to re-project the image - onto the plane. - -### Published Topics - -- `depth_rect` ([sensor_msgs/Image](/sensor_msgs/html/msg/Image.html#http://)): - Calculated depth of each pixel in the camera. - -### Parameters - -- `~ground_height` (float, default: `0.0`): Height of the ground, in meters. -- `~update_period` (float, default: `1`): Rate at which to re-compute depth map, - in updates per second. - ---- diff --git a/rktl_perception/package.xml b/rktl_perception/package.xml deleted file mode 100644 index f19c535c9..000000000 --- a/rktl_perception/package.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - rktl_perception - 0.0.0 - Perception for ARC Rocket League project - Autonomous Robotics Club of Purdue - BSD 3 Clause - http://www.purduearc.com - James Baxter - Daniel Gerblick - AJ Ernandes - - catkin - roscpp - pointgrey_camera_driver - image_proc - apriltag_ros - tf2 - tf2_ros - image_transport - cv_bridge - nodelet - camera_calibration - rviz - diff --git a/rktl_perception/src/ball_detection_node.cpp b/rktl_perception/src/ball_detection_node.cpp deleted file mode 100644 index 79fa76099..000000000 --- a/rktl_perception/src/ball_detection_node.cpp +++ /dev/null @@ -1,19 +0,0 @@ -/* Node to detect the ball's position on the field. - * License: - * BSD 3-Clause License - * Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - * All rights reserved. - */ - -#include - -int main(int argc, char* argv[]) { - ros::init(argc, argv, "ball_detection"); - - ros::NodeHandle nh; - ros::NodeHandle pnh("~"); - rktl_perception::BallDetection ballDetection(nh, pnh); - - ros::spin(); - return 0; -} diff --git a/rktl_planner/CMakeLists.txt b/rktl_planner/CMakeLists.txt deleted file mode 100644 index ce97bf1be..000000000 --- a/rktl_planner/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(rktl_planner) - -# find catkin so we can use our macros -find_package(catkin REQUIRED COMPONENTS - rospy - rktl_msgs - std_srvs - std_msgs - geometry_msgs - message_generation -) - -## Uncomment this if the package has a setup.py. This macro ensures -## modules and global scripts declared therein get installed -## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html -catkin_python_setup() - -# msg and srv -add_service_files( - FILES - CreateBezierPath.srv -) - -generate_messages( - DEPENDENCIES - rktl_msgs - std_srvs - std_msgs - geometry_msgs -) - -# generate variables for installation -catkin_package() diff --git a/rktl_planner/config/path_follower.yaml b/rktl_planner/config/path_follower.yaml deleted file mode 100644 index fb97b2333..000000000 --- a/rktl_planner/config/path_follower.yaml +++ /dev/null @@ -1,3 +0,0 @@ -lookahead_dist: 0.25 -lookahead_gain: 0.1 -lookahead_pnts: -1 diff --git a/rktl_planner/config/path_planner.yaml b/rktl_planner/config/path_planner.yaml deleted file mode 100644 index a07c92493..000000000 --- a/rktl_planner/config/path_planner.yaml +++ /dev/null @@ -1 +0,0 @@ -planner_type: 'complex' \ No newline at end of file diff --git a/rktl_planner/config/patrol_planner.yaml b/rktl_planner/config/patrol_planner.yaml deleted file mode 100644 index 10917f6df..000000000 --- a/rktl_planner/config/patrol_planner.yaml +++ /dev/null @@ -1,25 +0,0 @@ -# tuned params for no prediction at 0.15 delay -wall_avoidance: - reverse_time: 0.125 - distance_margin: 0.375 - heading_margin: 1.57 - -patrol_wall_dist: 0.75 - -curvature_gain: - kp: 2.0 - kd: 0.05 - falloff: 0.25 - -scoring: - car_lookahead_dist: 0.75 - goal_depth_target: 0.05 - -# X coordinate to switch to defense -defensive_line: -0.5 - -# Y coordinate to switch to reverse -reverse_line: 0.0 - -speed: 1.75 -attempt_timeout: 4.0 diff --git a/rktl_planner/setup.py b/rktl_planner/setup.py deleted file mode 100755 index a4c08b610..000000000 --- a/rktl_planner/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python3 - -from distutils.core import setup -from catkin_pkg.python_setup import generate_distutils_setup - -setup_args = generate_distutils_setup( - packages=['rktl_planner'], - package_dir={'': 'src'} -) - -setup(**setup_args) diff --git a/rktl_planner/src/rktl_planner/__init__.py b/rktl_planner/src/rktl_planner/__init__.py deleted file mode 100755 index 9114b66b0..000000000 --- a/rktl_planner/src/rktl_planner/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Helper modules for the local planner package. - -License: - BSD 3-Clause License - Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) - All rights reserved. -""" - -from rktl_planner.bezier_curve import BezierCurve -from rktl_planner.bezier_path import BezierPath - -__all__ = ['BezierCurve', 'BezierPath',] \ No newline at end of file diff --git a/rktl_planner/srv/CreateBezierPath.srv b/rktl_planner/srv/CreateBezierPath.srv deleted file mode 100644 index 468e112fe..000000000 --- a/rktl_planner/srv/CreateBezierPath.srv +++ /dev/null @@ -1,8 +0,0 @@ -float64 velocity -geometry_msgs/Pose[] target_poses -std_msgs/Duration[] target_durations -std_msgs/Duration bezier_segment_duration -std_msgs/Duration linear_segment_duration ---- -rktl_msgs/BezierPath[] bezier_paths -rktl_msgs/Path linear_path diff --git a/rktl_sim/CMakeLists.txt b/rktl_sim/CMakeLists.txt deleted file mode 100644 index 460ecbc03..000000000 --- a/rktl_sim/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -cmake_minimum_required(VERSION 3.0.2) -project(rktl_sim) - -# find catkin so we can use our macros -find_package(catkin REQUIRED) - -# install python module(s) -catkin_python_setup() - -# generate variables for installation -catkin_package() - -# tests -if(CATKIN_ENABLE_TESTING) - find_package(roslaunch REQUIRED) - roslaunch_add_file_check(launch) - find_package(rostest REQUIRED) - add_rostest(test/test_car.test) -endif() diff --git a/rktl_sim/config/simulation.yaml b/rktl_sim/config/simulation.yaml deleted file mode 100644 index 60be00b58..000000000 --- a/rktl_sim/config/simulation.yaml +++ /dev/null @@ -1,103 +0,0 @@ -rate: 10 -spawn_height: 0.06 - -car: - # init_pose: - # pos: [0.5, 0.0, 0.06] - sensor_noise: - pos: [0.01, 0.01, 0.00] - orient: [0.0, 0.0, 0.09] - dropout: 0.15 - -ball: - init_speed: 0.0 - # init_pose: - # pos: [0.0, 0.5, 0.06] - # orient: [0.0, 0.0, 0.0] - sensor_noise: - pos: [0.01, 0.01, 0.0] - orient: [0.0, 0.0, 0.0] - dropout: 0.1 - -# -# Simulation settings -# - -rate: 10 -spawn_height: 0.06 - -# Directly export to setPhysicsEngineParameters() -engine: - fixedTimeStep: 0.001 - restitutionVelocityThreshold: 0.0001 - -# Directly exported to changeDynamics() -dynamics: - ball: - mass: 0.2 - lateralFriction: 0.4 - restitution: 0.7 - rollingFriction: 0.0001 - spinningFriction: 0.001 - contactDamping: 0.0 - contactStiffness: 0.0 - collisionMargin: 0.0 - car: - mass: 0.0 - lateralFriction: 0.0 - restitution: 0.0 - rollingFriction: 0.0 - spinningFriction: 0.0 - contactDamping: 0.0 - contactStiffness: 0.0 - collisionMargin: 0.0 - walls: - mass: 0.0 - lateralFriction: 0.0 - restitution: 0.0 - rollingFriction: 0.0 - spinningFriction: 0.0 - contactDamping: 0.0 - contactStiffness: 0.0 - collisionMargin: 0.0 - floor: - mass: 0.0 - lateralFriction: 0.0 - restitution: 0.0 - rollingFriction: 0.0 - spinningFriction: 0.0 - contactDamping: 0.0 - contactStiffness: 0.0 - collisionMargin: 0.0 - -# -# Object instances -# - -sensor_noise: - car: - pos: [0.01, 0.01, 0.00] - orient: [0.0, 0.0, 0.09] - dropout: 0.0 - ball: - pos: [0.01, 0.01, 0.0] - orient: [0.0, 0.0, 0.0] - dropout: 0.0 - -cars: - - name: "car0" - # init_pose: - # pos: [0.5, 0.0, 0.06] - - # - name: "car1" - # # init_pose: - # # pos: [0.5, 0.0, 0.06] - # sensor_noise: - # pos: [0.01, 0.01, 0.00] - # orient: [0.0, 0.0, 0.09] - -ball: - init_speed: 0.0 - # init_pose: - # pos: [0.0, 0.5, 0.06] - # orient: [0.0, 0.0, 0.0] \ No newline at end of file diff --git a/rktl_sim/config/visualization.yaml b/rktl_sim/config/visualization.yaml deleted file mode 100644 index d88d24526..000000000 --- a/rktl_sim/config/visualization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -rate: 10 -cars: - body_width: 0.0762 # (3 in) - body_length: 0.2032 # (8 in) diff --git a/rktl_sim/setup.py b/rktl_sim/setup.py deleted file mode 100644 index cfc71ded9..000000000 --- a/rktl_sim/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python3 - -from distutils.core import setup -from catkin_pkg.python_setup import generate_distutils_setup - -setup_args = generate_distutils_setup( - packages=['simulator', 'visualizer'], - package_dir={'': 'src'} -) - -setup(**setup_args) diff --git a/src/image_pipeline/.github/workflows/basic-build-ci.yaml b/src/image_pipeline/.github/workflows/basic-build-ci.yaml new file mode 100644 index 000000000..25448b3fe --- /dev/null +++ b/src/image_pipeline/.github/workflows/basic-build-ci.yaml @@ -0,0 +1,39 @@ +/**: + ros__parameters: +name: Basic Build Workflow + +on: + push: + branches: [rolling] + pull_request: + types: [opened, reopened, synchronize] + +jobs: + build-rolling: + runs-on: ubuntu-latest + strategy: + fail-fast: false + container: + image: osrf/ros2:testing + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Create Workspace + run: | + mkdir src_tmp + mv `find -maxdepth 1 -not -name . -not -name src_tmp` src_tmp/ + mv src_tmp/ src/ + - name: Install Prerequisites + run: | + bash -c 'source /opt/ros/$ROS_DISTRO/setup.bash; \ + apt-get update && apt-get upgrade -y && rosdep update; \ + rosdep install --from-paths src --ignore-src -y' + - name: Build Workspace + run: | + bash -c 'source /opt/ros/$ROS_DISTRO/setup.bash; \ + colcon build' + - name: Run Tests + run: | + bash -c 'source /opt/ros/$ROS_DISTRO/setup.bash; \ + colcon test; \ + colcon test-result --verbose' diff --git a/src/image_pipeline/.gitignore b/src/image_pipeline/.gitignore new file mode 100644 index 000000000..72723e50a --- /dev/null +++ b/src/image_pipeline/.gitignore @@ -0,0 +1 @@ +*pyc diff --git a/src/image_pipeline/CONTRIBUTING.md b/src/image_pipeline/CONTRIBUTING.md new file mode 100644 index 000000000..5e190bdca --- /dev/null +++ b/src/image_pipeline/CONTRIBUTING.md @@ -0,0 +1,3 @@ +Any contribution that you make to this repository will +be under the BSD license 2.0, as dictated by that +[license](https://opensource.org/licenses/BSD-3-Clause). diff --git a/src/image_pipeline/LICENSE b/src/image_pipeline/LICENSE new file mode 100644 index 000000000..09078518e --- /dev/null +++ b/src/image_pipeline/LICENSE @@ -0,0 +1,239 @@ +Portions of the code are licensed under the BSD License 2.0: + +All rights reserved. + +Software License Agreement (BSD License 2.0) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * 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 OWNER 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. + + +Portions of the code are licensed under the Apaache License 2.0: + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +>>>>>>> Updating copyrights and adding dual-licensing. diff --git a/src/image_pipeline/README.md b/src/image_pipeline/README.md new file mode 100644 index 000000000..0835e5af0 --- /dev/null +++ b/src/image_pipeline/README.md @@ -0,0 +1,10 @@ +image_pipeline +============== + +[![](https://github.com/ros-perception/image_pipeline/workflows/Basic%20Build%20Workflow/badge.svg?branch=ros2)](https://github.com/ros-perception/image_pipeline/actions) + +This package fills the gap between getting raw images from a camera driver and higher-level vision processing. + +For more information on this metapackage and underlying packages, please see [the ROS wiki entry](http://wiki.ros.org/image_pipeline). + +For examples, see the [image_pipeline tutorials entry](http://wiki.ros.org/image_pipeline/Tutorials) on the ROS Wiki. diff --git a/src/image_pipeline/camera_calibration/CHANGELOG.rst b/src/image_pipeline/camera_calibration/CHANGELOG.rst new file mode 100644 index 000000000..4feb0f95d --- /dev/null +++ b/src/image_pipeline/camera_calibration/CHANGELOG.rst @@ -0,0 +1,248 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package camera_calibration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +4.0.0 (2022-12-24) +------------------ +* [backport iron] ROS 2: Added more aruco dicts, fixed aruco linerror bug (`#873 `_) (`#890 `_) + backport `#873 `_ +* [backport iron] ROS 2: Fixing thrown Exception in camerachecker.py (`#871 `_) (`#888 `_) + backport #`#871 `_ +* add myself as a maintainer (`#846 `_) +* fix threading shutdown +* use correct synchronous service call +* use remap rules instead of parameters for services +* remove duplicated definition of on_model_change +* fix service check +* remove commented code +* Fix QoS incompatibility camera_calibration ROS2 +* perform calibration in another thread +* Contributors: Alejandro Hernández Cordero, Christian Rauch, Kenji Brameld, Michael Ferguson, Michal Wojcik + +3.0.1 (2022-12-04) +------------------ +* add python3-opencv to camera calibration dependency +* port changes from `#755 `_ to rolling branch +* Contributors: Kenji Brameld + +3.0.0 (2022-04-29) +------------------ +* Some small fixes noticed while reviewing. +* fix premature camera model change in camera_calibration +* Fix shebang lines for noetic python3 +* Update fisheye distortion model definition +* Fix calibration yaml formatting (`#580 `_) (`#585 `_) +* updated linear_error function to handle partial board views (`#561 `_) +* Fix missing detected checkerboard points (`#558 `_) +* ChArUco board, Noetic (`#549 `_) +* fix `#503 `_: (`#545 `_) +* Minimal Noetic (`#530 `_) +* Apply `#509 `_ and `#526 `_ to Noetic Branch (`#528 `_) +* Add Fisheye calibration tool (`#440 `_) +* camera_calibration: Improve YAML formatting, make config dumping methods static (`#438 `_) +* camera_calibration: Fix all-zero distortion coeffs returned for a rational_polynomial model (`#433 `_) +* Make sure 'calibrate' button works even if not receiving images anymore +* Add a comment +* Replace deque with a modified Queue, add --queue-size param +* Remove print statement +* Cosmetic changes +* Add max-chessboard-speed option to allow more accurate calibration of rolling shutter cameras. +* revert back +* added missing imports +* update pytest.ini +* fixes to pass tests +* rebase change +* implemented fisheye mono and stereo calibration based on the melodic branch +* trimmed whitespace at line endings +* Update camera_calibration setup.cfg to use underscores (`#688 `_) +* Add maintainer (`#667 `_) +* Fixed crash when rosargs are given (`#597 `_) +* Contributors: Chris Lalancette, David Torres Ocaña, DavidTorresOcana, Gabor Soros, Jacob Perron, John Stechschulte, Joshua Whitley, Martin Valgur, Matthijs den Toom, Michael Carroll, Patrick Musau, Photon, Spiros Evangelatos, Victor Dubois, jaiveersinghNV, soeroesg + +2.2.1 (2020-08-27) +------------------ +* remove email blasts from steve macenski (`#596 `_) +* Add pytest.ini to fix warning (`#584 `_) + Fixes the following warning: + Warning: The 'junit_family' default value will change to 'xunit2' in pytest 6.0. + Add 'junit_family=xunit1' to your pytest.ini file to keep the current format in future versions of pytest and silence this warning. +* [Foxy] Use ament_auto Macros (`#573 `_) +* Contributors: Jacob Perron, Joshua Whitley, Steve Macenski + +2.2.0 (2020-07-27) +------------------ +* Removed basestring (no longer exists in new python 3 version). (`#554 `_) + Fixes `#551 `_ +* Initial ROS2 commit. +* Contributors: Michael Carroll, PfeifferMicha + +1.12.23 (2018-05-10) +-------------------- +* camera_checker: Ensure cols + rows are in correct order (`#319 `_) + Without this commit, specifying a smaller column than row size lead to + huge reported errors: + ``` + $ rosrun camera_calibration cameracheck.py --size 6x7 --square 0.0495 + Linearity RMS Error: 13.545 Pixels Reprojection RMS Error: 22.766 Pixels + $ rosrun camera_calibration cameracheck.py --size 7x6 --square 0.0495 + Linearity RMS Error: 0.092 Pixels Reprojection RMS Error: 0.083 Pixels + ``` + This commit switches columns and rows around if necessary. +* Contributors: Martin Günther + +1.12.22 (2017-12-08) +-------------------- +* Changed flags CV_LOAD_IMAGE_COLOR by IMREAD_COLOR to adapt to Opencv3. (`#252 `_) +* Fixed stereo calibration problem with chessboard with the same number of rows and cols by rotating the corners to same direction. +* Contributors: jbosch + +1.12.21 (2017-11-05) +-------------------- +* re-add the calibration nodes but now using the Python modules. + Fixes `#298 `_ +* Move nodes to Python module. +* Contributors: Vincent Rabaud + +1.12.20 (2017-04-30) +-------------------- +* properly save bytes buffer as such + This is useful for Python 3 and fixes `#256 `_. +* Get tests slightly looser. + OpenCV 3.2 gives slightly different results apparently. +* Use floor division where necessary. (`#247 `_) +* Fix and Improve Camera Calibration Checker Node (`#254 `_) + * Fix according to calibrator.py API + * Add approximate to cameracheck +* Force first corner off chessboard to be uppler left. + Fixes `#140 `_ +* fix doc jobs + This is a proper fix for `#233 `_ +* During stereo calibration check that the number of corners detected in the left and right images are the same. This fixes `ros-perception/image_pipeline#225 `_ +* Contributors: Leonard Gerard, Martin Peris, Vincent Rabaud, hgaiser + +1.12.19 (2016-07-24) +-------------------- +* Fix array check in camerachecky.py + This closes `#205 `_ +* Contributors: Vincent Rabaud + +1.12.18 (2016-07-12) +-------------------- + +1.12.17 (2016-07-11) +-------------------- +* fix typo np -> numpy +* fix failing tests +* Contributors: Shingo Kitagawa, Vincent Rabaud + +1.12.16 (2016-03-19) +-------------------- +* clean OpenCV dependency in package.xml +* Contributors: Vincent Rabaud + +1.12.15 (2016-01-17) +-------------------- +* better 16 handling in mkgray + This re-uses `#150 `_ and therefore closes `#150 `_ +* fix OpenCV2 compatibility +* fix tests with OpenCV3 +* [Calibrator]: add yaml file with calibration data in output +* Contributors: Vincent Rabaud, sambrose + +1.12.14 (2015-07-22) +-------------------- +* remove camera_hammer and install Python nodes properly + camera_hammer was just a test for camera info, nothing to do with + calibration. Plus the test was basic. +* Correct three errors that prevented the node to work properly. +* Contributors: Filippo Basso, Vincent Rabaud + +1.12.13 (2015-04-06) +-------------------- +* replace Queue by deque of fixed size for simplicity + That is a potential fix for `#112 `_ +* Contributors: Vincent Rabaud + +1.12.12 (2014-12-31) +-------------------- +* try to improve `#112 `_ +* Contributors: Vincent Rabaud + +1.12.11 (2014-10-26) +-------------------- + +1.12.10 (2014-09-28) +-------------------- +* Update calibrator.py + bugfix: stereo calibrator crashed after the signature of the method for the computation of the epipolar error changed but the function call was not updated +* Contributors: Volker Grabe + +1.12.9 (2014-09-21) +------------------- +* fix bad Python +* only analyze the latest image + fixes `#97 `_ +* flips width and height during resize to give correct aspect ratio +* Contributors: Russell Toris, Vincent Rabaud + +1.12.8 (2014-08-19) +------------------- +* install scripts in the local bin (they are now rosrun-able again) + fixes `#93 `_ +* fix default Constructor for OpenCV flags + this does not change anything in practice as the flag is set by the node. + It just fixes the test. +* Contributors: Vincent Rabaud + +1.12.6 (2014-07-27) +------------------- +* make sure the GUI is started in its processing thread and fix a typo + This fully fixes `#85 `_ +* fix bad call to save an image +* have display be in its own thread + that could be a fix for `#85 `_ +* fix bad usage of Numpy + fixes `#89 `_ +* fix asymmetric circle calibration + fixes `#35 `_ +* add more tests +* improve unittests to include all patterns +* install Python scripts properly + and fixes `#86 `_ +* fix typo that leads to segfault + fixes `#84 `_ +* also print self.report() on calibrate ... allows to use the params without having to commit them (e.g. for extrensic calibration between to cameras not used as stereo pair) +* fixes `#76 `_ + Move Python approximate time synchronizer to ros_comm +* remove all trace of cv in Python (use cv2) +* remove deprecated file (as mentioned in its help) +* fixes `#25 `_ + This is just removing deprecated options that were around since diamondback +* fixes `#74 `_ + calibrator.py is now using the cv2 only API when using cv_bridge. + The API got changed too but it seems to only be used internally. +* Contributors: Vincent Rabaud, ahb + +1.12.5 (2014-05-11) +------------------- +* Fix `#68 `_: StringIO issues in calibrator.py +* fix architecture independent +* Contributors: Miquel Massot, Vincent Rabaud + +1.12.4 (2014-04-28) +------------------- + +1.12.3 (2014-04-12) +------------------- +* camera_calibration: Fix Python import order +* Contributors: Scott K Logan + +1.12.2 (2014-04-08) +------------------- +* Fixes a typo on stereo camera info service calls + Script works after correcting the call names. +* Contributors: JoonasMelin + +1.11.4 (2013-11-23 13:10:55 +0100) +---------------------------------- +- add visualization during calibration and several calibration flags (#48) diff --git a/src/image_pipeline/camera_calibration/button.jpg b/src/image_pipeline/camera_calibration/button.jpg new file mode 100644 index 000000000..494a6f6fc Binary files /dev/null and b/src/image_pipeline/camera_calibration/button.jpg differ diff --git a/src/image_pipeline/camera_calibration/doc/conf.py b/src/image_pipeline/camera_calibration/doc/conf.py new file mode 100644 index 000000000..82ad7fe0b --- /dev/null +++ b/src/image_pipeline/camera_calibration/doc/conf.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +# +# camera_calibration documentation build configuration file, created by +# sphinx-quickstart on Mon Jun 1 14:21:53 2009. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.pngmath'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'camera_calibration' +copyright = '2009, Willow Garage, Inc.' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'camera_calibrationdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'camera_calibration.tex', 'stereo\\_utils Documentation', + 'James Bowman', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'http://docs.python.org/': None, + 'http://docs.scipy.org/doc/numpy' : None, + 'http://www.ros.org/doc/api/tf/html/python/' : None + } diff --git a/src/image_pipeline/camera_calibration/doc/index.rst b/src/image_pipeline/camera_calibration/doc/index.rst new file mode 100644 index 000000000..741e4e5b7 --- /dev/null +++ b/src/image_pipeline/camera_calibration/doc/index.rst @@ -0,0 +1,18 @@ +camera_calibration +================== + +The camera_calibration package contains a user-friendly calibration tool, +cameracalibrator. This tool uses the following Python classes, which +conveniently hide some of the complexities of using OpenCV's calibration +process and chessboard detection, and the details of constructing a ROS +CameraInfo message. These classes are documented here for people who +need to extend or make a new calibration tool. + +For details on the camera model and camera calibration process, see +http://docs.opencv.org/master/d9/d0c/group__calib3d.html + +.. autoclass:: camera_calibration.calibrator.MonoCalibrator + :members: + +.. autoclass:: camera_calibration.calibrator.StereoCalibrator + :members: diff --git a/src/image_pipeline/camera_calibration/mainpage.dox b/src/image_pipeline/camera_calibration/mainpage.dox new file mode 100644 index 000000000..dbcfe690e --- /dev/null +++ b/src/image_pipeline/camera_calibration/mainpage.dox @@ -0,0 +1,59 @@ +/** +\mainpage +\htmlinclude manifest.html + +\b The camera_calibration package contains tools for calibrating monocular and stereo cameras. + +\section codeapi Code API + +camera_calibration does not have a code API. + +\section rosapi ROS API + +List of nodes: +- \b calibrationnode + + + +
+ +\subsection node_name calibrationnode + +calibrationnode subscribes to ROS raw image topics, and presents a +calibration window. It can run in both monocular and stereo modes. +The calibration window shows the current images from the cameras, +highlighting the checkerboard. When the user presses the "CALIBRATE" +button, the node computes the camera calibration parameters. When the +user clicks "UPLOAD", the node uploads these new calibration parameters +to the camera driver using a service call. + +\subsubsection Usage +\verbatim +$ node_type1 [standard ROS args] +\endverbatim + +\par Example + +\verbatim +$ rosrun camera_calibration cal.py right:=/my_stereo/right/image_raw left:=/my_stereo/left/image_raw left_camera:=/my_stereo/left right_camera:=/my_stereo/right +\endverbatim + + +\subsubsection topics ROS topics + +Subscribes to: +- \b "left": [sensor_msgs/Image] left raw image topic, for stereo cameras +- \b "right": [sensor_msgs/Image] left raw image topic, for stereo cameras +- \b "image": [sensor_msgs/Image] raw image topic, for monocular cameras + +Makes service calls to: + +\subsubsection services ROS services +- \b "foo_service": [std_srvs/FooType] description of foo_service +- \b "camera/set_camera_info": [sensor_msgs/SetCameraInfo] node of the camera for monocular +- \b "left_camera/set_camera_info": [sensor_msgs/SetCameraInfo] node of the left stereo camera +- \b "right_camera/set_camera_info": [sensor_msgs/SetCameraInfo] node of the left stereo camera + + + +*/ diff --git a/src/image_pipeline/camera_calibration/package.xml b/src/image_pipeline/camera_calibration/package.xml new file mode 100644 index 000000000..e9335bf53 --- /dev/null +++ b/src/image_pipeline/camera_calibration/package.xml @@ -0,0 +1,40 @@ + + + + camera_calibration + 4.0.0 + + camera_calibration allows easy calibration of monocular or stereo + cameras using a checkerboard calibration target. + + + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/camera_calibration/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + James Bowman + Patrick Mihelich + + cv_bridge + image_geometry + message_filters + python3-opencv + rclpy + std_srvs + sensor_msgs + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + python3-requests + + + ament_python + + diff --git a/src/image_pipeline/camera_calibration/pytest.ini b/src/image_pipeline/camera_calibration/pytest.ini new file mode 100644 index 000000000..fe55d2ed6 --- /dev/null +++ b/src/image_pipeline/camera_calibration/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +junit_family=xunit2 diff --git a/src/image_pipeline/camera_calibration/resource/camera_calibration b/src/image_pipeline/camera_calibration/resource/camera_calibration new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/camera_calibration/rosdoc.yaml b/src/image_pipeline/camera_calibration/rosdoc.yaml new file mode 100644 index 000000000..9dee22792 --- /dev/null +++ b/src/image_pipeline/camera_calibration/rosdoc.yaml @@ -0,0 +1,6 @@ +/**: + ros__parameters: + - builder: sphinx + name: Python API + output_dir: python + sphinx_root_dir: doc diff --git a/src/image_pipeline/camera_calibration/scripts/tarfile_calibration.py b/src/image_pipeline/camera_calibration/scripts/tarfile_calibration.py new file mode 100755 index 000000000..059f2d303 --- /dev/null +++ b/src/image_pipeline/camera_calibration/scripts/tarfile_calibration.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python +# +# Software License Agreement (BSD License) +# +# Copyright (c) 2009, Willow Garage, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os +import numpy + +import cv2 +import cv_bridge +import tarfile + +from camera_calibration.calibrator import MonoCalibrator, StereoCalibrator, CalibrationException, ChessboardInfo + +import rclpy +import sensor_msgs.srv + +def display(win_name, img): + cv2.namedWindow(win_name, cv2.WINDOW_NORMAL) + cv2.imshow( win_name, numpy.asarray( img[:,:] )) + k = cv2.waitKey(0) + cv2.destroyWindow(win_name) + if k in [27, ord('q')]: + rclpy.shutdown() + + +def cal_from_tarfile(boards, tarname, mono = False, upload = False, calib_flags = 0, visualize = False, alpha=1.0): + if mono: + calibrator = MonoCalibrator(boards, calib_flags) + else: + calibrator = StereoCalibrator(boards, calib_flags) + + calibrator.do_tarfile_calibration(tarname) + + print(calibrator.ost()) + + if upload: + info = calibrator.as_message() + if mono: + set_camera_info_service = rclpy.ServiceProxy("%s/set_camera_info" % "camera", sensor_msgs.srv.SetCameraInfo) + + response = set_camera_info_service.call(info) + if not response.success: + raise RuntimeError("connected to set_camera_info service, but failed setting camera_info") + else: + set_left_camera_info_service = rclpy.ServiceProxy("%s/set_camera_info" % "left_camera", sensor_msgs.srv.SetCameraInfo) + set_right_camera_info_service = rclpy.ServiceProxy("%s/set_camera_info" % "right_camera", sensor_msgs.srv.SetCameraInfo) + + response1 = set_left_camera_info_service(info[0]) + response2 = set_right_camera_info_service(info[1]) + if not (response1.result().success and response2.result().success): + raise RuntimeError("connected to set_camera_info service, but failed setting camera_info") + + if visualize: + + #Show rectified images + calibrator.set_alpha(alpha) + + archive = tarfile.open(tarname, 'r') + if mono: + for f in archive.getnames(): + if f.startswith('left') and (f.endswith('.pgm') or f.endswith('png')): + filedata = archive.extractfile(f).read() + file_bytes = numpy.asarray(bytearray(filedata), dtype=numpy.uint8) + im=cv2.imdecode(file_bytes,cv2.IMREAD_COLOR) + + bridge = cv_bridge.CvBridge() + try: + msg=bridge.cv2_to_imgmsg(im, "bgr8") + except cv_bridge.CvBridgeError as e: + print(e) + + #handle msg returns the recitifed image with corner detection once camera is calibrated. + drawable=calibrator.handle_msg(msg) + vis=numpy.asarray( drawable.scrib[:,:]) + #Display. Name of window:f + display(f, vis) + else: + limages = [ f for f in archive.getnames() if (f.startswith('left') and (f.endswith('pgm') or f.endswith('png'))) ] + limages.sort() + rimages = [ f for f in archive.getnames() if (f.startswith('right') and (f.endswith('pgm') or f.endswith('png'))) ] + rimages.sort() + + if not len(limages) == len(rimages): + raise RuntimeError("Left, right images don't match. %d left images, %d right" % (len(limages), len(rimages))) + + for i in range(len(limages)): + l=limages[i] + r=rimages[i] + + if l.startswith('left') and (l.endswith('.pgm') or l.endswith('png')) and r.startswith('right') and (r.endswith('.pgm') or r.endswith('png')): + # LEFT IMAGE + filedata = archive.extractfile(l).read() + file_bytes = numpy.asarray(bytearray(filedata), dtype=numpy.uint8) + im_left=cv2.imdecode(file_bytes,cv2.IMREAD_COLOR) + + bridge = cv_bridge.CvBridge() + try: + msg_left=bridge.cv2_to_imgmsg(im_left, "bgr8") + except cv_bridge.CvBridgeError as e: + print(e) + + #RIGHT IMAGE + filedata = archive.extractfile(r).read() + file_bytes = numpy.asarray(bytearray(filedata), dtype=numpy.uint8) + im_right=cv2.imdecode(file_bytes,cv2.IMREAD_COLOR) + try: + msg_right=bridge.cv2_to_imgmsg(im_right, "bgr8") + except cv_bridge.CvBridgeError as e: + print(e) + + drawable=calibrator.handle_msg([ msg_left,msg_right] ) + + h, w = numpy.asarray(drawable.lscrib[:,:]).shape[:2] + vis = numpy.zeros((h, w*2, 3), numpy.uint8) + vis[:h, :w ,:] = numpy.asarray(drawable.lscrib[:,:]) + vis[:h, w:w*2, :] = numpy.asarray(drawable.rscrib[:,:]) + + display(l+" "+r,vis) + + +if __name__ == '__main__': + from optparse import OptionParser + parser = OptionParser("%prog TARFILE [ opts ]") + parser.add_option("--mono", default=False, action="store_true", dest="mono", + help="Monocular calibration only. Calibrates left images.") + parser.add_option("-s", "--size", default=[], action="append", dest="size", + help="specify chessboard size as NxM [default: 8x6]") + parser.add_option("-q", "--square", default=[], action="append", dest="square", + help="specify chessboard square size in meters [default: 0.108]") + parser.add_option("--upload", default=False, action="store_true", dest="upload", + help="Upload results to camera(s).") + parser.add_option("--fix-principal-point", action="store_true", default=False, + help="fix the principal point at the image center") + parser.add_option("--fix-aspect-ratio", action="store_true", default=False, + help="enforce focal lengths (fx, fy) are equal") + parser.add_option("--zero-tangent-dist", action="store_true", default=False, + help="set tangential distortion coefficients (p1, p2) to zero") + parser.add_option("-k", "--k-coefficients", type="int", default=2, metavar="NUM_COEFFS", + help="number of radial distortion coefficients to use (up to 6, default %default)") + parser.add_option("--visualize", action="store_true", default=False, + help="visualize rectified images after calibration") + parser.add_option("-a", "--alpha", type="float", default=1.0, metavar="ALPHA", + help="zoom for visualization of rectifies images. Ranges from 0 (zoomed in, all pixels in calibrated image are valid) to 1 (zoomed out, all pixels in original image are in calibrated image). default %default)") + + options, args = parser.parse_args() + + if len(options.size) != len(options.square): + parser.error("Number of size and square inputs must be the same!") + + if not options.square: + options.square.append("0.108") + options.size.append("8x6") + + boards = [] + for (sz, sq) in zip(options.size, options.square): + info = ChessboardInfo() + info.dim = float(sq) + size = tuple([int(c) for c in sz.split('x')]) + info.n_cols = size[0] + info.n_rows = size[1] + + boards.append(info) + + if not boards: + parser.error("Must supply at least one chessboard") + + if not args: + parser.error("Must give tarfile name") + if not os.path.exists(args[0]): + parser.error("Tarfile %s does not exist. Please select valid tarfile" % args[0]) + + tarname = args[0] + + num_ks = options.k_coefficients + + calib_flags = 0 + if options.fix_principal_point: + calib_flags |= cv2.CALIB_FIX_PRINCIPAL_POINT + if options.fix_aspect_ratio: + calib_flags |= cv2.CALIB_FIX_ASPECT_RATIO + if options.zero_tangent_dist: + calib_flags |= cv2.CALIB_ZERO_TANGENT_DIST + if (num_ks > 3): + calib_flags |= cv2.CALIB_RATIONAL_MODEL + if (num_ks < 6): + calib_flags |= cv2.CALIB_FIX_K6 + if (num_ks < 5): + calib_flags |= cv2.CALIB_FIX_K5 + if (num_ks < 4): + calib_flags |= cv2.CALIB_FIX_K4 + if (num_ks < 3): + calib_flags |= cv2.CALIB_FIX_K3 + if (num_ks < 2): + calib_flags |= cv2.CALIB_FIX_K2 + if (num_ks < 1): + calib_flags |= cv2.CALIB_FIX_K1 + + cal_from_tarfile(boards, tarname, options.mono, options.upload, calib_flags, options.visualize, options.alpha) diff --git a/src/image_pipeline/camera_calibration/setup.cfg b/src/image_pipeline/camera_calibration/setup.cfg new file mode 100644 index 000000000..0fe247b97 --- /dev/null +++ b/src/image_pipeline/camera_calibration/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/camera_calibration +[install] +install_scripts=$base/lib/camera_calibration diff --git a/src/image_pipeline/camera_calibration/setup.py b/src/image_pipeline/camera_calibration/setup.py new file mode 100644 index 000000000..a261d29d1 --- /dev/null +++ b/src/image_pipeline/camera_calibration/setup.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +from setuptools import setup, find_packages + +PACKAGE_NAME = "camera_calibration" + +setup( + name=PACKAGE_NAME, + version='4.0.0', + packages=["camera_calibration", "camera_calibration.nodes"], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + PACKAGE_NAME]), + ('share/' + PACKAGE_NAME, ['package.xml']), + ], + py_modules=[], + package_dir={'': 'src'}, + install_requires=[ + 'setuptools', + ], + zip_safe=True, + author='James Bowman, Patrick Mihelich', + maintainer='Vincent Rabaud, Steven Macenski, Joshua Whitley', + maintainer_email='vincent.rabaud@gmail.com, stevenmacenski@gmail.com, whitleysoftwareservices@gmail.com', + keywords=['ROS2'], + description='Camera_calibration allows easy calibration of monocular or stereo cameras using a checkerboard calibration target .', + license='BSD', + tests_require=[ + 'pytest', + 'requests' + ], + entry_points={ + 'console_scripts': [ + 'cameracalibrator = camera_calibration.nodes.cameracalibrator:main', + 'cameracheck = camera_calibration.nodes.cameracheck:main', + ], + }, +) diff --git a/src/image_pipeline/camera_calibration/src/camera_calibration/__init__.py b/src/image_pipeline/camera_calibration/src/camera_calibration/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/camera_calibration/src/camera_calibration/calibrator.py b/src/image_pipeline/camera_calibration/src/camera_calibration/calibrator.py new file mode 100644 index 000000000..6f7744612 --- /dev/null +++ b/src/image_pipeline/camera_calibration/src/camera_calibration/calibrator.py @@ -0,0 +1,1449 @@ +#!/usr/bin/env python +# +# Software License Agreement (BSD License) +# +# Copyright (c) 2009, Willow Garage, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +from io import BytesIO +import cv2 +import cv_bridge +import image_geometry +import math +import numpy.linalg +import pickle +import random +import sensor_msgs.msg +import sys +import tarfile +import time +from distutils.version import LooseVersion +from enum import Enum + +# Supported camera models +class CAMERA_MODEL(Enum): + PINHOLE = 0 + FISHEYE = 1 + +# Supported calibration patterns +class Patterns: + Chessboard, Circles, ACircles, ChArUco = list(range(4)) + +class CalibrationException(Exception): + pass + +# TODO: Make pattern per-board? +class ChessboardInfo(): + def __init__(self, pattern="chessboard", n_cols = 0, n_rows = 0, dim = 0.0, marker_size = 0.0, aruco_dict = None): + self.pattern = pattern + self.n_cols = n_cols + self.n_rows = n_rows + self.dim = dim + self.marker_size = marker_size + self.aruco_dict = None + self.charuco_board = None; + if pattern=="charuco": + self.aruco_dict = cv2.aruco.getPredefinedDictionary({ + "aruco_orig" : cv2.aruco.DICT_ARUCO_ORIGINAL, + "4x4_50" : cv2.aruco.DICT_4X4_50, + "4x4_100" : cv2.aruco.DICT_4X4_100, + "4x4_250" : cv2.aruco.DICT_4X4_250, + "4x4_1000" : cv2.aruco.DICT_4X4_1000, + "5x5_50" : cv2.aruco.DICT_5X5_50, + "5x5_100" : cv2.aruco.DICT_5X5_100, + "5x5_250" : cv2.aruco.DICT_5X5_250, + "5x5_1000" : cv2.aruco.DICT_5X5_1000, + "6x6_50" : cv2.aruco.DICT_6x6_50, + "6x6_100" : cv2.aruco.DICT_6x6_100, + "6x6_250" : cv2.aruco.DICT_6x6_250, + "6x6_1000" : cv2.aruco.DICT_6x6_1000, + "7x7_50" : cv2.aruco.DICT_7x7_50, + "7x7_100" : cv2.aruco.DICT_7x7_100, + "7x7_250" : cv2.aruco.DICT_7x7_250, + "7x7_1000" : cv2.aruco.DICT_7x7_1000}[aruco_dict]) + self.charuco_board = cv2.aruco.CharucoBoard_create(self.n_cols, self.n_rows, self.dim, self.marker_size, + self.aruco_dict) + +# Make all private!!!!! +def lmin(seq1, seq2): + """ Pairwise minimum of two sequences """ + return [min(a, b) for (a, b) in zip(seq1, seq2)] + +def lmax(seq1, seq2): + """ Pairwise maximum of two sequences """ + return [max(a, b) for (a, b) in zip(seq1, seq2)] + +def _pdist(p1, p2): + """ + Distance bwt two points. p1 = (x, y), p2 = (x, y) + """ + return math.sqrt(math.pow(p1[0] - p2[0], 2) + math.pow(p1[1] - p2[1], 2)) + +def _get_outside_corners(corners, board): + """ + Return the four corners of the board as a whole, as (up_left, up_right, down_right, down_left). + """ + xdim = board.n_cols + ydim = board.n_rows + + if board.pattern != "charuco" and corners.shape[1] * corners.shape[0] != xdim * ydim: + raise Exception("Invalid number of corners! %d corners. X: %d, Y: %d" % (corners.shape[1] * corners.shape[0], + xdim, ydim)) + if board.pattern == "charuco" and corners.shape[1] * corners.shape[0] != (xdim-1) * (ydim-1): + raise Exception(("Invalid number of corners! %d corners. X: %d, Y: %d\n for ChArUco boards, " + + "_get_largest_rectangle_corners handles partial views of the target") % (corners.shape[1] * + corners.shape[0], xdim-1, ydim-1)) + + up_left = corners[0,0] + up_right = corners[xdim - 1,0] + down_right = corners[-1,0] + down_left = corners[-xdim,0] + + return (up_left, up_right, down_right, down_left) + +def _get_largest_rectangle_corners(corners, ids, board): + """ + Return the largest rectangle with all four corners visible in a partial view of a ChArUco board, as (up_left, + up_right, down_right, down_left). + """ + + # ChArUco board corner numbering: + # + # 9 10 11 + # ^ 6 7 8 + # y 3 4 5 + # 0 1 2 + # x > + # + # reference: https://docs.opencv.org/master/df/d4a/tutorial_charuco_detection.html + + # xdim and ydim are number of squares, but we're working with inner corners + xdim = board.n_cols - 1 + ydim = board.n_rows - 1 + board_vis = [[[i*xdim + j] in ids for j in range(xdim)] for i in range(ydim)] + + best_area = 0 + best_rect = [-1, -1, -1, -1] + + for x1 in range(xdim): + for x2 in range(x1, xdim): + for y1 in range(ydim): + for y2 in range(y1, ydim): + if (board_vis[y1][x1] and board_vis[y1][x2] and board_vis[y2][x1] and + board_vis[y2][x2] and (x2-x1+1)*(y2-y1+1) > best_area): + best_area = (x2-x1+1)*(y2-y1+1) + best_rect = [x1, x2, y1, y2] + (x1, x2, y1, y2) = best_rect + corner_ids = (y2*xdim+x1, y2*xdim+x2, y1*xdim+x2, y1*xdim + x1) + corners = tuple(corners[numpy.where(ids == corner_id)[0]][0][0] for corner_id in corner_ids) + + return corners + +def _calculate_skew(corners): + """ + Get skew for given checkerboard detection. + Scaled to [0,1], which 0 = no skew, 1 = high skew + Skew is proportional to the divergence of three outside corners from 90 degrees. + """ + # TODO Using three nearby interior corners might be more robust, outside corners occasionally + # get mis-detected + up_left, up_right, down_right, _ = corners + + def angle(a, b, c): + """ + Return angle between lines ab, bc + """ + ab = a - b + cb = c - b + return math.acos(numpy.dot(ab,cb) / (numpy.linalg.norm(ab) * numpy.linalg.norm(cb))) + + skew = min(1.0, 2. * abs((math.pi / 2.) - angle(up_left, up_right, down_right))) + return skew + +def _calculate_area(corners): + """ + Get 2d image area of the detected checkerboard. + The projected checkerboard is assumed to be a convex quadrilateral, and the area computed as + |p X q|/2; see http://mathworld.wolfram.com/Quadrilateral.html. + """ + (up_left, up_right, down_right, down_left) = corners + a = up_right - up_left + b = down_right - up_right + c = down_left - down_right + p = b + c + q = a + b + return abs(p[0]*q[1] - p[1]*q[0]) / 2. + +def _get_corners(img, board, refine = True, checkerboard_flags=0): + """ + Get corners for a particular chessboard for an image + """ + h = img.shape[0] + w = img.shape[1] + if len(img.shape) == 3 and img.shape[2] == 3: + mono = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + else: + mono = img + (ok, corners) = cv2.findChessboardCorners(mono, (board.n_cols, board.n_rows), flags = cv2.CALIB_CB_ADAPTIVE_THRESH | + cv2.CALIB_CB_NORMALIZE_IMAGE | checkerboard_flags) + if not ok: + return (ok, corners) + + # If any corners are within BORDER pixels of the screen edge, reject the detection by setting ok to false + # NOTE: This may cause problems with very low-resolution cameras, where 8 pixels is a non-negligible fraction + # of the image size. See http://answers.ros.org/question/3155/how-can-i-calibrate-low-resolution-cameras + BORDER = 8 + if not all([(BORDER < corners[i, 0, 0] < (w - BORDER)) and (BORDER < corners[i, 0, 1] < (h - BORDER)) for i in range(corners.shape[0])]): + ok = False + + # Ensure that all corner-arrays are going from top to bottom. + if board.n_rows!=board.n_cols: + if corners[0, 0, 1] > corners[-1, 0, 1]: + corners = numpy.copy(numpy.flipud(corners)) + else: + direction_corners=(corners[-1]-corners[0])>=numpy.array([[0.0,0.0]]) + + if not numpy.all(direction_corners): + if not numpy.any(direction_corners): + corners = numpy.copy(numpy.flipud(corners)) + elif direction_corners[0][0]: + corners=numpy.rot90(corners.reshape(board.n_rows,board.n_cols,2)).reshape(board.n_cols*board.n_rows,1,2) + else: + corners=numpy.rot90(corners.reshape(board.n_rows,board.n_cols,2),3).reshape(board.n_cols*board.n_rows,1,2) + + if refine and ok: + # Use a radius of half the minimum distance between corners. This should be large enough to snap to the + # correct corner, but not so large as to include a wrong corner in the search window. + min_distance = float("inf") + for row in range(board.n_rows): + for col in range(board.n_cols - 1): + index = row*board.n_rows + col + min_distance = min(min_distance, _pdist(corners[index, 0], corners[index + 1, 0])) + for row in range(board.n_rows - 1): + for col in range(board.n_cols): + index = row*board.n_rows + col + min_distance = min(min_distance, _pdist(corners[index, 0], corners[index + board.n_cols, 0])) + radius = int(math.ceil(min_distance * 0.5)) + cv2.cornerSubPix(mono, corners, (radius,radius), (-1,-1), + ( cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1 )) + + return (ok, corners) + +def _get_charuco_corners(img, board, refine): + """ + Get chessboard corners from image of ChArUco board + """ + h = img.shape[0] + w = img.shape[1] + + if len(img.shape) == 3 and img.shape[2] == 3: + mono = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + else: + mono = img + + marker_corners, marker_ids, _ = cv2.aruco.detectMarkers(img, board.aruco_dict) + if len(marker_corners) == 0: + return (False, None, None) + _, square_corners, ids = cv2.aruco.interpolateCornersCharuco(marker_corners, marker_ids, img, board.charuco_board) + return ((square_corners is not None) and (len(square_corners) > 5), square_corners, ids) + +def _get_circles(img, board, pattern): + """ + Get circle centers for a symmetric or asymmetric grid + """ + h = img.shape[0] + w = img.shape[1] + if len(img.shape) == 3 and img.shape[2] == 3: + mono = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + else: + mono = img + + flag = cv2.CALIB_CB_SYMMETRIC_GRID + if pattern == Patterns.ACircles: + flag = cv2.CALIB_CB_ASYMMETRIC_GRID + mono_arr = numpy.array(mono) + (ok, corners) = cv2.findCirclesGrid(mono_arr, (board.n_cols, board.n_rows), flags=flag) + + # In symmetric case, findCirclesGrid does not detect the target if it's turned sideways. So we try + # again with dimensions swapped - not so efficient. + # TODO Better to add as second board? Corner ordering will change. + if not ok and pattern == Patterns.Circles: + (ok, corners) = cv2.findCirclesGrid(mono_arr, (board.n_rows, board.n_cols), flags=flag) + + return (ok, corners) + +def _get_dist_model(dist_params, cam_model): + # Select dist model + if CAMERA_MODEL.PINHOLE == cam_model: + if dist_params.size > 5: + dist_model = "rational_polynomial" + else: + dist_model = "plumb_bob" + elif CAMERA_MODEL.FISHEYE == cam_model: + dist_model = "equidistant" + else: + dist_model = "unknown" + return dist_model + +# TODO self.size needs to come from CameraInfo, full resolution +class Calibrator(): + """ + Base class for calibration system + """ + def __init__(self, boards, flags=0, fisheye_flags = 0, pattern=Patterns.Chessboard, name='', + checkerboard_flags=cv2.CALIB_CB_FAST_CHECK, max_chessboard_speed = -1.0): + # Ordering the dimensions for the different detectors is actually a minefield... + if pattern == Patterns.Chessboard: + # Make sure n_cols > n_rows to agree with OpenCV CB detector output + self._boards = [ChessboardInfo("chessboard", max(i.n_cols, i.n_rows), min(i.n_cols, i.n_rows), i.dim) for i in boards] + if pattern == Patterns.ChArUco: + self._boards = boards + elif pattern == Patterns.ACircles: + # 7x4 and 4x7 are actually different patterns. Assume square-ish pattern, so n_rows > n_cols. + self._boards = [ChessboardInfo("acircles", min(i.n_cols, i.n_rows), max(i.n_cols, i.n_rows), i.dim) for i in boards] + elif pattern == Patterns.Circles: + # We end up having to check both ways anyway + self._boards = boards + + # Set to true after we perform calibration + self.calibrated = False + self.calib_flags = flags + self.fisheye_calib_flags = fisheye_flags + self.checkerboard_flags = checkerboard_flags + self.pattern = pattern + self.br = cv_bridge.CvBridge() + self.camera_model = CAMERA_MODEL.PINHOLE + # self.db is list of (parameters, image) samples for use in calibration. parameters has form + # (X, Y, size, skew) all normalized to [0,1], to keep track of what sort of samples we've taken + # and ensure enough variety. + self.db = [] + # For each db sample, we also record the detected corners (and IDs, if using a ChArUco board) + self.good_corners = [] + # Set to true when we have sufficiently varied samples to calibrate + self.goodenough = False + self.param_ranges = [0.7, 0.7, 0.4, 0.5] + self.name = name + self.last_frame_corners = None + self.last_frame_ids = None + self.max_chessboard_speed = max_chessboard_speed + + def mkgray(self, msg): + """ + Convert a message into a 8-bit 1 channel monochrome OpenCV image + """ + # as cv_bridge automatically scales, we need to remove that behavior + # TODO: get a Python API in cv_bridge to check for the image depth. + if self.br.encoding_to_dtype_with_channels(msg.encoding)[0] in ['uint16', 'int16']: + mono16 = self.br.imgmsg_to_cv2(msg, '16UC1') + mono8 = numpy.array(mono16 / 256, dtype=numpy.uint8) + return mono8 + elif 'FC1' in msg.encoding: + # floating point image handling + img = self.br.imgmsg_to_cv2(msg, "passthrough") + _, max_val, _, _ = cv2.minMaxLoc(img) + if max_val > 0: + scale = 255.0 / max_val + mono_img = (img * scale).astype(numpy.uint8) + else: + mono_img = img.astype(numpy.uint8) + return mono_img + else: + return self.br.imgmsg_to_cv2(msg, "mono8") + + def get_parameters(self, corners, ids, board, size): + """ + Return list of parameters [X, Y, size, skew] describing the checkerboard view. + """ + (width, height) = size + Xs = corners[:,:,0] + Ys = corners[:,:,1] + if board.pattern == 'charuco': + outside_corners = _get_largest_rectangle_corners(corners, ids, board) + else: + outside_corners = _get_outside_corners(corners, board) + area = _calculate_area(outside_corners) + skew = _calculate_skew(outside_corners) + border = math.sqrt(area) + # For X and Y, we "shrink" the image all around by approx. half the board size. + # Otherwise large boards are penalized because you can't get much X/Y variation. + p_x = min(1.0, max(0.0, (numpy.mean(Xs) - border / 2) / (width - border))) + p_y = min(1.0, max(0.0, (numpy.mean(Ys) - border / 2) / (height - border))) + p_size = math.sqrt(area / (width * height)) + params = [p_x, p_y, p_size, skew] + return params + + def set_cammodel(self, modeltype): + self.camera_model = modeltype + + def is_slow_moving(self, corners, ids, last_frame_corners, last_frame_ids): + """ + Returns true if the motion of the checkerboard is sufficiently low between + this and the previous frame. + """ + # If we don't have previous frame corners, we can't accept the sample + if last_frame_corners is None: + return False + if ids is None: + num_corners = len(corners) + corner_deltas = (corners - last_frame_corners).reshape(num_corners, 2) + else: + corner_deltas = [] + last_frame_ids = list(last_frame_ids.transpose()[0]) + for i, c_id in enumerate(ids): + try: + last_i = last_frame_ids.index(c_id) + corner_deltas.append(corners[i] - last_frame_corners[last_i]) + except ValueError: pass + corner_deltas = numpy.concatenate(corner_deltas) + + # Average distance travelled overall for all corners + average_motion = numpy.average(numpy.linalg.norm(corner_deltas, axis = 1)) + return average_motion <= self.max_chessboard_speed + + def is_good_sample(self, params, corners, ids, last_frame_corners, last_frame_ids): + """ + Returns true if the checkerboard detection described by params should be added to the database. + """ + if not self.db: + return True + + def param_distance(p1, p2): + return sum([abs(a-b) for (a,b) in zip(p1, p2)]) + + db_params = [sample[0] for sample in self.db] + d = min([param_distance(params, p) for p in db_params]) + #print "d = %.3f" % d #DEBUG + # TODO What's a good threshold here? Should it be configurable? + if d <= 0.2: + return False + + if self.max_chessboard_speed > 0: + if not self.is_slow_moving(corners, ids, last_frame_corners, last_frame_ids): + return False + + # All tests passed, image should be good for calibration + return True + + _param_names = ["X", "Y", "Size", "Skew"] + + def compute_goodenough(self): + if not self.db: + return None + + # Find range of checkerboard poses covered by samples in database + all_params = [sample[0] for sample in self.db] + min_params = all_params[0] + max_params = all_params[0] + for params in all_params[1:]: + min_params = lmin(min_params, params) + max_params = lmax(max_params, params) + # Don't reward small size or skew + min_params = [min_params[0], min_params[1], 0., 0.] + + # For each parameter, judge how much progress has been made toward adequate variation + progress = [min((hi - lo) / r, 1.0) for (lo, hi, r) in zip(min_params, max_params, self.param_ranges)] + # If we have lots of samples, allow calibration even if not all parameters are green + # TODO Awkward that we update self.goodenough instead of returning it + self.goodenough = (len(self.db) >= 40) or all([p == 1.0 for p in progress]) + + return list(zip(self._param_names, min_params, max_params, progress)) + + def mk_object_points(self, boards, use_board_size = False): + opts = [] + for i, b in enumerate(boards): + num_pts = b.n_cols * b.n_rows + opts_loc = numpy.zeros((num_pts, 1, 3), numpy.float32) + for j in range(num_pts): + opts_loc[j, 0, 0] = (j // b.n_cols) + if self.pattern == Patterns.ACircles: + opts_loc[j, 0, 1] = 2*(j % b.n_cols) + (opts_loc[j, 0, 0] % 2) + else: + opts_loc[j, 0, 1] = (j % b.n_cols) + opts_loc[j, 0, 2] = 0 + if use_board_size: + opts_loc[j, 0, :] = opts_loc[j, 0, :] * b.dim + opts.append(opts_loc) + return opts + + def get_corners(self, img, refine = True): + """ + Use cvFindChessboardCorners to find corners of chessboard in image. + + Check all boards. Return corners for first chessboard that it detects + if given multiple size chessboards. + + If a ChArUco board is used, the marker IDs are also returned, otherwise + ids is None. + + Returns (ok, corners, ids, board) + """ + + for b in self._boards: + if self.pattern == Patterns.Chessboard: + (ok, corners) = _get_corners(img, b, refine, self.checkerboard_flags) + ids = None + elif self.pattern == Patterns.ChArUco: + (ok, corners, ids) = _get_charuco_corners(img, b, refine) + else: + (ok, corners) = _get_circles(img, b, self.pattern) + ids = None + if ok: + return (ok, corners, ids, b) + return (False, None, None, None) + + def downsample_and_detect(self, img): + """ + Downsample the input image to approximately VGA resolution and detect the + calibration target corners in the full-size image. + + Combines these apparently orthogonal duties as an optimization. Checkerboard + detection is too expensive on large images, so it's better to do detection on + the smaller display image and scale the corners back up to the correct size. + + Returns (scrib, corners, downsampled_corners, ids, board, (x_scale, y_scale)). + """ + # Scale the input image down to ~VGA size + height = img.shape[0] + width = img.shape[1] + scale = math.sqrt( (width*height) / (640.*480.) ) + if scale > 1.0: + scrib = cv2.resize(img, (int(width / scale), int(height / scale))) + else: + scrib = img + # Due to rounding, actual horizontal/vertical scaling may differ slightly + x_scale = float(width) / scrib.shape[1] + y_scale = float(height) / scrib.shape[0] + + if self.pattern == Patterns.Chessboard: + # Detect checkerboard + (ok, downsampled_corners, ids, board) = self.get_corners(scrib, refine = True) + + # Scale corners back to full size image + corners = None + if ok: + if scale > 1.0: + # Refine up-scaled corners in the original full-res image + # TODO Does this really make a difference in practice? + corners_unrefined = downsampled_corners.copy() + corners_unrefined[:, :, 0] *= x_scale + corners_unrefined[:, :, 1] *= y_scale + radius = int(math.ceil(scale)) + if len(img.shape) == 3 and img.shape[2] == 3: + mono = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + else: + mono = img + cv2.cornerSubPix(mono, corners_unrefined, (radius,radius), (-1,-1), + ( cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.1 )) + corners = corners_unrefined + else: + corners = downsampled_corners + else: + # Circle grid detection is fast even on large images + (ok, corners, ids, board) = self.get_corners(img) + # Scale corners to downsampled image for display + downsampled_corners = None + if ok: + if scale > 1.0: + downsampled_corners = corners.copy() + downsampled_corners[:,:,0] /= x_scale + downsampled_corners[:,:,1] /= y_scale + else: + downsampled_corners = corners + + return (scrib, corners, downsampled_corners, ids, board, (x_scale, y_scale)) + + @staticmethod + def lrmsg(d, k, r, p, size, camera_model): + """ Used by :meth:`as_message`. Return a CameraInfo message for the given calibration matrices """ + msg = sensor_msgs.msg.CameraInfo() + msg.width, msg.height = size + msg.distortion_model = _get_dist_model(d, camera_model) + + msg.d = numpy.ravel(d).copy().tolist() + msg.k = numpy.ravel(k).copy().tolist() + msg.r = numpy.ravel(r).copy().tolist() + msg.p = numpy.ravel(p).copy().tolist() + return msg + + @staticmethod + def lrreport(d, k, r, p): + print("D =", numpy.ravel(d).tolist()) + print("K =", numpy.ravel(k).tolist()) + print("R =", numpy.ravel(r).tolist()) + print("P =", numpy.ravel(p).tolist()) + + @staticmethod + def lrost(name, d, k, r, p, size): + assert k.shape == (3, 3) + assert r.shape == (3, 3) + assert p.shape == (3, 4) + calmessage = "\n".join([ + "# oST version 5.0 parameters", + "", + "", + "[image]", + "", + "width", + "%d" % size[0], + "", + "height", + "%d" % size[1], + "", + "[%s]" % name, + "", + "camera matrix", + " ".join("%8f" % k[0,i] for i in range(3)), + " ".join("%8f" % k[1,i] for i in range(3)), + " ".join("%8f" % k[2,i] for i in range(3)), + "", + "distortion", + " ".join("%8f" % x for x in d.flat), + "", + "rectification", + " ".join("%8f" % r[0,i] for i in range(3)), + " ".join("%8f" % r[1,i] for i in range(3)), + " ".join("%8f" % r[2,i] for i in range(3)), + "", + "projection", + " ".join("%8f" % p[0,i] for i in range(4)), + " ".join("%8f" % p[1,i] for i in range(4)), + " ".join("%8f" % p[2,i] for i in range(4)), + "" + ]) + assert len(calmessage) < 525, "Calibration info must be less than 525 bytes" + return calmessage + + @staticmethod + def lryaml(name, d, k, r, p, size, cam_model): + def format_mat(x, precision): + return ("[%s]" % ( + numpy.array2string(x, precision=precision, suppress_small=True, separator=", ") + .replace("[", "").replace("]", "").replace("\n", "\n ") + )) + + dist_model = _get_dist_model(d, cam_model) + + assert k.shape == (3, 3) + assert r.shape == (3, 3) + assert p.shape == (3, 4) + calmessage = "\n".join([ + "image_width: %d" % size[0], + "image_height: %d" % size[1], + "camera_name: " + name, + "camera_matrix:", + " rows: 3", + " cols: 3", + " data: " + format_mat(k, 5), + "distortion_model: " + dist_model, + "distortion_coefficients:", + " rows: 1", + " cols: %d" % d.size, + " data: [%s]" % ", ".join("%8f" % x for x in d.flat), + "rectification_matrix:", + " rows: 3", + " cols: 3", + " data: " + format_mat(r, 8), + "projection_matrix:", + " rows: 3", + " cols: 4", + " data: " + format_mat(p, 5), + "" + ]) + return calmessage + + def do_save(self): + filename = '/tmp/calibrationdata.tar.gz' + tf = tarfile.open(filename, 'w:gz') + self.do_tarfile_save(tf) # Must be overridden in subclasses + tf.close() + print(("Wrote calibration data to", filename)) + +def image_from_archive(archive, name): + """ + Load image PGM file from tar archive. + + Used for tarfile loading and unit test. + """ + member = archive.getmember(name) + imagefiledata = numpy.frombuffer(archive.extractfile(member).read(), numpy.uint8) + imagefiledata.resize((1, imagefiledata.size)) + return cv2.imdecode(imagefiledata, cv2.IMREAD_COLOR) + +class ImageDrawable(): + """ + Passed to CalibrationNode after image handled. Allows plotting of images + with detected corner points + """ + def __init__(self): + self.params = None + +class MonoDrawable(ImageDrawable): + def __init__(self): + ImageDrawable.__init__(self) + self.scrib = None + self.linear_error = -1.0 + +class StereoDrawable(ImageDrawable): + def __init__(self): + ImageDrawable.__init__(self) + self.lscrib = None + self.rscrib = None + self.epierror = -1 + self.dim = -1 + + +class MonoCalibrator(Calibrator): + """ + Calibration class for monocular cameras:: + + images = [cv2.imread("mono%d.png") for i in range(8)] + mc = MonoCalibrator() + mc.cal(images) + print mc.as_message() + """ + + is_mono = True # TODO Could get rid of is_mono + + def __init__(self, *args, **kwargs): + if 'name' not in kwargs: + kwargs['name'] = 'narrow_stereo/left' + super(MonoCalibrator, self).__init__(*args, **kwargs) + + def cal(self, images): + """ + Calibrate camera from given images + """ + goodcorners = self.collect_corners(images) + self.cal_fromcorners(goodcorners) + self.calibrated = True + + def collect_corners(self, images): + """ + :param images: source images containing chessboards + :type images: list of :class:`cvMat` + + Find chessboards in all images. + + Return [ (corners, ids, ChessboardInfo) ] + """ + self.size = (images[0].shape[1], images[0].shape[0]) + corners = [self.get_corners(i) for i in images] + + goodcorners = [(co, ids, b) for (ok, co, ids, b) in corners if ok] + if not goodcorners: + raise CalibrationException("No corners found in images!") + return goodcorners + + def cal_fromcorners(self, good): + """ + :param good: Good corner positions and boards + :type good: [(corners, ChessboardInfo)] + """ + + (ipts, ids, boards) = zip(*good) + opts = self.mk_object_points(boards) + # If FIX_ASPECT_RATIO flag set, enforce focal lengths have 1/1 ratio + intrinsics_in = numpy.eye(3, dtype=numpy.float64) + + if self.pattern == Patterns.ChArUco: + if self.camera_model == CAMERA_MODEL.FISHEYE: + raise NotImplemented("Can't perform fisheye calibration with ChArUco board") + + reproj_err, self.intrinsics, self.distortion, rvecs, tvecs = cv2.aruco.calibrateCameraCharuco( + ipts, ids, boards[0].charuco_board, self.size, intrinsics_in, None) + + elif self.camera_model == CAMERA_MODEL.PINHOLE: + print("mono pinhole calibration...") + reproj_err, self.intrinsics, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera( + opts, ipts, + self.size, + intrinsics_in, + None, + flags = self.calib_flags) + # OpenCV returns more than 8 coefficients (the additional ones all zeros) when CALIB_RATIONAL_MODEL is set. + # The extra ones include e.g. thin prism coefficients, which we are not interested in. + if self.calib_flags & cv2.CALIB_RATIONAL_MODEL: + self.distortion = dist_coeffs.flat[:8].reshape(-1, 1) # rational polynomial + else: + self.distortion = dist_coeffs.flat[:5].reshape(-1, 1) # plumb bob + elif self.camera_model == CAMERA_MODEL.FISHEYE: + print("mono fisheye calibration...") + # WARNING: cv2.fisheye.calibrate wants float64 points + ipts64 = numpy.asarray(ipts, dtype=numpy.float64) + ipts = ipts64 + opts64 = numpy.asarray(opts, dtype=numpy.float64) + opts = opts64 + reproj_err, self.intrinsics, self.distortion, rvecs, tvecs = cv2.fisheye.calibrate( + opts, ipts, self.size, + intrinsics_in, None, flags = self.fisheye_calib_flags) + + # R is identity matrix for monocular calibration + self.R = numpy.eye(3, dtype=numpy.float64) + self.P = numpy.zeros((3, 4), dtype=numpy.float64) + + self.set_alpha(0.0) + + def set_alpha(self, a): + """ + Set the alpha value for the calibrated camera solution. The alpha + value is a zoom, and ranges from 0 (zoomed in, all pixels in + calibrated image are valid) to 1 (zoomed out, all pixels in + original image are in calibrated image). + """ + + if self.camera_model == CAMERA_MODEL.PINHOLE: + # NOTE: Prior to Electric, this code was broken such that we never actually saved the new + # camera matrix. In effect, this enforced P = [K|0] for monocular cameras. + # TODO: Verify that OpenCV #1199 gets applied (improved GetOptimalNewCameraMatrix) + ncm, _ = cv2.getOptimalNewCameraMatrix(self.intrinsics, self.distortion, self.size, a) + for j in range(3): + for i in range(3): + self.P[j,i] = ncm[j, i] + self.mapx, self.mapy = cv2.initUndistortRectifyMap(self.intrinsics, self.distortion, self.R, ncm, self.size, cv2.CV_32FC1) + elif self.camera_model == CAMERA_MODEL.FISHEYE: + # NOTE: cv2.fisheye.estimateNewCameraMatrixForUndistortRectify not producing proper results, using a naive approach instead: + self.P[:3,:3] = self.intrinsics[:3,:3] + self.P[0,0] /= (1. + a) + self.P[1,1] /= (1. + a) + self.mapx, self.mapy = cv2.fisheye.initUndistortRectifyMap(self.intrinsics, self.distortion, self.R, self.P, self.size, cv2.CV_32FC1) + + + def remap(self, src): + """ + :param src: source image + :type src: :class:`cvMat` + + Apply the post-calibration undistortion to the source image + """ + return cv2.remap(src, self.mapx, self.mapy, cv2.INTER_LINEAR) + + def undistort_points(self, src): + """ + :param src: N source pixel points (u,v) as an Nx2 matrix + :type src: :class:`cvMat` + + Apply the post-calibration undistortion to the source points + """ + if self.camera_model == CAMERA_MODEL.PINHOLE: + return cv2.undistortPoints(src, self.intrinsics, self.distortion, R = self.R, P = self.P) + elif self.camera_model == CAMERA_MODEL.FISHEYE: + return cv2.fisheye.undistortPoints(src, self.intrinsics, self.distortion, R = self.R, P = self.P) + + def as_message(self): + """ Return the camera calibration as a CameraInfo message """ + return self.lrmsg(self.distortion, self.intrinsics, self.R, self.P, self.size, self.camera_model) + + def from_message(self, msg, alpha = 0.0): + """ Initialize the camera calibration from a CameraInfo message """ + + self.size = (msg.width, msg.height) + self.intrinsics = numpy.array(msg.k, dtype=numpy.float64, copy=True).reshape((3, 3)) + self.distortion = numpy.array(msg.d, dtype=numpy.float64, copy=True).reshape((len(msg.d), 1)) + self.R = numpy.array(msg.r, dtype=numpy.float64, copy=True).reshape((3, 3)) + self.P = numpy.array(msg.p, dtype=numpy.float64, copy=True).reshape((3, 4)) + + self.set_alpha(0.0) + + def report(self): + self.lrreport(self.distortion, self.intrinsics, self.R, self.P) + + def ost(self): + return self.lrost(self.name, self.distortion, self.intrinsics, self.R, self.P, self.size) + + def yaml(self): + return self.lryaml(self.name, self.distortion, self.intrinsics, self.R, self.P, self.size, self.camera_model) + + def linear_error_from_image(self, image): + """ + Detect the checkerboard and compute the linear error. + Mainly for use in tests. + """ + _, corners, _, ids, board, _ = self.downsample_and_detect(image) + if corners is None: + return None + + undistorted = self.undistort_points(corners) + return self.linear_error(undistorted, ids, board) + + @staticmethod + def linear_error(corners, ids, b): + + """ + Returns the linear error for a set of corners detected in the unrectified image. + """ + + if corners is None: + return None + + corners = numpy.squeeze(corners) + + def pt2line(x0, y0, x1, y1, x2, y2): + """ point is (x0, y0), line is (x1, y1, x2, y2) """ + return abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) / math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) + + n_cols = b.n_cols + n_rows = b.n_rows + if b.pattern == 'charuco': + n_cols -= 1 + n_rows -= 1 + n_pts = n_cols * n_rows + + if ids is None: + ids = numpy.arange(n_pts).reshape((n_pts, 1)) + + ids_to_idx = dict((ids[i, 0], i) for i in range(len(ids))) + + errors = [] + for row in range(n_rows): + row_min = row * n_cols + row_max = (row+1) * n_cols + pts_in_row = [x for x in ids if row_min <= x < row_max] + + # not enough points to calculate error + if len(pts_in_row) <= 2: continue + + left_pt = min(pts_in_row)[0] + right_pt = max(pts_in_row)[0] + x_left = corners[ids_to_idx[left_pt], 0] + y_left = corners[ids_to_idx[left_pt], 1] + x_right = corners[ids_to_idx[right_pt], 0] + y_right = corners[ids_to_idx[right_pt], 1] + + for pt in pts_in_row: + if pt[0] in (left_pt, right_pt): continue + x = corners[ids_to_idx[pt[0]], 0] + y = corners[ids_to_idx[pt[0]], 1] + errors.append(pt2line(x, y, x_left, y_left, x_right, y_right)) + + if errors: + return math.sqrt(sum([e**2 for e in errors]) / len(errors)) + else: + return None + + + def handle_msg(self, msg): + """ + Detects the calibration target and, if found and provides enough new information, + adds it to the sample database. + + Returns a MonoDrawable message with the display image and progress info. + """ + gray = self.mkgray(msg) + linear_error = -1 + + # Get display-image-to-be (scrib) and detection of the calibration target + scrib_mono, corners, downsampled_corners, ids, board, (x_scale, y_scale) = self.downsample_and_detect(gray) + + if self.calibrated: + # Show rectified image + # TODO Pull out downsampling code into function + gray_remap = self.remap(gray) + gray_rect = gray_remap + if x_scale != 1.0 or y_scale != 1.0: + gray_rect = cv2.resize(gray_remap, (scrib_mono.shape[1], scrib_mono.shape[0])) + + scrib = cv2.cvtColor(gray_rect, cv2.COLOR_GRAY2BGR) + + if corners is not None: + # Report linear error + undistorted = self.undistort_points(corners) + linear_error = self.linear_error(undistorted, ids, board) + + # Draw rectified corners + scrib_src = undistorted.copy() + scrib_src[:,:,0] /= x_scale + scrib_src[:,:,1] /= y_scale + cv2.drawChessboardCorners(scrib, (board.n_cols, board.n_rows), scrib_src, True) + + else: + scrib = cv2.cvtColor(scrib_mono, cv2.COLOR_GRAY2BGR) + if corners is not None: + # Draw (potentially downsampled) corners onto display image + if board.pattern == "charuco": + cv2.aruco.drawDetectedCornersCharuco(scrib, downsampled_corners, ids) + else: + cv2.drawChessboardCorners(scrib, (board.n_cols, board.n_rows), downsampled_corners, True) + + # Add sample to database only if it's sufficiently different from any previous sample. + params = self.get_parameters(corners, ids, board, (gray.shape[1], gray.shape[0])) + if self.is_good_sample(params, corners, ids, self.last_frame_corners, self.last_frame_ids): + self.db.append((params, gray)) + if self.pattern == Patterns.ChArUco: + self.good_corners.append((corners, ids, board)) + else: + self.good_corners.append((corners, None, board)) + print(("*** Added sample %d, p_x = %.3f, p_y = %.3f, p_size = %.3f, skew = %.3f" % tuple([len(self.db)] + params))) + + self.last_frame_corners = corners + self.last_frame_ids = ids + rv = MonoDrawable() + rv.scrib = scrib + rv.params = self.compute_goodenough() + rv.linear_error = linear_error + return rv + + def do_calibration(self, dump = False): + if not self.good_corners: + print("**** Collecting corners for all images! ****") #DEBUG + images = [i for (p, i) in self.db] + self.good_corners = self.collect_corners(images) + self.size = (self.db[0][1].shape[1], self.db[0][1].shape[0]) # TODO Needs to be set externally + # Dump should only occur if user wants it + if dump: + pickle.dump((self.is_mono, self.size, self.good_corners), + open("/tmp/camera_calibration_%08x.pickle" % random.getrandbits(32), "w")) + self.cal_fromcorners(self.good_corners) + self.calibrated = True + # DEBUG + print((self.report())) + print((self.ost())) + + def do_tarfile_save(self, tf): + """ Write images and calibration solution to a tarfile object """ + + def taradd(name, buf): + if isinstance(buf, str): + s = BytesIO(buf.encode('utf-8')) + else: + s = BytesIO(buf) + ti = tarfile.TarInfo(name) + ti.size = len(s.getvalue()) + ti.uname = 'calibrator' + ti.mtime = int(time.time()) + tf.addfile(tarinfo=ti, fileobj=s) + + ims = [("left-%04d.png" % i, im) for i,(_, im) in enumerate(self.db)] + for (name, im) in ims: + taradd(name, cv2.imencode(".png", im)[1].tostring()) + taradd('ost.yaml', self.yaml()) + taradd('ost.txt', self.ost()) + + def do_tarfile_calibration(self, filename): + archive = tarfile.open(filename, 'r') + + limages = [ image_from_archive(archive, f) for f in archive.getnames() if (f.startswith('left') and (f.endswith('.pgm') or f.endswith('png'))) ] + + self.cal(limages) + +# TODO Replicate MonoCalibrator improvements in stereo +class StereoCalibrator(Calibrator): + """ + Calibration class for stereo cameras:: + + limages = [cv2.imread("left%d.png") for i in range(8)] + rimages = [cv2.imread("right%d.png") for i in range(8)] + sc = StereoCalibrator() + sc.cal(limages, rimages) + print sc.as_message() + """ + + is_mono = False + + def __init__(self, *args, **kwargs): + if 'name' not in kwargs: + kwargs['name'] = 'narrow_stereo' + super(StereoCalibrator, self).__init__(*args, **kwargs) + self.l = MonoCalibrator(*args, **kwargs) + self.r = MonoCalibrator(*args, **kwargs) + # Collecting from two cameras in a horizontal stereo rig, can't get + # full X range in the left camera. + self.param_ranges[0] = 0.4 + + #override + def set_cammodel(self, modeltype): + super(StereoCalibrator, self).set_cammodel(modeltype) + self.l.set_cammodel(modeltype) + self.r.set_cammodel(modeltype) + + def cal(self, limages, rimages): + """ + :param limages: source left images containing chessboards + :type limages: list of :class:`cvMat` + :param rimages: source right images containing chessboards + :type rimages: list of :class:`cvMat` + + Find chessboards in images, and runs the OpenCV calibration solver. + """ + goodcorners = self.collect_corners(limages, rimages) + self.size = (limages[0].shape[1], limages[0].shape[0]) + self.l.size = self.size + self.r.size = self.size + self.cal_fromcorners(goodcorners) + self.calibrated = True + + def collect_corners(self, limages, rimages): + """ + For a sequence of left and right images, find pairs of images where both + left and right have a chessboard, and return their corners as a list of pairs. + """ + # Pick out (corners, ids, board) tuples + lcorners = [] + rcorners = [] + for img in limages: + (_, corners, _, ids, board, _) = self.downsample_and_detect(img) + lcorners.append((corners, ids, board)) + for img in rimages: + (_, corners, _, ids, board, _) = self.downsample_and_detect(img) + rcorners.append((corners, ids, board)) + + good = [(lco, rco, lid, rid, b) for ((lco, lid, b), (rco, rid, br)) in zip( lcorners, rcorners) + if (lco is not None and rco is not None)] + + if len(good) == 0: + raise CalibrationException("No corners found in images!") + return good + + def cal_fromcorners(self, good): + # Perform monocular calibrations + lcorners = [(lco, lid, b) for (lco, rco, lid, rid, b) in good] + rcorners = [(rco, rid, b) for (lco, rco, lid, rid, b) in good] + self.l.cal_fromcorners(lcorners) + self.r.cal_fromcorners(rcorners) + + (lipts, ripts, _, _, boards) = zip(*good) + + opts = self.mk_object_points(boards, True) + + flags = cv2.CALIB_FIX_INTRINSIC + + self.T = numpy.zeros((3, 1), dtype=numpy.float64) + self.R = numpy.eye(3, dtype=numpy.float64) + + if self.pattern == Patterns.ChArUco: + # TODO: implement stereo ChArUco calibration + raise NotImplemented("Stereo calibration not implemented for ChArUco boards") + + if self.camera_model == CAMERA_MODEL.PINHOLE: + print("stereo pinhole calibration...") + if LooseVersion(cv2.__version__).version[0] == 2: + cv2.stereoCalibrate(opts, lipts, ripts, self.size, + self.l.intrinsics, self.l.distortion, + self.r.intrinsics, self.r.distortion, + self.R, # R + self.T, # T + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 1, 1e-5), + flags = flags) + else: + cv2.stereoCalibrate(opts, lipts, ripts, + self.l.intrinsics, self.l.distortion, + self.r.intrinsics, self.r.distortion, + self.size, + self.R, # R + self.T, # T + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 1, 1e-5), + flags = flags) + elif self.camera_model == CAMERA_MODEL.FISHEYE: + print("stereo fisheye calibration...") + if LooseVersion(cv2.__version__).version[0] == 2: + print("ERROR: You need OpenCV >3 to use fisheye camera model") + sys.exit() + else: + # WARNING: cv2.fisheye.stereoCalibrate wants float64 points + lipts64 = numpy.asarray(lipts, dtype=numpy.float64) + lipts = lipts64 + ripts64 = numpy.asarray(ripts, dtype=numpy.float64) + ripts = ripts64 + opts64 = numpy.asarray(opts, dtype=numpy.float64) + opts = opts64 + + cv2.fisheye.stereoCalibrate(opts, lipts, ripts, + self.l.intrinsics, self.l.distortion, + self.r.intrinsics, self.r.distortion, + self.size, + self.R, # R + self.T, # T + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 1, 1e-5), # 30, 1e-6 + flags = flags) + + self.set_alpha(0.0) + + def set_alpha(self, a): + """ + Set the alpha value for the calibrated camera solution. The + alpha value is a zoom, and ranges from 0 (zoomed in, all pixels + in calibrated image are valid) to 1 (zoomed out, all pixels in + original image are in calibrated image). + """ + if self.camera_model == CAMERA_MODEL.PINHOLE: + cv2.stereoRectify(self.l.intrinsics, + self.l.distortion, + self.r.intrinsics, + self.r.distortion, + self.size, + self.R, + self.T, + self.l.R, self.r.R, self.l.P, self.r.P, + alpha = a) + + cv2.initUndistortRectifyMap(self.l.intrinsics, self.l.distortion, self.l.R, self.l.P, self.size, cv2.CV_32FC1, + self.l.mapx, self.l.mapy) + cv2.initUndistortRectifyMap(self.r.intrinsics, self.r.distortion, self.r.R, self.r.P, self.size, cv2.CV_32FC1, + self.r.mapx, self.r.mapy) + + elif self.camera_model == CAMERA_MODEL.FISHEYE: + self.Q = numpy.zeros((4,4), dtype=numpy.float64) + + flags = cv2.CALIB_ZERO_DISPARITY # Operation flags that may be zero or CALIB_ZERO_DISPARITY . + # If the flag is set, the function makes the principal points of each camera have the same pixel coordinates in the rectified views. + # And if the flag is not set, the function may still shift the images in the horizontal or vertical direction + # (depending on the orientation of epipolar lines) to maximize the useful image area. + + cv2.fisheye.stereoRectify(self.l.intrinsics, self.l.distortion, + self.r.intrinsics, self.r.distortion, + self.size, + self.R, self.T, + flags, + self.l.R, self.r.R, + self.l.P, self.r.P, + self.Q, + self.size, + a, + 1.0 ) + self.l.P[:3,:3] = numpy.dot(self.l.intrinsics,self.l.R) + self.r.P[:3,:3] = numpy.dot(self.r.intrinsics,self.r.R) + cv2.fisheye.initUndistortRectifyMap(self.l.intrinsics, self.l.distortion, self.l.R, self.l.intrinsics, self.size, cv2.CV_32FC1, + self.l.mapx, self.l.mapy) + cv2.fisheye.initUndistortRectifyMap(self.r.intrinsics, self.r.distortion, self.r.R, self.r.intrinsics, self.size, cv2.CV_32FC1, + self.r.mapx, self.r.mapy) + + def as_message(self): + """ + Return the camera calibration as a pair of CameraInfo messages, for left + and right cameras respectively. + """ + + return (self.lrmsg(self.l.distortion, self.l.intrinsics, self.l.R, self.l.P, self.size, self.l.camera_model), + self.lrmsg(self.r.distortion, self.r.intrinsics, self.r.R, self.r.P, self.size, self.r.camera_model)) + + def from_message(self, msgs, alpha = 0.0): + """ Initialize the camera calibration from a pair of CameraInfo messages. """ + self.size = (msgs[0].width, msgs[0].height) + + self.T = numpy.zeros((3, 1), dtype=numpy.float64) + self.R = numpy.eye(3, dtype=numpy.float64) + + self.l.from_message(msgs[0]) + self.r.from_message(msgs[1]) + # Need to compute self.T and self.R here, using the monocular parameters above + if False: + self.set_alpha(0.0) + + def report(self): + print("\nLeft:") + self.lrreport(self.l.distortion, self.l.intrinsics, self.l.R, self.l.P) + print("\nRight:") + self.lrreport(self.r.distortion, self.r.intrinsics, self.r.R, self.r.P) + print("self.T =", numpy.ravel(self.T).tolist()) + print("self.R =", numpy.ravel(self.R).tolist()) + + def ost(self): + return (self.lrost(self.name + "/left", self.l.distortion, self.l.intrinsics, self.l.R, self.l.P, self.size) + + self.lrost(self.name + "/right", self.r.distortion, self.r.intrinsics, self.r.R, self.r.P, self.size)) + + def yaml(self, suffix, info): + return self.lryaml(self.name + suffix, info.distortion, info.intrinsics, info.R, info.P, self.size, self.camera_model) + + # TODO Get rid of "from_images" versions of these, instead have function to get undistorted corners + def epipolar_error_from_images(self, limage, rimage): + """ + Detect the checkerboard in both images and compute the epipolar error. + Mainly for use in tests. + """ + lcorners = self.downsample_and_detect(limage)[1] + rcorners = self.downsample_and_detect(rimage)[1] + if lcorners is None or rcorners is None: + return None + + lundistorted = self.l.undistort_points(lcorners) + rundistorted = self.r.undistort_points(rcorners) + + return self.epipolar_error(lundistorted, rundistorted) + + def epipolar_error(self, lcorners, rcorners): + """ + Compute the epipolar error from two sets of matching undistorted points + """ + d = lcorners[:,:,1] - rcorners[:,:,1] + return numpy.sqrt(numpy.square(d).sum() / d.size) + + def chessboard_size_from_images(self, limage, rimage): + _, lcorners, _, _, board, _ = self.downsample_and_detect(limage) + _, rcorners, _, _, board, _ = self.downsample_and_detect(rimage) + if lcorners is None or rcorners is None: + return None + + lundistorted = self.l.undistort_points(lcorners) + rundistorted = self.r.undistort_points(rcorners) + + return self.chessboard_size(lundistorted, rundistorted, board) + + def chessboard_size(self, lcorners, rcorners, board, msg = None): + """ + Compute the square edge length from two sets of matching undistorted points + given the current calibration. + :param msg: a tuple of (left_msg, right_msg) + """ + # Project the points to 3d + cam = image_geometry.StereoCameraModel() + if msg == None: + msg = self.as_message() + cam.fromCameraInfo(*msg) + disparities = lcorners[:,:,0] - rcorners[:,:,0] + pt3d = [cam.projectPixelTo3d((lcorners[i,0,0], lcorners[i,0,1]), disparities[i,0]) for i in range(lcorners.shape[0]) ] + def l2(p0, p1): + return math.sqrt(sum([(c0 - c1) ** 2 for (c0, c1) in zip(p0, p1)])) + + # Compute the length from each horizontal and vertical line, and return the mean + cc = board.n_cols + cr = board.n_rows + lengths = ( + [l2(pt3d[cc * r + 0], pt3d[cc * r + (cc - 1)]) / (cc - 1) for r in range(cr)] + + [l2(pt3d[c + 0], pt3d[c + (cc * (cr - 1))]) / (cr - 1) for c in range(cc)]) + return sum(lengths) / len(lengths) + + def handle_msg(self, msg): + # TODO Various asserts that images have same dimension, same board detected... + (lmsg, rmsg) = msg + lgray = self.mkgray(lmsg) + rgray = self.mkgray(rmsg) + epierror = -1 + + # Get display-images-to-be and detections of the calibration target + lscrib_mono, lcorners, ldownsampled_corners, lids, lboard, (x_scale, y_scale) = self.downsample_and_detect(lgray) + rscrib_mono, rcorners, rdownsampled_corners, rids, rboard, _ = self.downsample_and_detect(rgray) + + if self.calibrated: + # Show rectified images + lremap = self.l.remap(lgray) + rremap = self.r.remap(rgray) + lrect = lremap + rrect = rremap + if x_scale != 1.0 or y_scale != 1.0: + lrect = cv2.resize(lremap, (lscrib_mono.shape[1], lscrib_mono.shape[0])) + rrect = cv2.resize(rremap, (rscrib_mono.shape[1], rscrib_mono.shape[0])) + + lscrib = cv2.cvtColor(lrect, cv2.COLOR_GRAY2BGR) + rscrib = cv2.cvtColor(rrect, cv2.COLOR_GRAY2BGR) + + # Draw rectified corners + if lcorners is not None: + lundistorted = self.l.undistort_points(lcorners) + scrib_src = lundistorted.copy() + scrib_src[:,:,0] /= x_scale + scrib_src[:,:,1] /= y_scale + cv2.drawChessboardCorners(lscrib, (lboard.n_cols, lboard.n_rows), scrib_src, True) + + if rcorners is not None: + rundistorted = self.r.undistort_points(rcorners) + scrib_src = rundistorted.copy() + scrib_src[:,:,0] /= x_scale + scrib_src[:,:,1] /= y_scale + cv2.drawChessboardCorners(rscrib, (rboard.n_cols, rboard.n_rows), scrib_src, True) + + # Report epipolar error + if lcorners is not None and rcorners is not None and len(lcorners) == len(rcorners): + epierror = self.epipolar_error(lundistorted, rundistorted) + + else: + lscrib = cv2.cvtColor(lscrib_mono, cv2.COLOR_GRAY2BGR) + rscrib = cv2.cvtColor(rscrib_mono, cv2.COLOR_GRAY2BGR) + # Draw any detected chessboards onto display (downsampled) images + if lcorners is not None: + cv2.drawChessboardCorners(lscrib, (lboard.n_cols, lboard.n_rows), + ldownsampled_corners, True) + if rcorners is not None: + cv2.drawChessboardCorners(rscrib, (rboard.n_cols, rboard.n_rows), + rdownsampled_corners, True) + + # Add sample to database only if it's sufficiently different from any previous sample + if lcorners is not None and rcorners is not None and len(lcorners) == len(rcorners): + params = self.get_parameters(lcorners, lids, lboard, (lgray.shape[1], lgray.shape[0])) + if self.is_good_sample(params, lcorners, lids, self.last_frame_corners, self.last_frame_ids): + self.db.append( (params, lgray, rgray) ) + self.good_corners.append( (lcorners, rcorners, lids, rids, lboard) ) + print(("*** Added sample %d, p_x = %.3f, p_y = %.3f, p_size = %.3f, skew = %.3f" % tuple([len(self.db)] + params))) + + self.last_frame_corners = lcorners + self.last_frame_ids = lids + rv = StereoDrawable() + rv.lscrib = lscrib + rv.rscrib = rscrib + rv.params = self.compute_goodenough() + rv.epierror = epierror + return rv + + def do_calibration(self, dump = False): + # TODO MonoCalibrator collects corners if needed here + self.size = (self.db[0][1].shape[1], self.db[0][1].shape[0]) # TODO Needs to be set externally + # Dump should only occur if user wants it + if dump: + pickle.dump((self.is_mono, self.size, self.good_corners), + open("/tmp/camera_calibration_%08x.pickle" % random.getrandbits(32), "w")) + self.l.size = self.size + self.r.size = self.size + self.cal_fromcorners(self.good_corners) + self.calibrated = True + # DEBUG + print((self.report())) + print((self.ost())) + + def do_tarfile_save(self, tf): + """ Write images and calibration solution to a tarfile object """ + ims = ([("left-%04d.png" % i, im) for i,(_, im, _) in enumerate(self.db)] + + [("right-%04d.png" % i, im) for i,(_, _, im) in enumerate(self.db)]) + + def taradd(name, buf): + if isinstance(buf, str): + s = BytesIO(buf.encode('utf-8')) + else: + s = BytesIO(buf) + ti = tarfile.TarInfo(name) + ti.size = len(s.getvalue()) + ti.uname = 'calibrator' + ti.mtime = int(time.time()) + tf.addfile(tarinfo=ti, fileobj=s) + + for (name, im) in ims: + taradd(name, cv2.imencode(".png", im)[1].tostring()) + taradd('left.yaml', self.yaml("/left", self.l)) + taradd('right.yaml', self.yaml("/right", self.r)) + taradd('ost.txt', self.ost()) + + def do_tarfile_calibration(self, filename): + archive = tarfile.open(filename, 'r') + limages = [ image_from_archive(archive, f) for f in archive.getnames() if (f.startswith('left') and (f.endswith('pgm') or f.endswith('png'))) ] + rimages = [ image_from_archive(archive, f) for f in archive.getnames() if (f.startswith('right') and (f.endswith('pgm') or f.endswith('png'))) ] + + if not len(limages) == len(rimages): + raise CalibrationException("Left, right images don't match. %d left images, %d right" % (len(limages), len(rimages))) + + ##\todo Check that the filenames match and stuff + + self.cal(limages, rimages) diff --git a/src/image_pipeline/camera_calibration/src/camera_calibration/camera_calibrator.py b/src/image_pipeline/camera_calibration/src/camera_calibration/camera_calibrator.py new file mode 100755 index 000000000..6d7ebdd40 --- /dev/null +++ b/src/image_pipeline/camera_calibration/src/camera_calibration/camera_calibrator.py @@ -0,0 +1,417 @@ +# Software License Agreement (BSD License) +# +# Copyright (c) 2009, Willow Garage, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import cv2 +import message_filters +import numpy +import os +import rclpy +from rclpy.node import Node +import sensor_msgs.msg +import sensor_msgs.srv +import threading +import time +from camera_calibration.calibrator import MonoCalibrator, StereoCalibrator, Patterns +try: + from queue import Queue +except ImportError: + from Queue import Queue +from camera_calibration.calibrator import CAMERA_MODEL +from rclpy.qos import qos_profile_system_default +from rclpy.qos import QoSProfile + + +class BufferQueue(Queue): + """Slight modification of the standard Queue that discards the oldest item + when adding an item and the queue is full. + """ + def put(self, item, *args, **kwargs): + # The base implementation, for reference: + # https://github.com/python/cpython/blob/2.7/Lib/Queue.py#L107 + # https://github.com/python/cpython/blob/3.8/Lib/queue.py#L121 + with self.mutex: + if self.maxsize > 0 and self._qsize() == self.maxsize: + self._get() + self._put(item) + self.unfinished_tasks += 1 + self.not_empty.notify() + + +class SpinThread(threading.Thread): + """ + Thread that spins the ros node, while imshow runs in the main thread + """ + + def __init__(self, node): + threading.Thread.__init__(self) + self.node = node + + def run(self): + rclpy.spin(self.node) + + +class ConsumerThread(threading.Thread): + def __init__(self, queue, function): + threading.Thread.__init__(self) + self.queue = queue + self.function = function + + def run(self): + while rclpy.ok(): + m = self.queue.get() + self.function(m) + + +class CalibrationNode(Node): + def __init__(self, name, boards, service_check = True, synchronizer = message_filters.TimeSynchronizer, flags = 0, + fisheye_flags = 0, pattern=Patterns.Chessboard, camera_name='', checkerboard_flags = 0, + max_chessboard_speed = -1, queue_size = 1): + super().__init__(name) + + self.set_camera_info_service = self.create_client(sensor_msgs.srv.SetCameraInfo, "camera/set_camera_info") + self.set_left_camera_info_service = self.create_client(sensor_msgs.srv.SetCameraInfo, "left_camera/set_camera_info") + self.set_right_camera_info_service = self.create_client(sensor_msgs.srv.SetCameraInfo, "right_camera/set_camera_info") + + if service_check: + available = False + # assume any non-default service names have been set. Wait for the service to become ready + for cli in [self.set_camera_info_service, self.set_left_camera_info_service, self.set_right_camera_info_service]: + print("Waiting for service", cli.srv_name, "...") + # check all services so they are ready. + if cli.wait_for_service(timeout_sec=1): + available = True + print("OK") + else: + print(f"Service {cli.srv_name} not found.") + + if not available: + raise RuntimeError("no camera service available") + + self._boards = boards + self._calib_flags = flags + self._fisheye_calib_flags = fisheye_flags + self._checkerboard_flags = checkerboard_flags + self._pattern = pattern + self._camera_name = camera_name + self._max_chessboard_speed = max_chessboard_speed + lsub = message_filters.Subscriber(self, sensor_msgs.msg.Image, 'left', qos_profile=self.get_topic_qos("left")) + rsub = message_filters.Subscriber(self, sensor_msgs.msg.Image, 'right', qos_profile=self.get_topic_qos("right")) + ts = synchronizer([lsub, rsub], 4) + ts.registerCallback(self.queue_stereo) + + msub = message_filters.Subscriber(self, sensor_msgs.msg.Image, 'image', qos_profile=self.get_topic_qos("image")) + msub.registerCallback(self.queue_monocular) + + self.q_mono = BufferQueue(queue_size) + self.q_stereo = BufferQueue(queue_size) + + self.c = None + + self._last_display = None + + mth = ConsumerThread(self.q_mono, self.handle_monocular) + mth.setDaemon(True) + mth.start() + + sth = ConsumerThread(self.q_stereo, self.handle_stereo) + sth.setDaemon(True) + sth.start() + + def redraw_stereo(self, *args): + pass + def redraw_monocular(self, *args): + pass + + def queue_monocular(self, msg): + self.q_mono.put(msg) + + def queue_stereo(self, lmsg, rmsg): + self.q_stereo.put((lmsg, rmsg)) + + def handle_monocular(self, msg): + if self.c == None: + if self._camera_name: + self.c = MonoCalibrator(self._boards, self._calib_flags, self._fisheye_calib_flags, self._pattern, name=self._camera_name, + checkerboard_flags=self._checkerboard_flags, + max_chessboard_speed = self._max_chessboard_speed) + else: + self.c = MonoCalibrator(self._boards, self._calib_flags, self._fisheye_calib_flags, self._pattern, + checkerboard_flags=self.checkerboard_flags, + max_chessboard_speed = self._max_chessboard_speed) + + # This should just call the MonoCalibrator + drawable = self.c.handle_msg(msg) + self.displaywidth = drawable.scrib.shape[1] + self.redraw_monocular(drawable) + + def handle_stereo(self, msg): + if self.c == None: + if self._camera_name: + self.c = StereoCalibrator(self._boards, self._calib_flags, self._fisheye_calib_flags, self._pattern, name=self._camera_name, + checkerboard_flags=self._checkerboard_flags, + max_chessboard_speed = self._max_chessboard_speed) + else: + self.c = StereoCalibrator(self._boards, self._calib_flags, self._fisheye_calib_flags, self._pattern, + checkerboard_flags=self._checkerboard_flags, + max_chessboard_speed = self._max_chessboard_speed) + + drawable = self.c.handle_msg(msg) + self.displaywidth = drawable.lscrib.shape[1] + drawable.rscrib.shape[1] + self.redraw_stereo(drawable) + + + def check_set_camera_info(self, response): + if response.success: + return True + + for i in range(10): + print("!" * 80) + print() + print("Attempt to set camera info failed: " + response.result() if response.result() is not None else "Not available") + print() + for i in range(10): + print("!" * 80) + print() + self.get_logger().error('Unable to set camera info for calibration. Failure message: %s' % response.result() if response.result() is not None else "Not available") + return False + + def do_upload(self): + self.c.report() + print(self.c.ost()) + info = self.c.as_message() + + req = sensor_msgs.srv.SetCameraInfo.Request() + rv = True + if self.c.is_mono: + req.camera_info = info + response = self.set_camera_info_service.call(req) + rv = self.check_set_camera_info(response) + else: + req.camera_info = info[0] + response = self.set_left_camera_info_service.call(req) + rv = rv and self.check_set_camera_info(response) + req.camera_info = info[1] + response = self.set_right_camera_info_service.call(req) + rv = rv and self.check_set_camera_info(response) + return rv + + def get_topic_qos(self, topic_name: str) -> QoSProfile: + """! + Given a topic name, get the QoS profile with which it is being published. + Replaces history and depth settings with default values since they cannot be retrieved + @param topic_name (str) the topic name + @return QosProfile the qos profile with which the topic is published. If no publishers exist + for the given topic, it returns the sensor data QoS. returns None in case ROS1 is being used + """ + topic_name = self.resolve_topic_name(topic_name) + topic_info = self.get_publishers_info_by_topic(topic_name=topic_name) + if len(topic_info): + qos_profile = topic_info[0].qos_profile + qos_profile.history = qos_profile_system_default.history + qos_profile.depth = qos_profile_system_default.depth + return qos_profile + else: + self.get_logger().warn(f"No publishers available for topic {topic_name}. Using system default QoS for subscriber.") + return qos_profile_system_default + + +class OpenCVCalibrationNode(CalibrationNode): + """ Calibration node with an OpenCV Gui """ + FONT_FACE = cv2.FONT_HERSHEY_SIMPLEX + FONT_SCALE = 0.6 + FONT_THICKNESS = 2 + + def __init__(self, *args, **kwargs): + + CalibrationNode.__init__(self, *args, **kwargs) + + self.queue_display = BufferQueue(maxsize=1) + self.initWindow() + + def spin(self): + sth = SpinThread(self) + sth.start() + + while rclpy.ok(): + if self.queue_display.qsize() > 0: + self.image = self.queue_display.get() + cv2.imshow("display", self.image) + else: + time.sleep(0.1) + k = cv2.waitKey(6) & 0xFF + if k in [27, ord('q')]: + return + elif k == ord('s') and self.image is not None: + self.screendump(self.image) + + def initWindow(self): + cv2.namedWindow("display", cv2.WINDOW_NORMAL) + cv2.setMouseCallback("display", self.on_mouse) + cv2.createTrackbar("Camera type: \n 0 : pinhole \n 1 : fisheye", "display", 0,1, self.on_model_change) + cv2.createTrackbar("scale", "display", 0, 100, self.on_scale) + + @classmethod + def putText(cls, img, text, org, color = (0,0,0)): + cv2.putText(img, text, org, cls.FONT_FACE, cls.FONT_SCALE, color, thickness = cls.FONT_THICKNESS) + + @classmethod + def getTextSize(cls, text): + return cv2.getTextSize(text, cls.FONT_FACE, cls.FONT_SCALE, cls.FONT_THICKNESS)[0] + + def on_mouse(self, event, x, y, flags, param): + if event == cv2.EVENT_LBUTTONDOWN and self.displaywidth < x: + if self.c.goodenough: + if 180 <= y < 280: + print("**** Calibrating ****") + # Perform calibration in another thread to prevent UI blocking + threading.Thread(target=self.c.do_calibration, name="Calibration").start() + self.buttons(self._last_display) + self.queue_display.put(self._last_display) + if self.c.calibrated: + if 280 <= y < 380: + self.c.do_save() + elif 380 <= y < 480: + # Only shut down if we set camera info correctly, #3993 + if self.do_upload(): + rclpy.shutdown() + def on_model_change(self, model_select_val): + if self.c == None: + print("Cannot change camera model until the first image has been received") + return + + self.c.set_cammodel( CAMERA_MODEL.PINHOLE if model_select_val < 0.5 else CAMERA_MODEL.FISHEYE) + + def on_scale(self, scalevalue): + if self.c.calibrated: + self.c.set_alpha(scalevalue / 100.0) + + def button(self, dst, label, enable): + dst.fill(255) + size = (dst.shape[1], dst.shape[0]) + if enable: + color = (155, 155, 80) + else: + color = (224, 224, 224) + cv2.circle(dst, (size[0] // 2, size[1] // 2), min(size) // 2, color, -1) + (w, h) = self.getTextSize(label) + self.putText(dst, label, ((size[0] - w) // 2, (size[1] + h) // 2), (255,255,255)) + + def buttons(self, display): + x = self.displaywidth + self.button(display[180:280,x:x+100], "CALIBRATE", self.c.goodenough) + self.button(display[280:380,x:x+100], "SAVE", self.c.calibrated) + self.button(display[380:480,x:x+100], "COMMIT", self.c.calibrated) + + def y(self, i): + """Set up right-size images""" + return 30 + 40 * i + + def screendump(self, im): + i = 0 + while os.access("/tmp/dump%d.png" % i, os.R_OK): + i += 1 + cv2.imwrite("/tmp/dump%d.png" % i, im) + print("Saved screen dump to /tmp/dump%d.png" % i) + + def redraw_monocular(self, drawable): + height = drawable.scrib.shape[0] + width = drawable.scrib.shape[1] + + display = numpy.zeros((max(480, height), width + 100, 3), dtype=numpy.uint8) + display[0:height, 0:width,:] = drawable.scrib + display[0:height, width:width+100,:].fill(255) + + self.buttons(display) + if not self.c.calibrated: + if drawable.params: + for i, (label, lo, hi, progress) in enumerate(drawable.params): + (w,_) = self.getTextSize(label) + self.putText(display, label, (width + (100 - w) // 2, self.y(i))) + color = (0,255,0) + if progress < 1.0: + color = (0, int(progress*255.), 255) + cv2.line(display, + (int(width + lo * 100), self.y(i) + 20), + (int(width + hi * 100), self.y(i) + 20), + color, 4) + + else: + self.putText(display, "lin.", (width, self.y(0))) + linerror = drawable.linear_error + if linerror is None or linerror < 0: + msg = "?" + else: + msg = "%.2f" % linerror + #print "linear", linerror + self.putText(display, msg, (width, self.y(1))) + + self._last_display = display + self.queue_display.put(display) + + def redraw_stereo(self, drawable): + height = drawable.lscrib.shape[0] + width = drawable.lscrib.shape[1] + + display = numpy.zeros((max(480, height), 2 * width + 100, 3), dtype=numpy.uint8) + display[0:height, 0:width,:] = drawable.lscrib + display[0:height, width:2*width,:] = drawable.rscrib + display[0:height, 2*width:2*width+100,:].fill(255) + + self.buttons(display) + + if not self.c.calibrated: + if drawable.params: + for i, (label, lo, hi, progress) in enumerate(drawable.params): + (w,_) = self.getTextSize(label) + self.putText(display, label, (2 * width + (100 - w) // 2, self.y(i))) + color = (0,255,0) + if progress < 1.0: + color = (0, int(progress*255.), 255) + cv2.line(display, + (int(2 * width + lo * 100), self.y(i) + 20), + (int(2 * width + hi * 100), self.y(i) + 20), + color, 4) + + else: + self.putText(display, "epi.", (2 * width, self.y(0))) + if drawable.epierror == -1: + msg = "?" + else: + msg = "%.2f" % drawable.epierror + self.putText(display, msg, (2 * width, self.y(1))) + # TODO dim is never set anywhere. Supposed to be observed chessboard size? + if drawable.dim != -1: + self.putText(display, "dim", (2 * width, self.y(2))) + self.putText(display, "%.3f" % drawable.dim, (2 * width, self.y(3))) + + self._last_display = display + self.queue_display.put(display) diff --git a/src/image_pipeline/camera_calibration/src/camera_calibration/camera_checker.py b/src/image_pipeline/camera_calibration/src/camera_calibration/camera_checker.py new file mode 100755 index 000000000..19149b5ec --- /dev/null +++ b/src/image_pipeline/camera_calibration/src/camera_calibration/camera_checker.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python +# +# Software License Agreement (BSD License) +# +# Copyright (c) 2009, Willow Garage, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import cv2 +import cv_bridge +import functools +import message_filters +import numpy +import rclpy +from rclpy.node import Node +import sensor_msgs.msg +import sensor_msgs.srv +import threading + +from camera_calibration.calibrator import MonoCalibrator, StereoCalibrator, ChessboardInfo +from message_filters import ApproximateTimeSynchronizer + +try: + from queue import Queue +except ImportError: + from Queue import Queue + + +def mean(seq): + return sum(seq) / len(seq) + +def lmin(seq1, seq2): + """ Pairwise minimum of two sequences """ + return [min(a, b) for (a, b) in zip(seq1, seq2)] + +def lmax(seq1, seq2): + """ Pairwise maximum of two sequences """ + return [max(a, b) for (a, b) in zip(seq1, seq2)] + +class ConsumerThread(threading.Thread): + def __init__(self, queue, function): + threading.Thread.__init__(self) + self.queue = queue + self.function = function + + def run(self): + while rclpy.ok(): + m = self.queue.get() + if self.queue.empty(): + break + self.function(m) + +class CameraCheckerNode(Node): + + def __init__(self, name, chess_size, dim, approximate=0): + super().__init__(name) + self.board = ChessboardInfo() + self.board.n_cols = chess_size[0] + self.board.n_rows = chess_size[1] + self.board.dim = dim + + # make sure n_cols is not smaller than n_rows, otherwise error computation will be off + if self.board.n_cols < self.board.n_rows: + self.board.n_cols, self.board.n_rows = self.board.n_rows, self.board.n_cols + + image_topic = "monocular/image_rect" + camera_topic = "monocular/camera_info" + + tosync_mono = [ + (image_topic, sensor_msgs.msg.Image), + (camera_topic, sensor_msgs.msg.CameraInfo), + ] + + if approximate <= 0: + sync = message_filters.TimeSynchronizer + else: + sync = functools.partial(ApproximateTimeSynchronizer, slop=approximate) + + tsm = sync([message_filters.Subscriber(self, type, topic) for (topic, type) in tosync_mono], 10) + tsm.registerCallback(self.queue_monocular) + + left_topic = "stereo/left/image_rect" + left_camera_topic = "stereo/left/camera_info" + right_topic = "stereo/right/image_rect" + right_camera_topic = "stereo/right/camera_info" + + tosync_stereo = [ + (left_topic, sensor_msgs.msg.Image), + (left_camera_topic, sensor_msgs.msg.CameraInfo), + (right_topic, sensor_msgs.msg.Image), + (right_camera_topic, sensor_msgs.msg.CameraInfo) + ] + + tss = sync([message_filters.Subscriber(self, type, topic) for (topic, type) in tosync_stereo], 10) + tss.registerCallback(self.queue_stereo) + + self.br = cv_bridge.CvBridge() + + self.q_mono = Queue() + self.q_stereo = Queue() + + mth = ConsumerThread(self.q_mono, self.handle_monocular) + mth.setDaemon(True) + mth.start() + + sth = ConsumerThread(self.q_stereo, self.handle_stereo) + sth.setDaemon(True) + sth.start() + + self.mc = MonoCalibrator([self.board]) + self.sc = StereoCalibrator([self.board]) + + def queue_monocular(self, msg, cmsg): + self.q_mono.put((msg, cmsg)) + + def queue_stereo(self, lmsg, lcmsg, rmsg, rcmsg): + self.q_stereo.put((lmsg, lcmsg, rmsg, rcmsg)) + + def mkgray(self, msg): + return self.mc.mkgray(msg) + + def image_corners(self, im): + (ok, corners, ids, b) = self.mc.get_corners(im) + if ok: + return corners, ids + else: + return None, None + + def handle_monocular(self, msg): + + (image, camera) = msg + gray = self.mkgray(image) + C, ids = self.image_corners(gray) + if C is not None: + linearity_rms = self.mc.linear_error(C, ids, self.board) + + # Add in reprojection check + image_points = C + object_points = self.mc.mk_object_points([self.board], use_board_size=True)[0] + dist_coeffs = numpy.zeros((4, 1)) + camera_matrix = numpy.array( [ [ camera.P[0], camera.P[1], camera.P[2] ], + [ camera.P[4], camera.P[5], camera.P[6] ], + [ camera.P[8], camera.P[9], camera.P[10] ] ] ) + ok, rot, trans = cv2.solvePnP(object_points, image_points, camera_matrix, dist_coeffs) + # Convert rotation into a 3x3 Rotation Matrix + rot3x3, _ = cv2.Rodrigues(rot) + # Reproject model points into image + object_points_world = numpy.asmatrix(rot3x3) * numpy.asmatrix(object_points.squeeze().T) + numpy.asmatrix(trans) + reprojected_h = camera_matrix * object_points_world + reprojected = (reprojected_h[0:2, :] / reprojected_h[2, :]) + reprojection_errors = image_points.squeeze().T - reprojected + + reprojection_rms = numpy.sqrt(numpy.sum(numpy.array(reprojection_errors) ** 2) / numpy.product(reprojection_errors.shape)) + + # Print the results + print("Linearity RMS Error: %.3f Pixels Reprojection RMS Error: %.3f Pixels" % (linearity_rms, reprojection_rms)) + else: + print('no chessboard') + + def handle_stereo(self, msg): + + (lmsg, lcmsg, rmsg, rcmsg) = msg + lgray = self.mkgray(lmsg) + rgray = self.mkgray(rmsg) + + L, _ = self.image_corners(lgray) + R, _ = self.image_corners(rgray) + if L is not None and R is not None: + epipolar = self.sc.epipolar_error(L, R) + + dimension = self.sc.chessboard_size(L, R, self.board, msg=(lcmsg, rcmsg)) + + print("epipolar error: %f pixels dimension: %f m" % (epipolar, dimension)) + else: + print("no chessboard") diff --git a/src/image_pipeline/camera_calibration/src/camera_calibration/nodes/__init__.py b/src/image_pipeline/camera_calibration/src/camera_calibration/nodes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/camera_calibration/src/camera_calibration/nodes/cameracalibrator.py b/src/image_pipeline/camera_calibration/src/camera_calibration/nodes/cameracalibrator.py new file mode 100755 index 000000000..df08e2706 --- /dev/null +++ b/src/image_pipeline/camera_calibration/src/camera_calibration/nodes/cameracalibrator.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python +# +# Software License Agreement (BSD License) +# +# Copyright (c) 2009, Willow Garage, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import cv2 +import functools +import message_filters +import rclpy +from camera_calibration.camera_calibrator import OpenCVCalibrationNode +from camera_calibration.calibrator import ChessboardInfo, Patterns +from message_filters import ApproximateTimeSynchronizer + +def optionsValidCharuco(options, parser): + """ + Validates the provided options when the pattern type is 'charuco' + """ + if options.pattern != 'charuco': return False + + n_boards = len(options.size) + if (n_boards != len(options.square) or n_boards != len(options.charuco_marker_size) or n_boards != + len(options.aruco_dict)): + parser.error("When using ChArUco boards, --size, --square, --charuco_marker_size, and --aruco_dict " + + "must be specified for each board") + return False + + # TODO: check for fisheye and stereo (not implemented with ChArUco) + return True + + +def main(): + from optparse import OptionParser, OptionGroup + parser = OptionParser("%prog --size SIZE1 --square SQUARE1 [ --size SIZE2 --square SQUARE2 ]", + description=None) + parser.add_option("-c", "--camera_name", + type="string", default='narrow_stereo', + help="name of the camera to appear in the calibration file") + group = OptionGroup(parser, "Chessboard Options", + "You must specify one or more chessboards as pairs of --size and --square options.") + group.add_option("-p", "--pattern", + type="string", default="chessboard", + help="calibration pattern to detect - 'chessboard', 'circles', 'acircles', 'charuco'\n" + + " if 'charuco' is used, a --charuco_marker_size and --aruco_dict argument must be supplied\n" + + " with each --size and --square argument") + group.add_option("-s", "--size", + action="append", default=[], + help="chessboard size as NxM, counting interior corners (e.g. a standard chessboard is 7x7)") + group.add_option("-q", "--square", + action="append", default=[], + help="chessboard square size in meters") + group.add_option("-m", "--charuco_marker_size", + action="append", default=[], + help="ArUco marker size (meters); only valid with `-p charuco`") + group.add_option("-d", "--aruco_dict", + action="append", default=[], + help="ArUco marker dictionary; only valid with `-p charuco`; one of 'aruco_orig', '4x4_250', " + + "'5x5_250', '6x6_250', '7x7_250'") + parser.add_option_group(group) + group = OptionGroup(parser, "ROS Communication Options") + group.add_option("--approximate", + type="float", default=0.0, + help="allow specified slop (in seconds) when pairing images from unsynchronized stereo cameras") + group.add_option("--no-service-check", + action="store_false", dest="service_check", default=True, + help="disable check for set_camera_info services at startup") + group.add_option("--queue-size", + type="int", default=1, + help="image queue size (default %default, set to 0 for unlimited)") + parser.add_option_group(group) + group = OptionGroup(parser, "Calibration Optimizer Options") + group.add_option("--fix-principal-point", + action="store_true", default=False, + help="for pinhole, fix the principal point at the image center") + group.add_option("--fix-aspect-ratio", + action="store_true", default=False, + help="for pinhole, enforce focal lengths (fx, fy) are equal") + group.add_option("--zero-tangent-dist", + action="store_true", default=False, + help="for pinhole, set tangential distortion coefficients (p1, p2) to zero") + group.add_option("-k", "--k-coefficients", + type="int", default=2, metavar="NUM_COEFFS", + help="for pinhole, number of radial distortion coefficients to use (up to 6, default %default)") + + group.add_option("--fisheye-recompute-extrinsicsts", + action="store_true", default=False, + help="for fisheye, extrinsic will be recomputed after each iteration of intrinsic optimization") + group.add_option("--fisheye-fix-skew", + action="store_true", default=False, + help="for fisheye, skew coefficient (alpha) is set to zero and stay zero") + group.add_option("--fisheye-fix-principal-point", + action="store_true", default=False, + help="for fisheye,fix the principal point at the image center") + group.add_option("--fisheye-k-coefficients", + type="int", default=4, metavar="NUM_COEFFS", + help="for fisheye, number of radial distortion coefficients to use fixing to zero the rest (up to 4, default %default)") + + group.add_option("--fisheye-check-conditions", + action="store_true", default=False, + help="for fisheye, the functions will check validity of condition number") + + group.add_option("--disable_calib_cb_fast_check", action='store_true', default=False, + help="uses the CALIB_CB_FAST_CHECK flag for findChessboardCorners") + group.add_option("--max-chessboard-speed", type="float", default=-1.0, + help="Do not use samples where the calibration pattern is moving faster \ + than this speed in px/frame. Set to eg. 0.5 for rolling shutter cameras.") + + parser.add_option_group(group) + options, _ = parser.parse_args(rclpy.utilities.remove_ros_args()) + + if (len(options.size) != len(options.square)): + parser.error("Number of size and square inputs must be the same!") + + if not options.square: + options.square.append("0.108") + options.size.append("8x6") + + boards = [] + if options.pattern == "charuco" and optionsValidCharuco(options, parser): + for (sz, sq, ms, ad) in zip(options.size, options.square, options.charuco_marker_size, options.aruco_dict): + size = tuple([int(c) for c in sz.split('x')]) + boards.append(ChessboardInfo('charuco', size[0], size[1], float(sq), float(ms), ad)) + else: + for (sz, sq) in zip(options.size, options.square): + size = tuple([int(c) for c in sz.split('x')]) + boards.append(ChessboardInfo(options.pattern, size[0], size[1], float(sq))) + + if options.approximate == 0.0: + sync = message_filters.TimeSynchronizer + else: + sync = functools.partial(ApproximateTimeSynchronizer, slop=options.approximate) + + # Pinhole opencv calibration options parsing + num_ks = options.k_coefficients + + calib_flags = 0 + if options.fix_principal_point: + calib_flags |= cv2.CALIB_FIX_PRINCIPAL_POINT + if options.fix_aspect_ratio: + calib_flags |= cv2.CALIB_FIX_ASPECT_RATIO + if options.zero_tangent_dist: + calib_flags |= cv2.CALIB_ZERO_TANGENT_DIST + if (num_ks > 3): + calib_flags |= cv2.CALIB_RATIONAL_MODEL + if (num_ks < 6): + calib_flags |= cv2.CALIB_FIX_K6 + if (num_ks < 5): + calib_flags |= cv2.CALIB_FIX_K5 + if (num_ks < 4): + calib_flags |= cv2.CALIB_FIX_K4 + if (num_ks < 3): + calib_flags |= cv2.CALIB_FIX_K3 + if (num_ks < 2): + calib_flags |= cv2.CALIB_FIX_K2 + if (num_ks < 1): + calib_flags |= cv2.CALIB_FIX_K1 + + # Opencv calibration flags parsing: + num_ks = options.fisheye_k_coefficients + fisheye_calib_flags = 0 + if options.fisheye_fix_principal_point: + fisheye_calib_flags |= cv2.fisheye.CALIB_FIX_PRINCIPAL_POINT + if options.fisheye_fix_skew: + fisheye_calib_flags |= cv2.fisheye.CALIB_FIX_SKEW + if options.fisheye_recompute_extrinsicsts: + fisheye_calib_flags |= cv2.fisheye.CALIB_RECOMPUTE_EXTRINSIC + if options.fisheye_check_conditions: + fisheye_calib_flags |= cv2.fisheye.CALIB_CHECK_COND + if (num_ks < 4): + fisheye_calib_flags |= cv2.fisheye.CALIB_FIX_K4 + if (num_ks < 3): + fisheye_calib_flags |= cv2.fisheye.CALIB_FIX_K3 + if (num_ks < 2): + fisheye_calib_flags |= cv2.fisheye.CALIB_FIX_K2 + if (num_ks < 1): + fisheye_calib_flags |= cv2.fisheye.CALIB_FIX_K1 + + pattern = Patterns.Chessboard + if options.pattern == 'circles': + pattern = Patterns.Circles + elif options.pattern == 'acircles': + pattern = Patterns.ACircles + elif options.pattern == 'charuco': + pattern = Patterns.ChArUco + elif options.pattern != 'chessboard': + print('Unrecognized pattern %s, defaulting to chessboard' % options.pattern) + + if options.disable_calib_cb_fast_check: + checkerboard_flags = 0 + else: + checkerboard_flags = cv2.CALIB_CB_FAST_CHECK + + rclpy.init() + node = OpenCVCalibrationNode("cameracalibrator", boards, options.service_check, sync, + calib_flags, fisheye_calib_flags, pattern, options.camera_name, + checkerboard_flags=checkerboard_flags, max_chessboard_speed=options.max_chessboard_speed, + queue_size=options.queue_size) + node.spin() + rclpy.shutdown() + +if __name__ == "__main__": + try: + main() + except Exception as e: + import traceback + traceback.print_exc() diff --git a/src/image_pipeline/camera_calibration/src/camera_calibration/nodes/cameracheck.py b/src/image_pipeline/camera_calibration/src/camera_calibration/nodes/cameracheck.py new file mode 100755 index 000000000..b2b3906cf --- /dev/null +++ b/src/image_pipeline/camera_calibration/src/camera_calibration/nodes/cameracheck.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# +# Software License Agreement (BSD License) +# +# Copyright (c) 2009, Willow Garage, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import rclpy +from camera_calibration.camera_checker import CameraCheckerNode + + +def main(): + from optparse import OptionParser + parser = OptionParser() + parser.add_option("-s", "--size", default="8x6", help="specify chessboard size as nxm [default: %default]") + parser.add_option("-q", "--square", default=".108", help="specify chessboard square size in meters [default: %default]") + parser.add_option("--approximate", + type="float", default=0.0, + help="allow specified slop (in seconds) when pairing images from unsynchronized stereo cameras") + + options, _ = parser.parse_args(rclpy.utilities.remove_ros_args()) + rclpy.init() + + size = tuple([int(c) for c in options.size.split('x')]) + dim = float(options.square) + approximate = float(options.approximate) + node = CameraCheckerNode("cameracheck", size, dim, approximate) + rclpy.spin(node) + +if __name__ == "__main__": + main() diff --git a/src/image_pipeline/camera_calibration/test/test_directed.py b/src/image_pipeline/camera_calibration/test/test_directed.py new file mode 100644 index 000000000..80ec1a8b9 --- /dev/null +++ b/src/image_pipeline/camera_calibration/test/test_directed.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python +# +# Software License Agreement (BSD License) +# +# Copyright (c) 2009, Willow Garage, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import cv2 + +import collections +import copy +import numpy +import os +import requests +import tarfile +import unittest + +from camera_calibration.calibrator import MonoCalibrator, StereoCalibrator, \ + Patterns, CalibrationException, ChessboardInfo, image_from_archive + +board = ChessboardInfo() +board.n_cols = 8 +board.n_rows = 6 +board.dim = 0.108 + +class TestDirected(unittest.TestCase): + def setUp(self): + if not os.path.isfile('/tmp/camera_calibration.tar.gz'): + url = 'http://download.ros.org/data/camera_calibration/camera_calibration.tar.gz' + r = requests.get(url, allow_redirects=True) + with open('/tmp/camera_calibration.tar.gz', 'wb') as cf: + cf.write(r.content) + + tar_path = '/tmp/camera_calibration.tar.gz' + self.tar = tarfile.open(tar_path, 'r') + self.limages = [image_from_archive(self.tar, "wide/left%04d.pgm" % i) for i in range(3, 15)] + self.rimages = [image_from_archive(self.tar, "wide/right%04d.pgm" % i) for i in range(3, 15)] + self.l = {} + self.r = {} + self.sizes = [(320,240), (640,480), (800,600), (1024,768)] + for dim in self.sizes: + self.l[dim] = [] + self.r[dim] = [] + for li,ri in zip(self.limages, self.rimages): + rli = cv2.resize(li, (dim[0], dim[1])) + rri = cv2.resize(ri, (dim[0], dim[1])) + self.l[dim].append(rli) + self.r[dim].append(rri) + + def assert_good_mono(self, c, dim, max_err): + self.assertTrue(len(c.ost()) > 0) + lin_err = 0 + n = 0 + for img in self.l[dim]: + lin_err_local = c.linear_error_from_image(img) + if lin_err_local: + lin_err += lin_err_local + n += 1 + if n > 0: + lin_err /= n + self.assertTrue(0.0 < lin_err, 'lin_err is %f' % lin_err) + self.assertTrue(lin_err < max_err, 'lin_err is %f' % lin_err) + + flat = c.remap(img) + self.assertEqual(img.shape, flat.shape) + + def test_monocular(self): + # Run the calibrator, produce a calibration, check it + mc = MonoCalibrator([ board ], cv2.CALIB_FIX_K3) + max_errs = [0.1, 0.2, 0.4, 0.7] + for i, dim in enumerate(self.sizes): + mc.cal(self.l[dim]) + self.assert_good_mono(mc, dim, max_errs[i]) + + # Make another calibration, import previous calibration as a message, + # and assert that the new one is good. + + mc2 = MonoCalibrator([board]) + mc2.from_message(mc.as_message()) + self.assert_good_mono(mc2, dim, max_errs[i]) + + def test_stereo(self): + epierrors = [0.1, 0.2, 0.45, 1.0] + for i, dim in enumerate(self.sizes): + print("Dim =", dim) + sc = StereoCalibrator([board], cv2.CALIB_FIX_K3) + sc.cal(self.l[dim], self.r[dim]) + + sc.report() + #print sc.ost() + + # NOTE: epipolar error currently increases with resolution. + # At highest res expect error ~0.75 + epierror = 0 + n = 0 + for l_img, r_img in zip(self.l[dim], self.r[dim]): + epierror_local = sc.epipolar_error_from_images(l_img, r_img) + if epierror_local: + epierror += epierror_local + n += 1 + epierror /= n + self.assertTrue(epierror < epierrors[i], + 'Epipolar error is %f for resolution i = %d' % (epierror, i)) + + self.assertAlmostEqual(sc.chessboard_size_from_images(self.l[dim][0], self.r[dim][0]), .108, 2) + + #print sc.as_message() + + img = self.l[dim][0] + flat = sc.l.remap(img) + self.assertEqual(img.shape, flat.shape) + flat = sc.r.remap(img) + self.assertEqual(img.shape, flat.shape) + + sc2 = StereoCalibrator([board]) + sc2.from_message(sc.as_message()) + # sc2.set_alpha(1.0) + #sc2.report() + self.assertTrue(len(sc2.ost()) > 0) + + def test_nochecker(self): + # Run with same images, but looking for an incorrect chessboard size (8, 7). + # Should raise an exception because of lack of input points. + new_board = copy.deepcopy(board) + new_board.n_cols = 8 + new_board.n_rows = 7 + + sc = StereoCalibrator([new_board]) + self.assertRaises(CalibrationException, lambda: sc.cal(self.limages, self.rimages)) + mc = MonoCalibrator([new_board]) + self.assertRaises(CalibrationException, lambda: mc.cal(self.limages)) + + + +class TestArtificial(unittest.TestCase): + Setup = collections.namedtuple('Setup', ['pattern', 'cols', 'rows', 'lin_err', 'K_err']) + + def setUp(self): + # Define some image transforms that will simulate a camera position + M = [] + self.k = numpy.array([[500, 0, 250], [0, 500, 250], [0, 0, 1]], numpy.float32) + self.d = numpy.array([]) + # physical size of the board + self.board_width_dim = 1 + + # Generate data for different grid types. For each grid type, define the different sizes of + # grid that are recognized (n row, n col) + # Patterns.Circles, Patterns.ACircles + self.setups = [ self.Setup(pattern=Patterns.Chessboard, cols=7, rows=8, lin_err=0.2, K_err=8.2), + self.Setup(pattern=Patterns.Circles, cols=7, rows=8, lin_err=0.1, K_err=4), + self.Setup(pattern=Patterns.ACircles, cols=3, rows=5, lin_err=0.1, K_err=8) ] + self.limages = [] + self.rimages = [] + for setup in self.setups: + self.limages.append([]) + self.rimages.append([]) + + # Create the pattern + if setup.pattern == Patterns.Chessboard: + pattern = numpy.zeros((50*(setup.rows+3), 50*(setup.cols+3), 1), numpy.uint8) + pattern.fill(255) + for j in range(1, setup.rows+2): + for i in range(1+(j%2), setup.cols+2, 2): + pattern[50*j:50*(j+1), 50*i:50*(i+1)].fill(0) + elif setup.pattern == Patterns.Circles: + pattern = numpy.zeros((50*(setup.rows+2), 50*(setup.cols+2), 1), numpy.uint8) + pattern.fill(255) + for j in range(1, setup.rows+1): + for i in range(1, setup.cols+1): + cv2.circle(pattern, (int(50*i + 25), int(50*j + 25)), 15, (0,0,0), -1) + elif setup.pattern == Patterns.ACircles: + x = 60 + pattern = numpy.zeros((x*(setup.rows+2), x*(setup.cols+5), 1), numpy.uint8) + pattern.fill(255) + for j in range(1, setup.rows+1): + for i in range(0, setup.cols): + cv2.circle(pattern, (int(x*(1 + 2*i + (j%2)) + x/2), int(x*j + x/2)), int(x/3), (0,0,0), -1) + + rows, cols, _ = pattern.shape + object_points_2d = numpy.array([[0, 0], [0, cols-1], [rows-1, cols-1], [rows-1, 0]], numpy.float32) + object_points_3d = numpy.array([[0, 0, 0], [0, cols-1, 0], [rows-1, cols-1, 0], [rows-1, 0, 0]], numpy.float32) + object_points_3d *= self.board_width_dim/float(cols) + + # create the artificial view points + rvec = [ [0, 0, 0], [0, 0, 0.4], [0, 0.4, 0], [0.4, 0, 0], [0.4, 0.4, 0], [0.4, 0, 0.4], [0, 0.4, 0.4], [0.4, 0.4, 0.4] ] + tvec = [ [-0.5, -0.5, 3], [-0.5, -0.5, 3], [-0.5, -0.1, 3], [-0.1, -0.5, 3], [-0.1, -0.1, 3], [-0.1, -0.5, 3], [-0.5, -0.1, 3], [-0.1, 0.1, 3] ] + + dsize = (480, 640) + for i in range(len(rvec)): + R = numpy.array(rvec[i], numpy.float32) + T = numpy.array(tvec[i], numpy.float32) + + image_points, _ = cv2.projectPoints(object_points_3d, R, T, self.k, self.d) + + # deduce the perspective transform + M.append(cv2.getPerspectiveTransform(object_points_2d, image_points)) + + # project the pattern according to the different cameras + pattern_warped = cv2.warpPerspective(pattern, M[i], dsize) + self.limages[-1].append(pattern_warped) + + def assert_good_mono(self, c, images, max_err): + #c.report() + self.assertTrue(len(c.ost()) > 0) + lin_err = 0 + n = 0 + for img in images: + lin_err_local = c.linear_error_from_image(img) + if lin_err_local: + lin_err += lin_err_local + n += 1 + if n > 0: + lin_err /= n + print("linear error is %f" % lin_err) + self.assertTrue(0.0 < lin_err, 'lin_err is %f' % lin_err) + self.assertTrue(lin_err < max_err, 'lin_err is %f' % lin_err) + + flat = c.remap(img) + self.assertEqual(img.shape, flat.shape) + + def test_monocular(self): + # Run the calibrator, produce a calibration, check it + for i, setup in enumerate(self.setups): + board = ChessboardInfo() + board.n_cols = setup.cols + board.n_rows = setup.rows + board.dim = self.board_width_dim + + mc = MonoCalibrator([ board ], flags=cv2.CALIB_FIX_K3, pattern=setup.pattern) + + if 0: + # display the patterns viewed by the camera + for pattern_warped in self.limages[i]: + cv2.imshow("toto", pattern_warped) + cv2.waitKey(0) + + mc.cal(self.limages[i]) + self.assert_good_mono(mc, self.limages[i], setup.lin_err) + + # Make sure the intrinsics are similar + err_intrinsics = numpy.linalg.norm(mc.intrinsics - self.k, ord=numpy.inf) + self.assertTrue(err_intrinsics < setup.K_err, + 'intrinsics error is %f for resolution i = %d' % (err_intrinsics, i)) + print('intrinsics error is %f' % numpy.linalg.norm(mc.intrinsics - self.k, ord=numpy.inf)) + + def test_rational_polynomial_model(self): + """Test that the distortion coefficients returned for a rational_polynomial model are not empty.""" + for i, setup in enumerate(self.setups): + board = ChessboardInfo() + board.n_cols = setup.cols + board.n_rows = setup.rows + board.dim = self.board_width_dim + + mc = MonoCalibrator([ board ], flags=cv2.CALIB_RATIONAL_MODEL, pattern=setup.pattern) + mc.cal(self.limages[i]) + self.assertEqual(len(mc.distortion.flat), 8, + 'length of distortion coefficients is %d' % len(mc.distortion.flat)) + self.assertTrue(all(mc.distortion.flat != 0), + 'some distortion coefficients are zero: %s' % str(mc.distortion.flatten())) + self.assertEqual(mc.as_message().distortion_model, 'rational_polynomial') + self.assert_good_mono(mc, self.limages[i], setup.lin_err) + + yaml = mc.yaml() + # Issue #278 + self.assertIn('cols: 8', yaml) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/src/image_pipeline/camera_calibration/test/test_multiple_boards.py b/src/image_pipeline/camera_calibration/test/test_multiple_boards.py new file mode 100644 index 000000000..40e1faa50 --- /dev/null +++ b/src/image_pipeline/camera_calibration/test/test_multiple_boards.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# +# Software License Agreement (BSD License) +# +# Copyright (c) 2009, Willow Garage, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import rclpy +import ament_index_python +import requests +import unittest +import tarfile +import os + +from camera_calibration.calibrator import StereoCalibrator, ChessboardInfo, image_from_archive + +# Large board used for PR2 calibration +board = ChessboardInfo() +board.n_cols = 7 +board.n_rows = 6 +board.dim = 0.108 + +class TestMultipleBoards(unittest.TestCase): + def test_multiple_boards(self): + small_board = ChessboardInfo() + small_board.n_cols = 5 + small_board.n_rows = 4 + small_board.dim = 0.025 + + stereo_cal = StereoCalibrator([board, small_board]) + if not os.path.isfile('/tmp/multi_board_calibration.tar.gz'): + url = 'http://download.ros.org/data/camera_calibration/multi_board_calibration.tar.gz' + r = requests.get(url, allow_redirects=True) + with open('/tmp/multi_board_calibration.tar.gz', 'wb') as mcf: + mcf.write(r.content) + + my_archive_name = '/tmp/multi_board_calibration.tar.gz' + stereo_cal.do_tarfile_calibration(my_archive_name) + + stereo_cal.report() + stereo_cal.ost() + + # Check error for big image + archive = tarfile.open(my_archive_name) + l1_big = image_from_archive(archive, "left-0000.png") + r1_big = image_from_archive(archive, "right-0000.png") + epi_big = stereo_cal.epipolar_error_from_images(l1_big, r1_big) + self.assertTrue(epi_big < 1.0, "Epipolar error for large checkerboard > 1.0. Error: %.2f" % epi_big) + + # Small checkerboard has larger error threshold for now + l1_sm = image_from_archive(archive, "left-0012-sm.png") + r1_sm = image_from_archive(archive, "right-0012-sm.png") + epi_sm = stereo_cal.epipolar_error_from_images(l1_sm, r1_sm) + self.assertTrue(epi_sm < 2.0, "Epipolar error for small checkerboard > 2.0. Error: %.2f" % epi_sm) + + + +if __name__ == '__main__': + unittest.main(verbosity=2) \ No newline at end of file diff --git a/src/image_pipeline/depth_image_proc/CHANGELOG.rst b/src/image_pipeline/depth_image_proc/CHANGELOG.rst new file mode 100644 index 000000000..01f4c2d7a --- /dev/null +++ b/src/image_pipeline/depth_image_proc/CHANGELOG.rst @@ -0,0 +1,223 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package depth_image_proc +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +4.0.0 (2022-12-24) +------------------ +* [backport iron] support rgba8 and bgra8 encodings by skipping alpha channel (`#869 `_) (`#896 `_) + backport `#869 `_ +* [backport iron] ROS 2: Add option to use the RGB image timestamp for the registered depth image (`#872 `_) (`#894 `_) + backport `#872 `_ +* [backport Iron] Support MONO16 image encodings: point_cloud_xyz (`#868 `_) (`#882 `_) + backport Iron `#868 `_ +* [backport iron] ROS 2: depth_image_proc/point_cloud_xyzi_radial Add intensity conversion (copy) for float (`#867 `_) (`#880 `_) + Backport `#867 `_ +* allow use as component or node (`#858 `_) + Backport `#852 `_ to Iron +* add myself as a maintainer (`#846 `_) +* Depth image transport configure susbcribers (`#844 `_) (`#845 `_) +* Updated depth_image_proc for ros2 + Instantiated template for convertDepth, added options to register, and + changed register from a class loader to an RCLPP component. +* Contributors: Alejandro Hernández Cordero, Michael Ferguson, ksommerkohrt + +3.0.1 (2022-12-04) +------------------ +* Replace deprecated headers + Fixing compiler warnings. +* Contributors: Jacob Perron + +3.0.0 (2022-04-29) +------------------ +* Cleanup of depth_image_proc. +* Fix linker error caused by templating in the conversions.cpp file (`#718 `_) +* Port upsampling interpolation from `#363 `_ to ROS2 (`#692 `_) +* Fix uncrustify errors +* allow loading depth_image_proc::RegisterNode as a component +* Replace deprecated geometry2 headers +* Fixed typo in pointcloudxyz launch file +* use unique_ptrs, remove unused code, add back in missing initMatrix call +* add xyzrgb radial node +* Use RCLCPP_WARN_THROTTLE (10 secs) to avoid terminal spam +* Fix tiny error in comment +* Warning instead of fatal error when frames are differents +* revert a293252 +* Replace deprecated geometry2 headers +* Add maintainer (`#667 `_) +* move to hpp/cpp structure, create conversions file +* Fix deprecation warning calling declare_parameter +* Contributors: Chris Lalancette, Evan Flynn, Francisco Martin Rico, Francisco Martín Rico, Harshal Deshpande, Jacob Perron, Joe Schornak, Joseph Schornak, Joshua Whitley, Patrick Musau + +2.2.1 (2020-08-27) +------------------ +* remove email blasts from steve macenski (`#596 `_) +* [Foxy] Use ament_auto Macros (`#573 `_) + * Fixing version and maintainer problems in camera_calibration. + * Applying ament_auto macros to depth_image_proc. + * Cleaning up package.xml in image_pipeline. + * Applying ament_auto macros to image_proc. + * Applying ament_auto macros to image_publisher. + * Applying ament_auto macros to image_rotate. + * Applying ament_auto macros to image_view. + * Replacing some deprecated headers in image_view. + * Fixing some build warnings in image_view. + * Applying ament_auto macros to stereo_image_proc. + * Adding some linter tests to image_pipeline. + * Updating package URLs to point to ROS Index. +* Add rclcpp and rclcpp_components dependencies to package.xml. (`#569 `_) (`#570 `_) + I noticed that these are listed in CMakeLists.txt but not in package.xml + and this is causing a build failure for the binary releases on + build.ros2.org: + http://build.ros2.org/view/Dbin_ubhf_uBhf/job/Dbin_uB64__depth_image_proc__ubuntu_bionic_amd64__binary/ + Co-authored-by: Steven! Ragnarök +* Contributors: Joshua Whitley, Steve Macenski + +2.2.0 (2020-07-27) +------------------ +* Replacing deprecated header includes with new HPP versions. (`#566 `_) + * Replacing deprecated header includes with new HPP versions. + * CI: Switching to official Foxy Docker container. + * Fixing headers which don't work correctly. +* Contributors: Joshua Whitley + +* make parameters work in depth_image_proc (`#544 `_) +* update depth_image_proc components (`#543 `_) + * update depth_image_proc components + This makes them loadable with the rclcpp_components + interface. I've fully tested PointCloudXYZRGB and + ConvertMetric, my use case doesn't use the others. + I also lack a setup to test the launch files fully, + but ran them with the realsense commented out and + they appear to be OK. + * fix linting +* Contributors: Michael Ferguson + +2.0.0 (2018-12-09) +------------------ +* enable rclcpp_register_node_plugins (`#368 `_) +* Port depth image proc on ROS2 (`#362 `_) +* Initial ROS2 commit. +* Contributors: Chris Ye, Michael Carroll + +1.12.23 (2018-05-10) +-------------------- + +1.12.22 (2017-12-08) +-------------------- + +1.12.21 (2017-11-05) +-------------------- +* Fix C++11 compilation + This fixes `#292 `_ and `#291 `_ +* Contributors: Vincent Rabaud + +1.12.20 (2017-04-30) +-------------------- +* Fix CMake warnings about Eigen. +* Convert depth image metric from [m] to [mm] +* address gcc6 build error + With gcc6, compiling fails with `stdlib.h: No such file or directory`, + as including '-isystem /usr/include' breaks with gcc6, cf., + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70129. + This commit addresses this issue for this package in the same way + it was addressed in various other ROS packages. A list of related + commits and pull requests is at: + https://github.com/ros/rosdistro/issues/12783 + Signed-off-by: Lukas Bulwahn +* Contributors: Kentaro Wada, Lukas Bulwahn, Vincent Rabaud + +1.12.19 (2016-07-24) +-------------------- + +1.12.18 (2016-07-12) +-------------------- + +1.12.17 (2016-07-11) +-------------------- + +1.12.16 (2016-03-19) +-------------------- +* check number of channels before the process +* search minimum value with OpenCV +* Use OpenCV to be faster +* Add a feature for a depth image to crop foremost image +* Contributors: Kenta Yonekura + +1.12.15 (2016-01-17) +-------------------- +* Add option for exact time sync for point_cloud_xyzrgb +* simplify OpenCV3 conversion +* Contributors: Kentaro Wada, Vincent Rabaud + +1.12.14 (2015-07-22) +-------------------- + +1.12.13 (2015-04-06) +-------------------- +* Add radial point cloud processors +* Contributors: Hunter Laux + +1.12.12 (2014-12-31) +-------------------- +* adds range_max +* exports depth_conversions + with convert for xyz PC only +* exports DepthTraits +* Contributors: enriquefernandez + +1.12.11 (2014-10-26) +-------------------- + +1.12.10 (2014-09-28) +-------------------- + +1.12.9 (2014-09-21) +------------------- +* get code to compile with OpenCV3 + fixes `#96 `_ +* Contributors: Vincent Rabaud + +1.12.8 (2014-08-19) +------------------- + +1.12.6 (2014-07-27) +------------------- +* Add point_cloud_xyzi nodelet + This is for cameras that output depth and intensity images. + It's based on the point_cloud_xyzrgb nodelet. +* Missing runtime dependency - eigen_conversions + `libdepth_image_proc` is missing this dependency at runtime + ``` + > ldd libdepth_image_proc.so | grep eigen + libeigen_conversions.so => not found + ``` + Which causes the following error on loading depth_image_proc: + ``` + [ INFO] [1402564815.530736554]: /camera/rgb/camera_info -> /camera/rgb/camera_info + [ERROR] [1402564815.727176562]: Failed to load nodelet [/camera/depth_metric_rect] of type + [depth_image_proc/convert_metric]: Failed to load library /opt/ros/indigo/lib//libdepth_image_proc.so. + Make sure that you are calling the PLUGINLIB_EXPORT_CLASS macro in the library code, and that + names are consistent between this macro and your XML. Error string: Could not load library (Poco + exception = libeigen_conversions.so: cannot open shared object file: No such file or directory) + [FATAL] [1402564815.727410623]: Service call failed! + ``` +* Contributors: Daniel Stonier, Hunter Laux + +1.12.4 (2014-04-28) +------------------- +* depth_image_proc: fix missing symbols in nodelets +* Contributors: Michael Ferguson + +1.12.3 (2014-04-12) +------------------- + +1.12.2 (2014-04-08) +------------------- + +1.12.1 (2014-04-06) +------------------- +* replace tf usage by tf2 usage + +1.12.0 (2014-04-04) +------------------- +* remove PCL dependency diff --git a/src/image_pipeline/depth_image_proc/CMakeLists.txt b/src/image_pipeline/depth_image_proc/CMakeLists.txt new file mode 100644 index 000000000..1a0dc88e1 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/CMakeLists.txt @@ -0,0 +1,88 @@ +cmake_minimum_required(VERSION 3.5) +project(depth_image_proc) + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake_auto REQUIRED) +ament_auto_find_build_dependencies() + +find_package(Eigen3 QUIET) +if(NOT EIGEN3_FOUND) + find_package(Eigen REQUIRED) + set(EIGEN3_INCLUDE_DIRS ${EIGEN_INCLUDE_DIRS}) + include_directories(include ${EIGEN3_INCLUDE_DIRS}) +endif() + +find_package(OpenCV REQUIRED) + +ament_auto_add_library(${PROJECT_NAME} SHARED + src/conversions.cpp + src/convert_metric.cpp + src/crop_foremost.cpp + src/disparity.cpp + src/point_cloud_xyz.cpp + src/point_cloud_xyzrgb.cpp + src/point_cloud_xyzi.cpp + src/point_cloud_xyz_radial.cpp + src/point_cloud_xyzi_radial.cpp + src/point_cloud_xyzrgb_radial.cpp + src/register.cpp +) + +# Register individual components and also build standalone nodes for each +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::ConvertMetricNode" + EXECUTABLE convert_metric_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::CropForemostNode" + EXECUTABLE crop_foremost_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::DisparityNode" + EXECUTABLE disparity_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::PointCloudXyzNode" + EXECUTABLE point_cloud_xyz_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::PointCloudXyzrgbNode" + EXECUTABLE point_cloud_xyzrgb_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::PointCloudXyziNode" + EXECUTABLE point_cloud_xyzi_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::PointCloudXyzRadialNode" + EXECUTABLE point_cloud_xyz_radial_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::PointCloudXyziRadialNode" + EXECUTABLE point_cloud_xyzi_radial_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::PointCloudXyziRadialNode" + EXECUTABLE point_cloud_xyzrgb_radial_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "depth_image_proc::RegisterNode" + EXECUTABLE register_node +) + +target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBRARIES}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_auto_package(INSTALL_TO_SHARE launch) diff --git a/src/image_pipeline/depth_image_proc/include/depth_image_proc/conversions.hpp b/src/image_pipeline/depth_image_proc/include/depth_image_proc/conversions.hpp new file mode 100644 index 000000000..eef392c63 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/include/depth_image_proc/conversions.hpp @@ -0,0 +1,155 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__CONVERSIONS_HPP_ +#define DEPTH_IMAGE_PROC__CONVERSIONS_HPP_ + +#include + +#include "image_geometry/pinhole_camera_model.hpp" + +#include + +#include +#include +#include +#include + +namespace depth_image_proc +{ + +// Handles float or uint16 depths +template +void convertDepth( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg, + const image_geometry::PinholeCameraModel & model, + double range_max = 0.0) +{ + // Use correct principal point from calibration + float center_x = model.cx(); + float center_y = model.cy(); + + // Combine unit conversion (if necessary) with scaling by focal length for computing (X,Y) + double unit_scaling = DepthTraits::toMeters(T(1) ); + float constant_x = unit_scaling / model.fx(); + float constant_y = unit_scaling / model.fy(); + float bad_point = std::numeric_limits::quiet_NaN(); + + sensor_msgs::PointCloud2Iterator iter_x(*cloud_msg, "x"); + sensor_msgs::PointCloud2Iterator iter_y(*cloud_msg, "y"); + sensor_msgs::PointCloud2Iterator iter_z(*cloud_msg, "z"); + const T * depth_row = reinterpret_cast(&depth_msg->data[0]); + int row_step = depth_msg->step / sizeof(T); + for (int v = 0; v < static_cast(cloud_msg->height); ++v, depth_row += row_step) { + for (int u = 0; u < static_cast(cloud_msg->width); ++u, ++iter_x, ++iter_y, ++iter_z) { + T depth = depth_row[u]; + + // Missing points denoted by NaNs + if (!DepthTraits::valid(depth)) { + if (range_max != 0.0) { + depth = DepthTraits::fromMeters(range_max); + } else { + *iter_x = *iter_y = *iter_z = bad_point; + continue; + } + } + + // Fill in XYZ + *iter_x = (u - center_x) * depth * constant_x; + *iter_y = (v - center_y) * depth * constant_y; + *iter_z = DepthTraits::toMeters(depth); + } + } +} + +// Handles float or uint16 depths +template +void convertDepthRadial( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg, + cv::Mat & transform) +{ + // Combine unit conversion (if necessary) with scaling by focal length for computing (X,Y) + float bad_point = std::numeric_limits::quiet_NaN(); + + sensor_msgs::PointCloud2Iterator iter_x(*cloud_msg, "x"); + sensor_msgs::PointCloud2Iterator iter_y(*cloud_msg, "y"); + sensor_msgs::PointCloud2Iterator iter_z(*cloud_msg, "z"); + const T * depth_row = reinterpret_cast(&depth_msg->data[0]); + int row_step = depth_msg->step / sizeof(T); + for (int v = 0; v < static_cast(cloud_msg->height); ++v, depth_row += row_step) { + for (int u = 0; u < static_cast(cloud_msg->width); ++u, ++iter_x, ++iter_y, ++iter_z) { + T depth = depth_row[u]; + + // Missing points denoted by NaNs + if (!DepthTraits::valid(depth)) { + *iter_x = *iter_y = *iter_z = bad_point; + continue; + } + const cv::Vec3f & cvPoint = transform.at(u, v) * DepthTraits::toMeters(depth); + // Fill in XYZ + *iter_x = cvPoint(0); + *iter_y = cvPoint(1); + *iter_z = cvPoint(2); + } + } +} + +// Handles float or uint16 depths +template +void convertIntensity( + const sensor_msgs::msg::Image::ConstSharedPtr & intensity_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg) +{ + sensor_msgs::PointCloud2Iterator iter_i(*cloud_msg, "intensity"); + const T * inten_row = reinterpret_cast(&intensity_msg->data[0]); + + const int i_row_step = intensity_msg->step / sizeof(T); + for (int v = 0; v < static_cast(cloud_msg->height); ++v, inten_row += i_row_step) { + for (int u = 0; u < static_cast(cloud_msg->width); ++u, ++iter_i) { + *iter_i = inten_row[u]; + } + } +} + +// Handles RGB8, BGR8, and MONO8 +void convertRgb( + const sensor_msgs::msg::Image::ConstSharedPtr & rgb_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg, + int red_offset, int green_offset, int blue_offset, int color_step); + +cv::Mat initMatrix(cv::Mat cameraMatrix, cv::Mat distCoeffs, int width, int height, bool radial); + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__CONVERSIONS_HPP_ diff --git a/src/image_pipeline/depth_image_proc/include/depth_image_proc/depth_traits.hpp b/src/image_pipeline/depth_image_proc/include/depth_image_proc/depth_traits.hpp new file mode 100644 index 000000000..7e3c39d3d --- /dev/null +++ b/src/image_pipeline/depth_image_proc/include/depth_image_proc/depth_traits.hpp @@ -0,0 +1,75 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__DEPTH_TRAITS_HPP_ +#define DEPTH_IMAGE_PROC__DEPTH_TRAITS_HPP_ + +#include +#include +#include +#include + +namespace depth_image_proc +{ + +// Encapsulate differences between processing float and uint16_t depths +template +struct DepthTraits {}; + +template<> +struct DepthTraits +{ + static inline bool valid(uint16_t depth) {return depth != 0;} + static inline float toMeters(uint16_t depth) {return depth * 0.001f;} // originally mm + static inline uint16_t fromMeters(float depth) {return (depth * 1000.0f) + 0.5f;} + // Do nothing - already zero-filled + static inline void initializeBuffer(std::vector & buffer) {(void) buffer;} +}; + +template<> +struct DepthTraits +{ + static inline bool valid(float depth) {return std::isfinite(depth);} + static inline float toMeters(float depth) {return depth;} + static inline float fromMeters(float depth) {return depth;} + + static inline void initializeBuffer(std::vector & buffer) + { + float * start = reinterpret_cast(&buffer[0]); + float * end = reinterpret_cast(&buffer[0] + buffer.size()); + std::fill(start, end, std::numeric_limits::quiet_NaN()); + } +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__DEPTH_TRAITS_HPP_ diff --git a/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyz.hpp b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyz.hpp new file mode 100644 index 000000000..397f4ab6d --- /dev/null +++ b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyz.hpp @@ -0,0 +1,85 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_HPP_ + +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace depth_image_proc +{ + +namespace enc = sensor_msgs::image_encodings; + +class PointCloudXyzNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyzNode(const rclcpp::NodeOptions & options); + +private: + using PointCloud2 = sensor_msgs::msg::PointCloud2; + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + + // Subscriptions + image_transport::CameraSubscriber sub_depth_; + int queue_size_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + image_geometry::PinholeCameraModel model_; + + void connectCb(); + + void depthCb( + const Image::ConstSharedPtr & depth_msg, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_HPP_ diff --git a/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyz_radial.hpp b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyz_radial.hpp new file mode 100644 index 000000000..2cfc44e03 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyz_radial.hpp @@ -0,0 +1,85 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_RADIAL_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_RADIAL_HPP_ + +#include +#include +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" + +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +class PointCloudXyzRadialNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyzRadialNode(const rclcpp::NodeOptions & options); + +private: + // Subscriptions + image_transport::CameraSubscriber sub_depth_; + int queue_size_; + + // Publications + std::mutex connect_mutex_; + using PointCloud = sensor_msgs::msg::PointCloud2; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + std::vector D_; + std::array K_; + + uint32_t width_; + uint32_t height_; + + cv::Mat transform_; + + void connectCb(); + + void depthCb( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_RADIAL_HPP_ diff --git a/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzi.hpp b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzi.hpp new file mode 100644 index 000000000..cd2cbd6ea --- /dev/null +++ b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzi.hpp @@ -0,0 +1,90 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_HPP_ + +#include +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" +#include "message_filters/subscriber.h" +#include "message_filters/sync_policies/approximate_time.h" +#include "message_filters/synchronizer.h" + +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +namespace enc = sensor_msgs::image_encodings; + +class PointCloudXyziNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyziNode(const rclcpp::NodeOptions & options); + +private: + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + using PointCloud = sensor_msgs::msg::PointCloud2; + + // Subscriptions + image_transport::SubscriberFilter sub_depth_, sub_intensity_; + message_filters::Subscriber sub_info_; + using SyncPolicy = + message_filters::sync_policies::ApproximateTime; + using Synchronizer = message_filters::Synchronizer; + std::shared_ptr sync_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + image_geometry::PinholeCameraModel model_; + + void connectCb(); + + void imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & intensity_msg, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_HPP_ diff --git a/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzi_radial.hpp b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzi_radial.hpp new file mode 100644 index 000000000..98398f227 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzi_radial.hpp @@ -0,0 +1,103 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_RADIAL_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_RADIAL_HPP_ + +#include +#include +#include +#include + +#include "depth_image_proc/visibility.h" +#include "message_filters/subscriber.h" +#include "message_filters/synchronizer.h" +#include "message_filters/sync_policies/exact_time.h" + +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +using SyncPolicy = + message_filters::sync_policies::ExactTime< + sensor_msgs::msg::Image, + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo>; + +class PointCloudXyziRadialNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyziRadialNode(const rclcpp::NodeOptions & options); + +private: + using PointCloud = sensor_msgs::msg::PointCloud2; + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + + // Subscriptions + image_transport::SubscriberFilter sub_depth_, sub_intensity_; + message_filters::Subscriber sub_info_; + + int queue_size_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + using Synchronizer = message_filters::Synchronizer; + std::shared_ptr sync_; + + std::vector D_; + std::array K_; + + uint32_t width_; + uint32_t height_; + + cv::Mat transform_; + + void connectCb(); + + void imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & intensity_msg_in, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_RADIAL_HPP_ diff --git a/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb.hpp b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb.hpp new file mode 100644 index 000000000..e18db0cfc --- /dev/null +++ b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb.hpp @@ -0,0 +1,94 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_HPP_ + +#include +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" +#include "message_filters/subscriber.h" +#include "message_filters/synchronizer.h" +#include "message_filters/sync_policies/exact_time.h" +#include "message_filters/sync_policies/approximate_time.h" + +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +class PointCloudXyzrgbNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyzrgbNode(const rclcpp::NodeOptions & options); + +private: + using PointCloud2 = sensor_msgs::msg::PointCloud2; + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + + // Subscriptions + image_transport::SubscriberFilter sub_depth_, sub_rgb_; + message_filters::Subscriber sub_info_; + using SyncPolicy = + message_filters::sync_policies::ApproximateTime; + using ExactSyncPolicy = + message_filters::sync_policies::ExactTime; + using Synchronizer = message_filters::Synchronizer; + using ExactSynchronizer = message_filters::Synchronizer; + std::shared_ptr sync_; + std::shared_ptr exact_sync_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + image_geometry::PinholeCameraModel model_; + + void connectCb(); + + void imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & rgb_msg, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_HPP_ diff --git a/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb_radial.hpp b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb_radial.hpp new file mode 100644 index 000000000..ef07c31d5 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb_radial.hpp @@ -0,0 +1,106 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_RADIAL_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_RADIAL_HPP_ + +#include +#include +#include +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" +#include "message_filters/subscriber.h" +#include "message_filters/synchronizer.h" +#include "message_filters/sync_policies/exact_time.h" +#include "message_filters/sync_policies/approximate_time.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +class PointCloudXyzrgbRadialNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyzrgbRadialNode(const rclcpp::NodeOptions & options); + +private: + using PointCloud2 = sensor_msgs::msg::PointCloud2; + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + + // Subscriptions + image_transport::SubscriberFilter sub_depth_, sub_rgb_; + message_filters::Subscriber sub_info_; + using SyncPolicy = + message_filters::sync_policies::ApproximateTime; + using ExactSyncPolicy = + message_filters::sync_policies::ExactTime; + using Synchronizer = message_filters::Synchronizer; + using ExactSynchronizer = message_filters::Synchronizer; + std::unique_ptr sync_; + std::unique_ptr exact_sync_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + std::vector D_; + std::array K_; + + uint32_t width_; + uint32_t height_; + + cv::Mat transform_; + + image_geometry::PinholeCameraModel model_; + + void connectCb(); + + void imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & rgb_msg, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_RADIAL_HPP_ diff --git a/src/image_pipeline/depth_image_proc/include/depth_image_proc/visibility.h b/src/image_pipeline/depth_image_proc/include/depth_image_proc/visibility.h new file mode 100644 index 000000000..43d4d047a --- /dev/null +++ b/src/image_pipeline/depth_image_proc/include/depth_image_proc/visibility.h @@ -0,0 +1,83 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. +#ifndef DEPTH_IMAGE_PROC__VISIBILITY_H_ +#define DEPTH_IMAGE_PROC__VISIBILITY_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + + #ifdef __GNUC__ + #define DEPTH_IMAGE_PROC_EXPORT __attribute__ ((dllexport)) + #define DEPTH_IMAGE_PROC_IMPORT __attribute__ ((dllimport)) + #else + #define DEPTH_IMAGE_PROC_EXPORT __declspec(dllexport) + #define DEPTH_IMAGE_PROC_IMPORT __declspec(dllimport) + #endif + + #ifdef DEPTH_IMAGE_PROC_DLL + #define DEPTH_IMAGE_PROC_PUBLIC DEPTH_IMAGE_PROC_EXPORT + #else + #define DEPTH_IMAGE_PROC_PUBLIC DEPTH_IMAGE_PROC_IMPORT + #endif + + #define DEPTH_IMAGE_PROC_PUBLIC_TYPE DEPTH_IMAGE_PROC_PUBLIC + + #define DEPTH_IMAGE_PROC_LOCAL + +#else + + #define DEPTH_IMAGE_PROC_EXPORT __attribute__ ((visibility("default"))) + #define DEPTH_IMAGE_PROC_IMPORT + + #if __GNUC__ >= 4 + #define DEPTH_IMAGE_PROC_PUBLIC __attribute__ ((visibility("default"))) + #define DEPTH_IMAGE_PROC_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define DEPTH_IMAGE_PROC_PUBLIC + #define DEPTH_IMAGE_PROC_LOCAL + #endif + + #define DEPTH_IMAGE_PROC_PUBLIC_TYPE +#endif + +#ifdef __cplusplus +} +#endif + +#endif // DEPTH_IMAGE_PROC__VISIBILITY_H_ diff --git a/src/image_pipeline/depth_image_proc/launch/convert_metric.launch.py b/src/image_pipeline/depth_image_proc/launch/convert_metric.launch.py new file mode 100644 index 000000000..37e6c09f2 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/convert_metric.launch.py @@ -0,0 +1,75 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/convert_metric.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::ConvertMetricNode', + name='convert_metric_node', + remappings=[('image_raw', '/camera/depth/image_rect_raw'), + ('camera_info', '/camera/depth/camera_info'), + ('image', '/camera/depth/converted_image')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/crop_foremost.launch.py b/src/image_pipeline/depth_image_proc/launch/crop_foremost.launch.py new file mode 100644 index 000000000..17485336f --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/crop_foremost.launch.py @@ -0,0 +1,74 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/crop_formost.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::CropForemostNode', + name='crop_foremost_node', + remappings=[('image_raw', '/camera/depth/image_rect_raw'), + ('camera_info', '/camera/depth/camera_info'), + ('image', '/camera/depth/converted_image')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/disparity.launch.py b/src/image_pipeline/depth_image_proc/launch/disparity.launch.py new file mode 100644 index 000000000..acf2af812 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/disparity.launch.py @@ -0,0 +1,77 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/disparity.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # we use realsense camera for test, realsense not support left and right topic + # so we remap to depth image only for interface test. + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::DisparityNode', + name='disparity_node', + remappings=[('left/image_rect', '/camera/depth/image_rect_raw'), + ('right/camera_info', '/camera/depth/camera_info'), + ('left/disparity', '/camera/left/disparity')] + ), + ], + output='screen', + ), + + # TODO: rviz could not display disparity(stereo_msgs) + # run stereo_view for display after image_view be ported + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/point_cloud_xyz.launch.py b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyz.launch.py new file mode 100644 index 000000000..1428bcec1 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyz.launch.py @@ -0,0 +1,75 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyz.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyzNode', + name='point_cloud_xyz_node', + remappings=[('image_rect', '/camera/depth/image_rect_raw'), + ('camera_info', '/camera/depth/camera_info'), + ('image', '/camera/depth/converted_image')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/point_cloud_xyz_radial.launch.py b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyz_radial.launch.py new file mode 100644 index 000000000..d578a05ce --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyz_radial.launch.py @@ -0,0 +1,75 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyz_radial.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyzRadialNode', + name='point_cloud_xyz_radial_node', + remappings=[('image_raw', '/camera/depth/image_rect_raw'), + ('camera_info', '/camera/depth/camera_info'), + ('image', '/camera/depth/converted_image')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzi.launch.py b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzi.launch.py new file mode 100644 index 000000000..191b3f089 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzi.launch.py @@ -0,0 +1,77 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyzi.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # TODO: Realsense camera do not support intensity message + # use color image instead of intensity only for interface test + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyziNode', + name='point_cloud_xyzi', + remappings=[('depth/image_rect', '/camera/aligned_depth_to_color/image_raw'), + ('intensity/image_rect', '/camera/color/image_raw'), + ('intensity/camera_info', '/camera/color/camera_info'), + ('points', '/camera/depth/points')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzi_radial.launch.py b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzi_radial.launch.py new file mode 100644 index 000000000..721387748 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzi_radial.launch.py @@ -0,0 +1,77 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyzi.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # TODO: Realsense camera do not support intensity message, + # use depth instead of intensity only for interface test + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyziRadialNode', + name='point_cloud_xyzi_radial_node', + remappings=[('depth/image_raw', '/camera/depth/image_rect_raw'), + ('intensity/image_raw', '/camera/depth/image_rect_raw'), + ('intensity/camera_info', '/camera/depth/camera_info'), + ('points', '/camera/depth/points')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzrgb.launch.py b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzrgb.launch.py new file mode 100644 index 000000000..10b333ac2 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzrgb.launch.py @@ -0,0 +1,77 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyzrgb.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyzrgbNode', + name='point_cloud_xyzrgb_node', + remappings=[('rgb/camera_info', '/camera/color/camera_info'), + ('rgb/image_rect_color', '/camera/color/image_raw'), + ('depth_registered/image_rect', + '/camera/aligned_depth_to_color/image_raw'), + ('points', '/camera/depth_registered/points')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzrgb_radial.launch.py b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzrgb_radial.launch.py new file mode 100644 index 000000000..263e2397c --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/point_cloud_xyzrgb_radial.launch.py @@ -0,0 +1,90 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +# uncomment if you want to launch rviz too in this launch +# import os +# from ament_index_python.packages import get_package_share_directory +import os + +from ament_index_python.packages import get_package_share_directory +import launch +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyzrgb.rviz') + return LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='rviz', default_value='False', + description='Launch RViz for viewing point cloud xyzrgb radial data' + ), + launch_ros.actions.Node( + condition=launch.conditions.IfCondition( + launch.substitutions.LaunchConfiguration('rviz') + ), + package='rviz2', + executable='rviz2', + output='screen', + arguments=['--display-config', default_rviz] + ), + # launch plugin through rclcpp_components container + # make sure remapped topics are available + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyzrgbRadialNode', + name='point_cloud_xyzrgb_node', + remappings=[('rgb/camera_info', '/camera/color/camera_info'), + ('rgb/image_rect_color', '/camera/color/image_raw'), + ('depth_registered/image_rect', + '/camera/aligned_depth_to_color/image_raw'), + ('points', '/camera/depth_registered/points')] + ), + ], + output='screen', + ), + + # rviz + # launch_ros.actions.Node( + # package='rviz2', executable='rviz2', output='screen', + # arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/register.launch.py b/src/image_pipeline/depth_image_proc/launch/register.launch.py new file mode 100644 index 000000000..0eab8878e --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/register.launch.py @@ -0,0 +1,79 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/register.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::RegisterNode', + name='register_node', + remappings=[('depth/image_rect', '/camera/depth/image_rect_raw'), + ('depth/camera_info', '/camera/depth/camera_info'), + ('rgb/camera_info', '/camera/color/camera_info'), + ('depth_registered/image_rect', + '/camera/depth_registered/image_rect'), + ('depth_registered/camera_info', + '/camera/depth_registered/camera_info')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/depth_image_proc/launch/rviz/convert_metric.rviz b/src/image_pipeline/depth_image_proc/launch/rviz/convert_metric.rviz new file mode 100644 index 000000000..d76e1637f --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/rviz/convert_metric.rviz @@ -0,0 +1,136 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_raw1 + - /image_raw1/Status1 + - /converted_image1 + - /converted_image1/Status1 + Splitter Ratio: 0.5 + Tree Height: 581 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_raw + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: converted_image + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/converted_image + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_color_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/XYOrbit + 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.7853981852531433 + Target Frame: + Value: XYOrbit (rviz_default_plugins) + Yaw: 0.7853981852531433 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d00000282000000c900fffffffb000000120069006d006100670065005f00720061007701000002c5000000c50000002800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000001e0063006f006e007600650072007400650064005f0069006d0061006700650100000390000000cc0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d00610067006500000003ad000000af0000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000003ae0000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1853 + X: 67 + Y: 27 + converted_image: + collapsed: false + image_raw: + collapsed: false diff --git a/src/image_pipeline/depth_image_proc/launch/rviz/crop_formost.rviz b/src/image_pipeline/depth_image_proc/launch/rviz/crop_formost.rviz new file mode 100644 index 000000000..f1cf786e6 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/rviz/crop_formost.rviz @@ -0,0 +1,134 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_raw1 + - /crop_name1 + Splitter Ratio: 0.5 + Tree Height: 581 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_raw + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: crop_name + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/crop_image + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_color_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/XYOrbit + 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.7853981852531433 + Target Frame: + Value: XYOrbit (rviz_default_plugins) + Yaw: 0.7853981852531433 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d00000282000000c900fffffffb000000120069006d006100670065005f00720061007701000002c5000000c50000002800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000001200630072006f0070005f006e0061006d00650100000390000000cc0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d00610067006500000003ad000000af0000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000003ae0000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1853 + X: 67 + Y: 27 + crop_name: + collapsed: false + image_raw: + collapsed: false diff --git a/src/image_pipeline/depth_image_proc/launch/rviz/disparity.rviz b/src/image_pipeline/depth_image_proc/launch/rviz/disparity.rviz new file mode 100644 index 000000000..6ffe90c5c --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/rviz/disparity.rviz @@ -0,0 +1,134 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /fake left/image_rect1 + - /fake left/disparity1 + Splitter Ratio: 0.5 + Tree Height: 581 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: fake left/image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: fake left/disparity + Normalize Range: true + Queue Size: 10 + Topic: /camera/left/disparity + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_color_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/XYOrbit + 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.7853981852531433 + Target Frame: + Value: XYOrbit (rviz_default_plugins) + Yaw: 0.7853981852531433 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d00000282000000c900fffffffb0000002800660061006b00650020006c006500660074002f0069006d006100670065005f007200650063007401000002c5000000c50000002800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000002600660061006b00650020006c006500660074002f0064006900730070006100720069007400790100000390000000cc0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d00610067006500000003ad000000af0000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000003ae0000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1853 + X: 67 + Y: 27 + fake left/disparity: + collapsed: false + fake left/image_rect: + collapsed: false diff --git a/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyz.rviz b/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyz.rviz new file mode 100644 index 000000000..ae68f102a --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyz.rviz @@ -0,0 +1,151 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_rect1 + - /PointCloud21 + Splitter Ratio: 0.5 + Tree Height: 608 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: /camera/depth/points + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_depth_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 2.125654935836792 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.718582630157471 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d0000029d000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650000000256000000a20000000000000000fb000000140069006d006100670065005f007200650063007401000002e00000017c0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d006100670065000000038b000000d10000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + image_rect: + collapsed: false diff --git a/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyz_radial.rviz b/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyz_radial.rviz new file mode 100644 index 000000000..ae68f102a --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyz_radial.rviz @@ -0,0 +1,151 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_rect1 + - /PointCloud21 + Splitter Ratio: 0.5 + Tree Height: 608 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: /camera/depth/points + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_depth_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 2.125654935836792 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.718582630157471 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d0000029d000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650000000256000000a20000000000000000fb000000140069006d006100670065005f007200650063007401000002e00000017c0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d006100670065000000038b000000d10000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + image_rect: + collapsed: false diff --git a/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyzi.rviz b/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyzi.rviz new file mode 100644 index 000000000..ee87ced30 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyzi.rviz @@ -0,0 +1,151 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_rect1 + - /PointCloud21 + Splitter Ratio: 0.5 + Tree Height: 608 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 255 + Min Color: 0; 0; 0 + Min Intensity: 2 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: /camera/depth/points + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_depth_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 2.125654935836792 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.718582630157471 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d0000029d000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650000000256000000a20000000000000000fb000000140069006d006100670065005f007200650063007401000002e00000017c0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d006100670065000000038b000000d10000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + image_rect: + collapsed: false diff --git a/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyzrgb.rviz b/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyzrgb.rviz new file mode 100644 index 000000000..daba8ef8e --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/rviz/point_cloud_xyzrgb.rviz @@ -0,0 +1,179 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /color_image1 + - /depth_image1 + - /registered_depth_image1 + - /PointCloud21 + Splitter Ratio: 0.5 + Tree Height: 392 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: color_image + Normalize Range: true + Queue Size: 10 + Topic: /camera/color/image_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: depth_image + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: registered_depth_image + Normalize Range: true + Queue Size: 10 + Topic: /camera/aligned_depth_to_color/image_raw + Unreliable: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: RGB8 + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: /camera/depth_registered/points + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_color_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 1.8979061841964722 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.718582630157471 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d00000213000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650100000256000000a20000002800fffffffb0000001600640065007000740068005f0069006d00610067006501000002fe000000a90000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d00610067006501000003ad000000af0000002800ffffff000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + color_image: + collapsed: false + depth_image: + collapsed: false + registered_depth_image: + collapsed: false diff --git a/src/image_pipeline/depth_image_proc/launch/rviz/register.rviz b/src/image_pipeline/depth_image_proc/launch/rviz/register.rviz new file mode 100644 index 000000000..6191de026 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/launch/rviz/register.rviz @@ -0,0 +1,148 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /depth/image_rect1 + - /depth_registered1 + - /color image1 + Splitter Ratio: 0.5 + Tree Height: 366 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: depth/image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: depth_registered + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth_registered/image_rect + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: color image + Normalize Range: true + Queue Size: 10 + Topic: /camera/color/image_raw + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_depth_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 2.125654935836792 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.723582744598389 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000dfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d000001ab000000c900fffffffb0000002000640065007000740068002f0069006d006100670065005f007200650063007401000001ee000000c70000002800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650000000256000000a20000000000000000fb000000160063006f006c006f007200200069006d00610067006501000002bb000000cb0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d006100670065000000038b000000d10000000000000000fb0000002000640065007000740068005f0072006500670069007300740065007200650064010000038c000000d00000002800ffffff000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + color image: + collapsed: false + depth/image_rect: + collapsed: false + depth_registered: + collapsed: false diff --git a/src/image_pipeline/depth_image_proc/mainpage.dox b/src/image_pipeline/depth_image_proc/mainpage.dox new file mode 100644 index 000000000..6b0414bd0 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/mainpage.dox @@ -0,0 +1,26 @@ +/** +\mainpage +\htmlinclude manifest.html + +\b depth_image_proc is ... + + + + +\section codeapi Code API + + + + +*/ diff --git a/src/image_pipeline/depth_image_proc/package.xml b/src/image_pipeline/depth_image_proc/package.xml new file mode 100644 index 000000000..1ee00d2a9 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/package.xml @@ -0,0 +1,50 @@ + + + + depth_image_proc + 4.0.0 + + + Contains components for processing depth images such as those + produced by OpenNI camera. Functions include creating disparity + images and point clouds, as well as registering (reprojecting) + a depth image into another camera frame. + + + + Vincent Rabaud + Joshua Whitley + Chris Ye + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/depth_image_proc/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Patrick Mihelich + + ament_cmake_auto + + class_loader + + cv_bridge + image_geometry + image_transport + libopencv-dev + message_filters + rclcpp + rclcpp_components + sensor_msgs + stereo_msgs + tf2 + tf2_eigen + tf2_ros + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/image_pipeline/depth_image_proc/src/conversions.cpp b/src/image_pipeline/depth_image_proc/src/conversions.cpp new file mode 100644 index 000000000..ba462323f --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/conversions.cpp @@ -0,0 +1,108 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 + +namespace depth_image_proc +{ + +cv::Mat initMatrix( + cv::Mat cameraMatrix, cv::Mat distCoeffs, + int width, int height, bool radial) +{ + int i, j; + int totalsize = width * height; + cv::Mat pixelVectors(1, totalsize, CV_32FC3); + cv::Mat dst(1, totalsize, CV_32FC3); + + cv::Mat sensorPoints(cv::Size(height, width), CV_32FC2); + cv::Mat undistortedSensorPoints(1, totalsize, CV_32FC2); + + std::vector ch; + for (j = 0; j < height; j++) { + for (i = 0; i < width; i++) { + cv::Vec2f & p = sensorPoints.at(i, j); + p[0] = i; + p[1] = j; + } + } + + sensorPoints = sensorPoints.reshape(2, 1); + + cv::undistortPoints(sensorPoints, undistortedSensorPoints, cameraMatrix, distCoeffs); + + ch.push_back(undistortedSensorPoints); + ch.push_back(cv::Mat::ones(1, totalsize, CV_32FC1)); + cv::merge(ch, pixelVectors); + + if (radial) { + for (i = 0; i < totalsize; i++) { + normalize( + pixelVectors.at(i), + dst.at(i)); + } + pixelVectors = dst; + } + return pixelVectors.reshape(3, width); +} + +void convertRgb( + const sensor_msgs::msg::Image::ConstSharedPtr & rgb_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg, + int red_offset, int green_offset, int blue_offset, int color_step) +{ + sensor_msgs::PointCloud2Iterator iter_r(*cloud_msg, "r"); + sensor_msgs::PointCloud2Iterator iter_g(*cloud_msg, "g"); + sensor_msgs::PointCloud2Iterator iter_b(*cloud_msg, "b"); + const uint8_t * rgb = &rgb_msg->data[0]; + int rgb_skip = rgb_msg->step - rgb_msg->width * color_step; + for (int v = 0; v < static_cast(cloud_msg->height); ++v, rgb += rgb_skip) { + for (int u = 0; u < static_cast(cloud_msg->width); ++u, + rgb += color_step, ++iter_r, ++iter_g, ++iter_b) + { + *iter_r = rgb[red_offset]; + *iter_g = rgb[green_offset]; + *iter_b = rgb[blue_offset]; + } + } +} + +// force template instantiation +template void convertDepth( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg, + const image_geometry::PinholeCameraModel & model, + double range_max); + +} // namespace depth_image_proc diff --git a/src/image_pipeline/depth_image_proc/src/convert_metric.cpp b/src/image_pipeline/depth_image_proc/src/convert_metric.cpp new file mode 100644 index 000000000..f52f3e560 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/convert_metric.cpp @@ -0,0 +1,146 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 +#include + +#include "depth_image_proc/visibility.h" + +#include +#include +#include + +namespace depth_image_proc +{ + +class ConvertMetricNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC ConvertMetricNode(const rclcpp::NodeOptions & options); + +private: + // Subscriptions + image_transport::Subscriber sub_raw_; + + // Publications + std::mutex connect_mutex_; + image_transport::Publisher pub_depth_; + + void connectCb(); + + void depthCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg); +}; + +ConvertMetricNode::ConvertMetricNode(const rclcpp::NodeOptions & options) +: Node("ConvertMetricNode", options) +{ + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // image_transport::SubscriberStatusCallback connect_cb = + // std::bind(&ConvertMetricNode::connectCb, this); + connectCb(); + + // Make sure we don't enter connectCb() between advertising and assigning to pub_depth_ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement when SubscriberStatusCallback is available + // pub_depth_ = it_->advertise("image", 1, connect_cb, connect_cb); + pub_depth_ = image_transport::create_publisher(this, "image"); +} + +// Handles (un)subscribing when clients (un)subscribe +void ConvertMetricNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_depth_.getNumSubscribers() == 0) + if (0) { + sub_raw_.shutdown(); + } else if (!sub_raw_) { + image_transport::TransportHints hints(this, "raw"); + sub_raw_ = image_transport::create_subscription( + this, "image_raw", + std::bind(&ConvertMetricNode::depthCb, this, std::placeholders::_1), + hints.getTransport()); + } +} + +void ConvertMetricNode::depthCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg) +{ + auto depth_msg = std::make_shared(); + depth_msg->header = raw_msg->header; + depth_msg->height = raw_msg->height; + depth_msg->width = raw_msg->width; + + // Set data, encoding and step after converting the metric. + if (raw_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) { + depth_msg->encoding = sensor_msgs::image_encodings::TYPE_32FC1; + depth_msg->step = + raw_msg->width * (sensor_msgs::image_encodings::bitDepth(depth_msg->encoding) / 8); + depth_msg->data.resize(depth_msg->height * depth_msg->step); + // Fill in the depth image data, converting mm to m + float bad_point = std::numeric_limits::quiet_NaN(); + const uint16_t * raw_data = reinterpret_cast(&raw_msg->data[0]); + float * depth_data = reinterpret_cast(&depth_msg->data[0]); + for (unsigned index = 0; index < depth_msg->height * depth_msg->width; ++index) { + uint16_t raw = raw_data[index]; + depth_data[index] = (raw == 0) ? bad_point : static_cast(raw * 0.001f); + } + } else if (raw_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) { + depth_msg->encoding = sensor_msgs::image_encodings::TYPE_16UC1; + depth_msg->step = + raw_msg->width * (sensor_msgs::image_encodings::bitDepth(depth_msg->encoding) / 8); + depth_msg->data.resize(depth_msg->height * depth_msg->step); + // Fill in the depth image data, converting m to mm + uint16_t bad_point = 0; + const float * raw_data = reinterpret_cast(&raw_msg->data[0]); + uint16_t * depth_data = reinterpret_cast(&depth_msg->data[0]); + for (unsigned index = 0; index < depth_msg->height * depth_msg->width; ++index) { + float raw = raw_data[index]; + depth_data[index] = std::isnan(raw) ? bad_point : static_cast(raw * 1000); + } + } else { + RCLCPP_ERROR(get_logger(), "Unsupported image conversion from %s.", raw_msg->encoding.c_str()); + return; + } + + pub_depth_.publish(depth_msg); +} + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::ConvertMetricNode) diff --git a/src/image_pipeline/depth_image_proc/src/crop_foremost.cpp b/src/image_pipeline/depth_image_proc/src/crop_foremost.cpp new file mode 100644 index 000000000..e44d4ca34 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/crop_foremost.cpp @@ -0,0 +1,153 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 "cv_bridge/cv_bridge.hpp" +#include "depth_image_proc/visibility.h" + +#include +#include +#include + +namespace depth_image_proc +{ + +namespace enc = sensor_msgs::image_encodings; + +class CropForemostNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC CropForemostNode(const rclcpp::NodeOptions & options); + +private: + // Subscriptions + image_transport::Subscriber sub_raw_; + + // Publications + std::mutex connect_mutex_; + image_transport::Publisher pub_depth_; + + void connectCb(); + + void depthCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg); + + double distance_; + + rclcpp::Logger logger_ = rclcpp::get_logger("CropForemostNode"); +}; + +CropForemostNode::CropForemostNode(const rclcpp::NodeOptions & options) +: Node("CropForemostNode", options) +{ + distance_ = this->declare_parameter("distance", 0.0); + + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // image_transport::SubscriberStatusCallback connect_cb = + // std::bind(&CropForemostNode::connectCb, this); + connectCb(); + // Make sure we don't enter connectCb() between advertising and assigning to pub_depth_ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement when SubscriberStatusCallback is available + // pub_depth_ = it_->advertise("image", 1, connect_cb, connect_cb); + pub_depth_ = image_transport::create_publisher(this, "image"); +} + +// Handles (un)subscribing when clients (un)subscribe +void CropForemostNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_depth_.getNumSubscribers() == 0) + if (0) { + sub_raw_.shutdown(); + } else if (!sub_raw_) { + image_transport::TransportHints hints(this, "raw"); + sub_raw_ = image_transport::create_subscription( + this, "image_raw", + std::bind(&CropForemostNode::depthCb, this, std::placeholders::_1), + hints.getTransport()); + } +} + +void CropForemostNode::depthCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg) +{ + cv_bridge::CvImagePtr cv_ptr; + try { + cv_ptr = cv_bridge::toCvCopy(raw_msg); + } catch (cv_bridge::Exception & e) { + RCLCPP_ERROR(logger_, "cv_bridge exception: %s", e.what()); + return; + } + + // Check the number of channels + if (sensor_msgs::image_encodings::numChannels(raw_msg->encoding) != 1) { + RCLCPP_ERROR( + logger_, "Only grayscale image is acceptable, got [%s]", + raw_msg->encoding.c_str()); + return; + } + + // search the min value without invalid value "0" + double minVal; + cv::minMaxIdx(cv_ptr->image, &minVal, 0, 0, 0, cv_ptr->image != 0); + + int imtype = cv_bridge::getCvType(raw_msg->encoding); + switch (imtype) { + case CV_8UC1: + case CV_8SC1: + case CV_32F: + cv::threshold(cv_ptr->image, cv_ptr->image, minVal + distance_, 0, CV_THRESH_TOZERO_INV); + break; + case CV_16UC1: + case CV_16SC1: + case CV_32SC1: + case CV_64F: + // 8 bit or 32 bit floating array is required to use cv::threshold + cv_ptr->image.convertTo(cv_ptr->image, CV_32F); + cv::threshold(cv_ptr->image, cv_ptr->image, minVal + distance_, 1, CV_THRESH_TOZERO_INV); + + cv_ptr->image.convertTo(cv_ptr->image, imtype); + break; + } + + pub_depth_.publish(cv_ptr->toImageMsg()); +} + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::CropForemostNode) diff --git a/src/image_pipeline/depth_image_proc/src/disparity.cpp b/src/image_pipeline/depth_image_proc/src/disparity.cpp new file mode 100644 index 000000000..51a97c4da --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/disparity.cpp @@ -0,0 +1,194 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 + +#include "depth_image_proc/visibility.h" +#include "message_filters/subscriber.h" +#include "message_filters/time_synchronizer.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +class DisparityNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC DisparityNode(const rclcpp::NodeOptions & options); + +private: + image_transport::SubscriberFilter sub_depth_image_; + message_filters::Subscriber sub_info_; + using Sync = message_filters::TimeSynchronizer; + std::shared_ptr sync_; + + std::mutex connect_mutex_; + using DisparityImage = stereo_msgs::msg::DisparityImage; + rclcpp::Publisher::SharedPtr pub_disparity_; + double min_range_; + double max_range_; + double delta_d_; + + void connectCb(); + + void depthCb( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info_msg); + + template + void convert( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + stereo_msgs::msg::DisparityImage::SharedPtr & disp_msg); +}; + +DisparityNode::DisparityNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("DisparityNode", options) +{ + // Read parameters + int queue_size = this->declare_parameter("queue_size", 5); + min_range_ = this->declare_parameter("min_range", 0.0); + max_range_ = this->declare_parameter( + "max_range", + std::numeric_limits::infinity()); + delta_d_ = this->declare_parameter("delta_d", 0.125); + + // Synchronize inputs. Topic subscriptions happen on demand in the connection callback. + sync_ = std::make_shared(sub_depth_image_, sub_info_, queue_size); + sync_->registerCallback( + std::bind( + &DisparityNode::depthCb, this, std::placeholders::_1, std::placeholders::_2)); + + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // ros::SubscriberStatusCallback connect_cb = std::bind(&DisparityNode::connectCb, this); + connectCb(); + + // Make sure we don't enter connectCb() between advertising and assigning to pub_disparity_ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement when SubscriberStatusCallback is available + // pub_disparity_ = + // left_nh.advertise("disparity", 1, connect_cb, connect_cb); + pub_disparity_ = create_publisher( + "left/disparity", rclcpp::SensorDataQoS()); +} + +// Handles (un)subscribing when clients (un)subscribe +void DisparityNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_disparity_.getNumSubscribers() == 0) + if (0) { + sub_depth_image_.unsubscribe(); + sub_info_.unsubscribe(); + } else if (!sub_depth_image_.getSubscriber()) { + image_transport::TransportHints hints(this, "raw"); + sub_depth_image_.subscribe(this, "left/image_rect", hints.getTransport()); + sub_info_.subscribe(this, "right/camera_info"); + } +} + +void DisparityNode::depthCb( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info_msg) +{ + auto disp_msg = std::make_shared(); + disp_msg->header = depth_msg->header; + disp_msg->image.header = disp_msg->header; + disp_msg->image.encoding = sensor_msgs::image_encodings::TYPE_32FC1; + disp_msg->image.height = depth_msg->height; + disp_msg->image.width = depth_msg->width; + disp_msg->image.step = disp_msg->image.width * sizeof(float); + disp_msg->image.data.resize(disp_msg->image.height * disp_msg->image.step, 0.0f); + double fx = info_msg->p[0]; + disp_msg->t = -info_msg->p[3] / fx; + disp_msg->f = fx; + // Remaining fields depend on device characteristics, so rely on user input + disp_msg->min_disparity = disp_msg->f * disp_msg->t / max_range_; + disp_msg->max_disparity = disp_msg->f * disp_msg->t / min_range_; + disp_msg->delta_d = delta_d_; + + if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) { + convert(depth_msg, disp_msg); + } else if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) { + convert(depth_msg, disp_msg); + } else { + RCLCPP_ERROR( + get_logger(), "Depth image has unsupported encoding [%s]", depth_msg->encoding.c_str()); + return; + } + + pub_disparity_->publish(*disp_msg); +} + +template +void DisparityNode::convert( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + stereo_msgs::msg::DisparityImage::SharedPtr & disp_msg) +{ + // For each depth Z, disparity d = fT / Z + float unit_scaling = DepthTraits::toMeters(T(1) ); + float constant = disp_msg->f * disp_msg->t / unit_scaling; + + const T * depth_row = reinterpret_cast(&depth_msg->data[0]); + int row_step = depth_msg->step / sizeof(T); + float * disp_data = reinterpret_cast(&disp_msg->image.data[0]); + for (int v = 0; v < static_cast(depth_msg->height); ++v) { + for (int u = 0; u < static_cast(depth_msg->width); ++u) { + T depth = depth_row[u]; + if (DepthTraits::valid(depth)) { + *disp_data = constant / depth; + } + ++disp_data; + } + + depth_row += row_step; + } +} + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::DisparityNode) diff --git a/src/image_pipeline/depth_image_proc/src/point_cloud_xyz.cpp b/src/image_pipeline/depth_image_proc/src/point_cloud_xyz.cpp new file mode 100644 index 000000000..0703032c8 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/point_cloud_xyz.cpp @@ -0,0 +1,126 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace depth_image_proc +{ + +PointCloudXyzNode::PointCloudXyzNode(const rclcpp::NodeOptions & options) +: Node("PointCloudXyzNode", options) +{ + // Read parameters + queue_size_ = this->declare_parameter("queue_size", 5); + + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // ros::SubscriberStatusCallback connect_cb = boost::bind(&PointCloudXyzNode::connectCb, this); + connectCb(); + + // Make sure we don't enter connectCb() between advertising and assigning to pub_point_cloud_ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement when SubscriberStatusCallback is available + // pub_point_cloud_ = nh.advertise("points", 1, connect_cb, connect_cb); + pub_point_cloud_ = create_publisher("points", rclcpp::SensorDataQoS()); +} + +// Handles (un)subscribing when clients (un)subscribe +void PointCloudXyzNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_point_cloud_->getNumSubscribers() == 0) + if (0) { + sub_depth_.shutdown(); + } else if (!sub_depth_) { + auto custom_qos = rmw_qos_profile_system_default; + custom_qos.depth = queue_size_; + + sub_depth_ = image_transport::create_camera_subscription( + this, + "image_rect", + std::bind(&PointCloudXyzNode::depthCb, this, std::placeholders::_1, std::placeholders::_2), + "raw", + custom_qos); + } +} + +void PointCloudXyzNode::depthCb( + const Image::ConstSharedPtr & depth_msg, + const CameraInfo::ConstSharedPtr & info_msg) +{ + auto cloud_msg = std::make_shared(); + cloud_msg->header = depth_msg->header; + cloud_msg->height = depth_msg->height; + cloud_msg->width = depth_msg->width; + cloud_msg->is_dense = false; + cloud_msg->is_bigendian = false; + + sensor_msgs::PointCloud2Modifier pcd_modifier(*cloud_msg); + pcd_modifier.setPointCloud2FieldsByString(1, "xyz"); + + // Update camera model + model_.fromCameraInfo(info_msg); + + // Convert Depth Image to Pointcloud + if (depth_msg->encoding == enc::TYPE_16UC1 || depth_msg->encoding == enc::MONO16) { + convertDepth(depth_msg, cloud_msg, model_); + } else if (depth_msg->encoding == enc::TYPE_32FC1) { + convertDepth(depth_msg, cloud_msg, model_); + } else { + RCLCPP_ERROR( + get_logger(), "Depth image has unsupported encoding [%s]", depth_msg->encoding.c_str()); + return; + } + + pub_point_cloud_->publish(*cloud_msg); +} + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::PointCloudXyzNode) diff --git a/src/image_pipeline/depth_image_proc/src/point_cloud_xyz_radial.cpp b/src/image_pipeline/depth_image_proc/src/point_cloud_xyz_radial.cpp new file mode 100644 index 000000000..db5692709 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/point_cloud_xyz_radial.cpp @@ -0,0 +1,135 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 "image_geometry/pinhole_camera_model.hpp" + +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + + +PointCloudXyzRadialNode::PointCloudXyzRadialNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("PointCloudXyzRadialNode", options) +{ + // Read parameters + queue_size_ = this->declare_parameter("queue_size", 5); + + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // ros::SubscriberStatusCallback connect_cb = + // boost::bind(&PointCloudXyzRadialNode::connectCb, this); + connectCb(); + // Make sure we don't enter connectCb() between advertising and assigning to pub_point_cloud_ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement when SubscriberStatusCallback is available + // pub_point_cloud_ = nh.advertise("points", 1, connect_cb, connect_cb); + pub_point_cloud_ = create_publisher( + "points", rclcpp::SensorDataQoS()); +} + +// Handles (un)subscribing when clients (un)subscribe +void PointCloudXyzRadialNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_point_cloud_.getNumSubscribers() == 0) + if (0) { + sub_depth_.shutdown(); + } else if (!sub_depth_) { + auto custom_qos = rmw_qos_profile_system_default; + custom_qos.depth = queue_size_; + + sub_depth_ = image_transport::create_camera_subscription( + this, + "image_raw", + std::bind( + &PointCloudXyzRadialNode::depthCb, this, std::placeholders::_1, + std::placeholders::_2), + "raw", + custom_qos); + } +} + +void PointCloudXyzRadialNode::depthCb( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info_msg) +{ + auto cloud_msg = std::make_shared(); + cloud_msg->header = depth_msg->header; + cloud_msg->height = depth_msg->height; + cloud_msg->width = depth_msg->width; + cloud_msg->is_dense = false; + cloud_msg->is_bigendian = false; + + sensor_msgs::PointCloud2Modifier pcd_modifier(*cloud_msg); + pcd_modifier.setPointCloud2FieldsByString(1, "xyz"); + + if (info_msg->d != D_ || info_msg->k != K_ || width_ != info_msg->width || + height_ != info_msg->height) + { + D_ = info_msg->d; + K_ = info_msg->k; + width_ = info_msg->width; + height_ = info_msg->height; + transform_ = initMatrix(cv::Mat_(3, 3, &K_[0]), cv::Mat(D_), width_, height_, true); + } + + // Convert Depth Image to Pointcloud + if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) { + convertDepthRadial(depth_msg, cloud_msg, transform_); + } else if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) { + convertDepthRadial(depth_msg, cloud_msg, transform_); + } else { + RCLCPP_ERROR( + get_logger(), "Depth image has unsupported encoding [%s]", depth_msg->encoding.c_str()); + return; + } + + pub_point_cloud_->publish(*cloud_msg); +} + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::PointCloudXyzRadialNode) diff --git a/src/image_pipeline/depth_image_proc/src/point_cloud_xyzi.cpp b/src/image_pipeline/depth_image_proc/src/point_cloud_xyzi.cpp new file mode 100644 index 000000000..7eb986b88 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/point_cloud_xyzi.cpp @@ -0,0 +1,235 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 + +#include "cv_bridge/cv_bridge.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + + +PointCloudXyziNode::PointCloudXyziNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("PointCloudXyziNode", options) +{ + // Read parameters + int queue_size = this->declare_parameter("queue_size", 5); + + // Synchronize inputs. Topic subscriptions happen on demand in the connection callback. + sync_ = std::make_shared( + SyncPolicy(queue_size), + sub_depth_, + sub_intensity_, + sub_info_); + sync_->registerCallback( + std::bind( + &PointCloudXyziNode::imageCb, + this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // ros::SubscriberStatusCallback connect_cb = boost::bind(&PointCloudXyziNode::connectCb, this); + connectCb(); + // Make sure we don't enter connectCb() between advertising and assigning to pub_point_cloud_ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement when SubscriberStatusCallback is available + // pub_point_cloud_ = depth_nh.advertise("points", 1, connect_cb, connect_cb); + pub_point_cloud_ = create_publisher("points", rclcpp::SensorDataQoS()); +} + +// Handles (un)subscribing when clients (un)subscribe +void PointCloudXyziNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_point_cloud_->getNumSubscribers() == 0) + if (0) { + sub_depth_.unsubscribe(); + sub_intensity_.unsubscribe(); + sub_info_.unsubscribe(); + } else if (!sub_depth_.getSubscriber()) { + // parameter for depth_image_transport hint + std::string depth_image_transport_param = "depth_image_transport"; + + // depth image can use different transport.(e.g. compressedDepth) + image_transport::TransportHints depth_hints(this, "raw", depth_image_transport_param); + sub_depth_.subscribe(this, "depth/image_rect", depth_hints.getTransport()); + + // intensity uses normal ros transport hints. + image_transport::TransportHints hints(this, "raw"); + sub_intensity_.subscribe(this, "intensity/image_rect", hints.getTransport()); + sub_info_.subscribe(this, "intensity/camera_info"); + } +} + +void PointCloudXyziNode::imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + const sensor_msgs::msg::Image::ConstSharedPtr & intensity_msg_in, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info_msg) +{ + // Check for bad inputs + if (depth_msg->header.frame_id != intensity_msg_in->header.frame_id) { + RCLCPP_WARN_THROTTLE( + get_logger(), + *get_clock(), + 10000, // 10 seconds + "Depth image frame id [%s] doesn't match image frame id [%s]", + depth_msg->header.frame_id.c_str(), intensity_msg_in->header.frame_id.c_str()); + } + + // Update camera model + model_.fromCameraInfo(info_msg); + + // Check if the input image has to be resized + sensor_msgs::msg::Image::ConstSharedPtr intensity_msg = intensity_msg_in; + if (depth_msg->width != intensity_msg->width || depth_msg->height != intensity_msg->height) { + sensor_msgs::msg::CameraInfo info_msg_tmp = *info_msg; + info_msg_tmp.width = depth_msg->width; + info_msg_tmp.height = depth_msg->height; + float ratio = static_cast(depth_msg->width) / static_cast(intensity_msg->width); + info_msg_tmp.k[0] *= ratio; + info_msg_tmp.k[2] *= ratio; + info_msg_tmp.k[4] *= ratio; + info_msg_tmp.k[5] *= ratio; + info_msg_tmp.p[0] *= ratio; + info_msg_tmp.p[2] *= ratio; + info_msg_tmp.p[5] *= ratio; + info_msg_tmp.p[6] *= ratio; + model_.fromCameraInfo(info_msg_tmp); + + cv_bridge::CvImageConstPtr cv_ptr; + try { + cv_ptr = cv_bridge::toCvShare(intensity_msg, intensity_msg->encoding); + } catch (const cv_bridge::Exception & e) { + RCLCPP_ERROR(get_logger(), "cv_bridge exception: %s", e.what()); + return; + } + cv_bridge::CvImage cv_rsz; + cv_rsz.header = cv_ptr->header; + cv_rsz.encoding = cv_ptr->encoding; + cv::resize( + cv_ptr->image.rowRange(0, depth_msg->height / ratio), cv_rsz.image, + cv::Size(depth_msg->width, depth_msg->height)); + if ((intensity_msg->encoding == enc::MONO8) || (intensity_msg->encoding == enc::MONO16)) { + intensity_msg = cv_rsz.toImageMsg(); + } else { + intensity_msg = cv_bridge::toCvCopy(cv_rsz.toImageMsg(), enc::MONO8)->toImageMsg(); + } + + // RCLCPP_ERROR(get_logger(), "Depth resolution (%ux%u) does not match resolution (%ux%u)", + // depth_msg->width, depth_msg->height, rgb_msg->width, rgb_msg->height); + // return; + } else { + intensity_msg = intensity_msg_in; + } + + // Supported color encodings: MONO8, MONO16 + if (intensity_msg->encoding != enc::MONO8 || intensity_msg->encoding != enc::MONO16) { + try { + intensity_msg = cv_bridge::toCvCopy(intensity_msg, enc::MONO8)->toImageMsg(); + } catch (const cv_bridge::Exception & e) { + RCLCPP_ERROR( + get_logger(), "Unsupported encoding [%s]: %s", + intensity_msg->encoding.c_str(), e.what()); + return; + } + } + + auto cloud_msg = std::make_shared(); + cloud_msg->header = depth_msg->header; // Use depth image time stamp + cloud_msg->height = depth_msg->height; + cloud_msg->width = depth_msg->width; + cloud_msg->is_dense = false; + cloud_msg->is_bigendian = false; + + sensor_msgs::PointCloud2Modifier pcd_modifier(*cloud_msg); + // pcd_modifier.setPointCloud2FieldsByString(2, "xyz", "i"); + pcd_modifier.setPointCloud2Fields( + 4, + "x", 1, sensor_msgs::msg::PointField::FLOAT32, + "y", 1, sensor_msgs::msg::PointField::FLOAT32, + "z", 1, sensor_msgs::msg::PointField::FLOAT32, + "intensity", 1, sensor_msgs::msg::PointField::FLOAT32); + + // Convert Depth Image to Pointcloud + if (depth_msg->encoding == enc::TYPE_16UC1) { + convertDepth(depth_msg, cloud_msg, model_); + } else if (depth_msg->encoding == enc::TYPE_32FC1) { + convertDepth(depth_msg, cloud_msg, model_); + } else { + RCLCPP_ERROR( + get_logger(), "Depth image has unsupported encoding [%s]", depth_msg->encoding.c_str()); + return; + } + + // Convert Intensity Image to Pointcloud + if (intensity_msg->encoding == enc::MONO8) { + convertIntensity(intensity_msg, cloud_msg); + } else if (intensity_msg->encoding == enc::MONO16) { + convertIntensity(intensity_msg, cloud_msg); + } else if (intensity_msg->encoding == enc::TYPE_16UC1) { + convertIntensity(intensity_msg, cloud_msg); + } else if (intensity_msg->encoding == enc::TYPE_32FC1) { + convertIntensity(intensity_msg, cloud_msg); + } else { + RCLCPP_ERROR( + get_logger(), "Intensity image has unsupported encoding [%s]", + intensity_msg->encoding.c_str()); + return; + } + + pub_point_cloud_->publish(*cloud_msg); +} + + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::PointCloudXyziNode) diff --git a/src/image_pipeline/depth_image_proc/src/point_cloud_xyzi_radial.cpp b/src/image_pipeline/depth_image_proc/src/point_cloud_xyzi_radial.cpp new file mode 100644 index 000000000..c873993c2 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/point_cloud_xyzi_radial.cpp @@ -0,0 +1,177 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 + +#include "depth_image_proc/visibility.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + + +PointCloudXyziRadialNode::PointCloudXyziRadialNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("PointCloudXyziRadialNode", options) +{ + // Read parameters + queue_size_ = this->declare_parameter("queue_size", 5); + + // Synchronize inputs. Topic subscriptions happen on demand in the connection callback. + sync_ = std::make_shared( + SyncPolicy(queue_size_), + sub_depth_, + sub_intensity_, + sub_info_); + sync_->registerCallback( + std::bind( + &PointCloudXyziRadialNode::imageCb, + this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // ros::SubscriberStatusCallback connect_cb = + // boost::bind(&PointCloudXyziRadialNode::connectCb, this); + connectCb(); + + // Make sure we don't enter connectCb() between advertising and assigning to pub_point_cloud_ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement when SubscriberStatusCallback is available + // pub_point_cloud_ = nh.advertise("points", 20, connect_cb, connect_cb); + pub_point_cloud_ = create_publisher( + "points", rclcpp::SensorDataQoS()); +} + +// Handles (un)subscribing when clients (un)subscribe +void PointCloudXyziRadialNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_point_cloud_.getNumSubscribers() == 0) + if (0) { + sub_depth_.unsubscribe(); + sub_intensity_.unsubscribe(); + sub_info_.unsubscribe(); + } else if (!sub_depth_.getSubscriber()) { + // parameter for depth_image_transport hint + std::string depth_image_transport_param = "depth_image_transport"; + + // depth image can use different transport.(e.g. compressedDepth) + image_transport::TransportHints depth_hints(this, "raw", depth_image_transport_param); + sub_depth_.subscribe(this, "depth/image_raw", depth_hints.getTransport()); + + // intensity uses normal ros transport hints. + image_transport::TransportHints hints(this, "raw"); + sub_intensity_.subscribe(this, "intensity/image_raw", hints.getTransport()); + sub_info_.subscribe(this, "intensity/camera_info"); + } +} + +void PointCloudXyziRadialNode::imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & intensity_msg, + const CameraInfo::ConstSharedPtr & info_msg) +{ + auto cloud_msg = std::make_shared(); + cloud_msg->header = depth_msg->header; + cloud_msg->height = depth_msg->height; + cloud_msg->width = depth_msg->width; + cloud_msg->is_dense = false; + cloud_msg->is_bigendian = false; + + sensor_msgs::PointCloud2Modifier pcd_modifier(*cloud_msg); + pcd_modifier.setPointCloud2Fields( + 4, + "x", 1, sensor_msgs::msg::PointField::FLOAT32, + "y", 1, sensor_msgs::msg::PointField::FLOAT32, + "z", 1, sensor_msgs::msg::PointField::FLOAT32, + "intensity", 1, sensor_msgs::msg::PointField::FLOAT32); + + + if (info_msg->d != D_ || info_msg->k != K_ || width_ != info_msg->width || + height_ != info_msg->height) + { + D_ = info_msg->d; + K_ = info_msg->k; + width_ = info_msg->width; + height_ = info_msg->height; + transform_ = initMatrix(cv::Mat_(3, 3, &K_[0]), cv::Mat(D_), width_, height_, true); + } + + // Convert Depth Image to Pointcloud + if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) { + convertDepthRadial(depth_msg, cloud_msg, transform_); + } else if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) { + convertDepthRadial(depth_msg, cloud_msg, transform_); + } else { + RCLCPP_ERROR( + get_logger(), "Depth image has unsupported encoding [%s]", depth_msg->encoding.c_str()); + return; + } + + if (intensity_msg->encoding == sensor_msgs::image_encodings::MONO8) { + convertIntensity(intensity_msg, cloud_msg); + } else if (intensity_msg->encoding == sensor_msgs::image_encodings::MONO16) { + convertIntensity(intensity_msg, cloud_msg); + } else if (intensity_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) { + convertIntensity(intensity_msg, cloud_msg); + } else if (intensity_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) { + convertIntensity(intensity_msg, cloud_msg); + } else { + RCLCPP_ERROR( + get_logger(), "Intensity image has unsupported encoding [%s]", + intensity_msg->encoding.c_str()); + return; + } + + pub_point_cloud_->publish(*cloud_msg); +} + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::PointCloudXyziRadialNode) diff --git a/src/image_pipeline/depth_image_proc/src/point_cloud_xyzrgb.cpp b/src/image_pipeline/depth_image_proc/src/point_cloud_xyzrgb.cpp new file mode 100644 index 000000000..013c3ac49 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/point_cloud_xyzrgb.cpp @@ -0,0 +1,290 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 + +#include "cv_bridge/cv_bridge.hpp" + +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + + +PointCloudXyzrgbNode::PointCloudXyzrgbNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("PointCloudXyzrgbNode", options) +{ + // Read parameters + int queue_size = this->declare_parameter("queue_size", 5); + bool use_exact_sync = this->declare_parameter("exact_sync", false); + + // Synchronize inputs. Topic subscriptions happen on demand in the connection callback. + if (use_exact_sync) { + exact_sync_ = std::make_shared( + ExactSyncPolicy(queue_size), + sub_depth_, + sub_rgb_, + sub_info_); + exact_sync_->registerCallback( + std::bind( + &PointCloudXyzrgbNode::imageCb, + this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + } else { + sync_ = std::make_shared(SyncPolicy(queue_size), sub_depth_, sub_rgb_, sub_info_); + sync_->registerCallback( + std::bind( + &PointCloudXyzrgbNode::imageCb, + this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + } + + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // ros::SubscriberStatusCallback connect_cb = boost::bind(&PointCloudXyzrgbNode::connectCb, this); + connectCb(); + // TODO(ros2) Implement when SubscriberStatusCallback is available + // Make sure we don't enter connectCb() between advertising and assigning to pub_point_cloud_ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement connect_cb when SubscriberStatusCallback is available + // pub_point_cloud_ = depth_nh.advertise("points", 1, connect_cb, connect_cb); + pub_point_cloud_ = create_publisher("points", rclcpp::SensorDataQoS()); + // TODO(ros2) Implement connect_cb when SubscriberStatusCallback is available +} + +// Handles (un)subscribing when clients (un)subscribe +void PointCloudXyzrgbNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_point_cloud_->getNumSubscribers() == 0) + if (0) { + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + sub_depth_.unsubscribe(); + sub_rgb_.unsubscribe(); + sub_info_.unsubscribe(); + } else if (!sub_depth_.getSubscriber()) { + // parameter for depth_image_transport hint + std::string depth_image_transport_param = "depth_image_transport"; + image_transport::TransportHints depth_hints(this, "raw", depth_image_transport_param); + + rclcpp::SubscriptionOptions sub_opts; + // Update the subscription options to allow reconfigurable qos settings. + sub_opts.qos_overriding_options = rclcpp::QosOverridingOptions { + { + // Here all policies that are desired to be reconfigurable are listed. + rclcpp::QosPolicyKind::Depth, + rclcpp::QosPolicyKind::Durability, + rclcpp::QosPolicyKind::History, + rclcpp::QosPolicyKind::Reliability, + }}; + + // depth image can use different transport.(e.g. compressedDepth) + sub_depth_.subscribe( + this, "depth_registered/image_rect", + depth_hints.getTransport(), rmw_qos_profile_default, sub_opts); + + // rgb uses normal ros transport hints. + image_transport::TransportHints hints(this, "raw"); + sub_rgb_.subscribe( + this, "rgb/image_rect_color", + hints.getTransport(), rmw_qos_profile_default, sub_opts); + sub_info_.subscribe(this, "rgb/camera_info"); + } +} + +void PointCloudXyzrgbNode::imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & rgb_msg_in, + const CameraInfo::ConstSharedPtr & info_msg) +{ + // Check for bad inputs + if (depth_msg->header.frame_id != rgb_msg_in->header.frame_id) { + RCLCPP_WARN_THROTTLE( + get_logger(), + *get_clock(), + 10000, // 10 seconds + "Depth image frame id [%s] doesn't match RGB image frame id [%s]", + depth_msg->header.frame_id.c_str(), rgb_msg_in->header.frame_id.c_str()); + } + + // Update camera model + model_.fromCameraInfo(info_msg); + + // Check if the input image has to be resized + Image::ConstSharedPtr rgb_msg = rgb_msg_in; + if (depth_msg->width != rgb_msg->width || depth_msg->height != rgb_msg->height) { + CameraInfo info_msg_tmp = *info_msg; + info_msg_tmp.width = depth_msg->width; + info_msg_tmp.height = depth_msg->height; + float ratio = static_cast(depth_msg->width) / static_cast(rgb_msg->width); + info_msg_tmp.k[0] *= ratio; + info_msg_tmp.k[2] *= ratio; + info_msg_tmp.k[4] *= ratio; + info_msg_tmp.k[5] *= ratio; + info_msg_tmp.p[0] *= ratio; + info_msg_tmp.p[2] *= ratio; + info_msg_tmp.p[5] *= ratio; + info_msg_tmp.p[6] *= ratio; + model_.fromCameraInfo(info_msg_tmp); + + cv_bridge::CvImageConstPtr cv_ptr; + try { + cv_ptr = cv_bridge::toCvShare(rgb_msg, rgb_msg->encoding); + } catch (cv_bridge::Exception & e) { + RCLCPP_ERROR(get_logger(), "cv_bridge exception: %s", e.what()); + return; + } + cv_bridge::CvImage cv_rsz; + cv_rsz.header = cv_ptr->header; + cv_rsz.encoding = cv_ptr->encoding; + cv::resize( + cv_ptr->image.rowRange(0, depth_msg->height / ratio), cv_rsz.image, + cv::Size(depth_msg->width, depth_msg->height)); + if ((rgb_msg->encoding == sensor_msgs::image_encodings::RGB8) || + (rgb_msg->encoding == sensor_msgs::image_encodings::BGR8) || + (rgb_msg->encoding == sensor_msgs::image_encodings::MONO8)) + { + rgb_msg = cv_rsz.toImageMsg(); + } else { + rgb_msg = + cv_bridge::toCvCopy(cv_rsz.toImageMsg(), sensor_msgs::image_encodings::RGB8)->toImageMsg(); + } + + RCLCPP_ERROR( + get_logger(), "Depth resolution (%ux%u) does not match RGB resolution (%ux%u)", + depth_msg->width, depth_msg->height, rgb_msg->width, rgb_msg->height); + return; + } else { + rgb_msg = rgb_msg_in; + } + + // Supported color encodings: RGB8, BGR8, MONO8 + int red_offset, green_offset, blue_offset, color_step; + if (rgb_msg->encoding == sensor_msgs::image_encodings::RGB8) { + red_offset = 0; + green_offset = 1; + blue_offset = 2; + color_step = 3; + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::RGBA8) { + red_offset = 0; + green_offset = 1; + blue_offset = 2; + color_step = 4; + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::BGR8) { + red_offset = 2; + green_offset = 1; + blue_offset = 0; + color_step = 3; + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::BGRA8) { + red_offset = 2; + green_offset = 1; + blue_offset = 0; + color_step = 4; + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::MONO8) { + red_offset = 0; + green_offset = 0; + blue_offset = 0; + color_step = 1; + } else { + try { + rgb_msg = cv_bridge::toCvCopy(rgb_msg, sensor_msgs::image_encodings::RGB8)->toImageMsg(); + } catch (cv_bridge::Exception & e) { + RCLCPP_ERROR( + get_logger(), "Unsupported encoding [%s]: %s", rgb_msg->encoding.c_str(), e.what()); + return; + } + red_offset = 0; + green_offset = 1; + blue_offset = 2; + color_step = 3; + } + + auto cloud_msg = std::make_shared(); + cloud_msg->header = depth_msg->header; // Use depth image time stamp + cloud_msg->height = depth_msg->height; + cloud_msg->width = depth_msg->width; + cloud_msg->is_dense = false; + cloud_msg->is_bigendian = false; + + sensor_msgs::PointCloud2Modifier pcd_modifier(*cloud_msg); + pcd_modifier.setPointCloud2FieldsByString(2, "xyz", "rgb"); + + // Convert Depth Image to Pointcloud + if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) { + convertDepth(depth_msg, cloud_msg, model_); + } else if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) { + convertDepth(depth_msg, cloud_msg, model_); + } else { + RCLCPP_ERROR( + get_logger(), "Depth image has unsupported encoding [%s]", depth_msg->encoding.c_str()); + return; + } + + // Convert RGB + if (rgb_msg->encoding == sensor_msgs::image_encodings::RGB8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::BGR8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::BGRA8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::RGBA8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::MONO8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else { + RCLCPP_ERROR( + get_logger(), "RGB image has unsupported encoding [%s]", rgb_msg->encoding.c_str()); + return; + } + + pub_point_cloud_->publish(*cloud_msg); +} + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::PointCloudXyzrgbNode) diff --git a/src/image_pipeline/depth_image_proc/src/point_cloud_xyzrgb_radial.cpp b/src/image_pipeline/depth_image_proc/src/point_cloud_xyzrgb_radial.cpp new file mode 100644 index 000000000..04d780dd5 --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/point_cloud_xyzrgb_radial.cpp @@ -0,0 +1,288 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 + +#include "cv_bridge/cv_bridge.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + + +PointCloudXyzrgbRadialNode::PointCloudXyzrgbRadialNode(const rclcpp::NodeOptions & options) +: Node("PointCloudXyzrgbRadialNode", options) +{ + // Read parameters + int queue_size = this->declare_parameter("queue_size", 5); + bool use_exact_sync = this->declare_parameter("exact_sync", false); + + // Synchronize inputs. Topic subscriptions happen on demand in the connection callback. + if (use_exact_sync) { + exact_sync_ = std::make_unique( + ExactSyncPolicy(queue_size), + sub_depth_, + sub_rgb_, + sub_info_); + exact_sync_->registerCallback( + std::bind( + &PointCloudXyzrgbRadialNode::imageCb, + this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + } else { + sync_ = + std::make_unique(SyncPolicy(queue_size), sub_depth_, sub_rgb_, sub_info_); + sync_->registerCallback( + std::bind( + &PointCloudXyzrgbRadialNode::imageCb, + this, + std::placeholders::_1, + std::placeholders::_2, + std::placeholders::_3)); + } + + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // ros::SubscriberStatusCallback connect_cb = + // boost::bind(&PointCloudXyzrgbRadialNode::connectCb, this); + connectCb(); + // TODO(ros2) Implement when SubscriberStatusCallback is available + // Make sure we don't enter connectCb() between advertising and assigning to pub_point_cloud_ + // std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement connect_cb when SubscriberStatusCallback is available + // pub_point_cloud_ = depth_nh.advertise("points", 1, connect_cb, connect_cb); + pub_point_cloud_ = create_publisher("points", rclcpp::SensorDataQoS()); + // TODO(ros2) Implement connect_cb when SubscriberStatusCallback is available +} + +// Handles (un)subscribing when clients (un)subscribe +void PointCloudXyzrgbRadialNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_point_cloud_->getNumSubscribers() == 0) + if (0) { + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + sub_depth_.unsubscribe(); + sub_rgb_.unsubscribe(); + sub_info_.unsubscribe(); + } else if (!sub_depth_.getSubscriber()) { + // parameter for depth_image_transport hint + std::string depth_image_transport_param = "depth_image_transport"; + image_transport::TransportHints depth_hints(this, "raw", depth_image_transport_param); + + // depth image can use different transport.(e.g. compressedDepth) + sub_depth_.subscribe(this, "depth_registered/image_rect", depth_hints.getTransport()); + + // rgb uses normal ros transport hints. + image_transport::TransportHints hints(this, "raw"); + sub_rgb_.subscribe(this, "rgb/image_rect_color", hints.getTransport()); + sub_info_.subscribe(this, "rgb/camera_info"); + } +} + +void PointCloudXyzrgbRadialNode::imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & rgb_msg_in, + const CameraInfo::ConstSharedPtr & info_msg) +{ + // Check for bad inputs + if (depth_msg->header.frame_id != rgb_msg_in->header.frame_id) { + RCLCPP_WARN( + get_logger(), "Depth image frame id [%s] doesn't match RGB image frame id [%s]", + depth_msg->header.frame_id.c_str(), rgb_msg_in->header.frame_id.c_str()); + return; + } + + // Update camera model + model_.fromCameraInfo(info_msg); + + // Check if the input image has to be resized + Image::ConstSharedPtr rgb_msg = rgb_msg_in; + if (depth_msg->width != rgb_msg->width || depth_msg->height != rgb_msg->height) { + CameraInfo info_msg_tmp = *info_msg; + info_msg_tmp.width = depth_msg->width; + info_msg_tmp.height = depth_msg->height; + float ratio = static_cast(depth_msg->width) / static_cast(rgb_msg->width); + info_msg_tmp.k[0] *= ratio; + info_msg_tmp.k[2] *= ratio; + info_msg_tmp.k[4] *= ratio; + info_msg_tmp.k[5] *= ratio; + info_msg_tmp.p[0] *= ratio; + info_msg_tmp.p[2] *= ratio; + info_msg_tmp.p[5] *= ratio; + info_msg_tmp.p[6] *= ratio; + model_.fromCameraInfo(info_msg_tmp); + + cv_bridge::CvImageConstPtr cv_ptr; + try { + cv_ptr = cv_bridge::toCvShare(rgb_msg, rgb_msg->encoding); + } catch (cv_bridge::Exception & e) { + RCLCPP_ERROR(get_logger(), "cv_bridge exception: %s", e.what()); + return; + } + cv_bridge::CvImage cv_rsz; + cv_rsz.header = cv_ptr->header; + cv_rsz.encoding = cv_ptr->encoding; + cv::resize( + cv_ptr->image.rowRange(0, depth_msg->height / ratio), cv_rsz.image, + cv::Size(depth_msg->width, depth_msg->height)); + if ((rgb_msg->encoding == sensor_msgs::image_encodings::RGB8) || + (rgb_msg->encoding == sensor_msgs::image_encodings::BGR8) || + (rgb_msg->encoding == sensor_msgs::image_encodings::MONO8)) + { + rgb_msg = cv_rsz.toImageMsg(); + } else { + rgb_msg = + cv_bridge::toCvCopy(cv_rsz.toImageMsg(), sensor_msgs::image_encodings::RGB8)->toImageMsg(); + } + + RCLCPP_ERROR( + get_logger(), "Depth resolution (%ux%u) does not match RGB resolution (%ux%u)", + depth_msg->width, depth_msg->height, rgb_msg->width, rgb_msg->height); + return; + } else { + rgb_msg = rgb_msg_in; + } + + if (info_msg->d != D_ || info_msg->k != K_ || width_ != info_msg->width || + height_ != info_msg->height) + { + D_ = info_msg->d; + K_ = info_msg->k; + width_ = info_msg->width; + height_ = info_msg->height; + transform_ = initMatrix(cv::Mat_(3, 3, &K_[0]), cv::Mat(D_), width_, height_, true); + } + + // Supported color encodings: RGB8, BGR8, MONO8 + int red_offset, green_offset, blue_offset, color_step; + if (rgb_msg->encoding == sensor_msgs::image_encodings::RGB8) { + red_offset = 0; + green_offset = 1; + blue_offset = 2; + color_step = 3; + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::RGBA8) { + red_offset = 0; + green_offset = 1; + blue_offset = 2; + color_step = 4; + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::BGR8) { + red_offset = 2; + green_offset = 1; + blue_offset = 0; + color_step = 3; + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::BGRA8) { + red_offset = 2; + green_offset = 1; + blue_offset = 0; + color_step = 4; + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::MONO8) { + red_offset = 0; + green_offset = 0; + blue_offset = 0; + color_step = 1; + } else { + try { + rgb_msg = cv_bridge::toCvCopy(rgb_msg, sensor_msgs::image_encodings::RGB8)->toImageMsg(); + } catch (cv_bridge::Exception & e) { + RCLCPP_ERROR( + get_logger(), "Unsupported encoding [%s]: %s", rgb_msg->encoding.c_str(), e.what()); + return; + } + red_offset = 0; + green_offset = 1; + blue_offset = 2; + color_step = 3; + } + + auto cloud_msg = std::make_shared(); + cloud_msg->header = depth_msg->header; // Use depth image time stamp + cloud_msg->height = depth_msg->height; + cloud_msg->width = depth_msg->width; + cloud_msg->is_dense = false; + cloud_msg->is_bigendian = false; + + sensor_msgs::PointCloud2Modifier pcd_modifier(*cloud_msg); + pcd_modifier.setPointCloud2FieldsByString(2, "xyz", "rgb"); + + // Convert Depth Image to Pointcloud + if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) { + convertDepthRadial(depth_msg, cloud_msg, transform_); + } else if (depth_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) { + convertDepthRadial(depth_msg, cloud_msg, transform_); + } else { + RCLCPP_ERROR( + get_logger(), "Depth image has unsupported encoding [%s]", depth_msg->encoding.c_str()); + return; + } + + // Convert RGB + if (rgb_msg->encoding == sensor_msgs::image_encodings::RGB8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::BGR8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::BGRA8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::RGBA8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else if (rgb_msg->encoding == sensor_msgs::image_encodings::MONO8) { + convertRgb(rgb_msg, cloud_msg, red_offset, green_offset, blue_offset, color_step); + } else { + RCLCPP_ERROR( + get_logger(), "RGB image has unsupported encoding [%s]", rgb_msg->encoding.c_str()); + return; + } + + pub_point_cloud_->publish(*cloud_msg); +} + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::PointCloudXyzrgbRadialNode) diff --git a/src/image_pipeline/depth_image_proc/src/register.cpp b/src/image_pipeline/depth_image_proc/src/register.cpp new file mode 100644 index 000000000..92f59bb4c --- /dev/null +++ b/src/image_pipeline/depth_image_proc/src/register.cpp @@ -0,0 +1,330 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 + +#include "Eigen/Geometry" +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" +#include "message_filters/subscriber.h" +#include "message_filters/synchronizer.h" +#include "message_filters/sync_policies/approximate_time.h" +#include "tf2_ros/buffer.h" +#include "tf2_ros/transform_listener.h" + +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +class RegisterNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC RegisterNode(const rclcpp::NodeOptions & options); + +private: + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + + // Subscriptions + image_transport::SubscriberFilter sub_depth_image_; + message_filters::Subscriber sub_depth_info_, sub_rgb_info_; + std::shared_ptr tf_buffer_; + std::shared_ptr tf_; + using SyncPolicy = message_filters::sync_policies::ApproximateTime< + Image, CameraInfo, + CameraInfo>; + using Synchronizer = message_filters::Synchronizer; + std::shared_ptr sync_; + + // Publications + std::mutex connect_mutex_; + image_transport::CameraPublisher pub_registered_; + + image_geometry::PinholeCameraModel depth_model_, rgb_model_; + + // Parameters + bool fill_upsampling_holes_; + bool use_rgb_timestamp_; // use source time stamp from RGB camera + + void connectCb(); + + void imageCb( + const Image::ConstSharedPtr & depth_image_msg, + const CameraInfo::ConstSharedPtr & depth_info_msg, + const CameraInfo::ConstSharedPtr & rgb_info_msg); + + template + void convert( + const Image::ConstSharedPtr & depth_msg, + const Image::SharedPtr & registered_msg, + const Eigen::Affine3d & depth_to_rgb); +}; + +RegisterNode::RegisterNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("RegisterNode", options) +{ + rclcpp::Clock::SharedPtr clock = this->get_clock(); + tf_buffer_ = std::make_shared(clock); + tf_ = std::make_shared(*tf_buffer_); + + // Read parameters + int queue_size = this->declare_parameter("queue_size", 5); + fill_upsampling_holes_ = this->declare_parameter("fill_upsampling_holes", false); + use_rgb_timestamp_ = this->declare_parameter("use_rgb_timestamp", false); + + // Synchronize inputs. Topic subscriptions happen on demand in the connection callback. + sync_ = std::make_shared( + SyncPolicy(queue_size), + sub_depth_image_, + sub_depth_info_, + sub_rgb_info_); + sync_->registerCallback( + std::bind( + &RegisterNode::imageCb, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); + + // Monitor whether anyone is subscribed to the output + // TODO(ros2) Implement when SubscriberStatusCallback is available + // image_transport::SubscriberStatusCallback image_connect_cb + // boost::bind(&RegisterNode::connectCb, this); + // ros::SubscriberStatusCallback info_connect_cb = boost::bind(&RegisterNode::connectCb, this); + connectCb(); + // Make sure we don't enter connectCb() between advertising and assigning to pub_registered_ + std::lock_guard lock(connect_mutex_); + // pub_registered_ = it_depth_reg.advertiseCamera("image_rect", 1, + // image_connect_cb, image_connect_cb, + // info_connect_cb, info_connect_cb); + pub_registered_ = image_transport::create_camera_publisher(this, "depth_registered/image_rect"); +} + +// Handles (un)subscribing when clients (un)subscribe +void RegisterNode::connectCb() +{ + std::lock_guard lock(connect_mutex_); + // TODO(ros2) Implement getNumSubscribers when rcl/rmw support it + // if (pub_point_cloud_->getNumSubscribers() == 0) + if (0) { + sub_depth_image_.unsubscribe(); + sub_depth_info_.unsubscribe(); + sub_rgb_info_.unsubscribe(); + } else if (!sub_depth_image_.getSubscriber()) { + image_transport::TransportHints hints(this, "raw"); + sub_depth_image_.subscribe(this, "depth/image_rect", hints.getTransport()); + sub_depth_info_.subscribe(this, "depth/camera_info"); + sub_rgb_info_.subscribe(this, "rgb/camera_info"); + } +} + +void RegisterNode::imageCb( + const Image::ConstSharedPtr & depth_image_msg, + const CameraInfo::ConstSharedPtr & depth_info_msg, + const CameraInfo::ConstSharedPtr & rgb_info_msg) +{ + // Update camera models - these take binning & ROI into account + depth_model_.fromCameraInfo(depth_info_msg); + rgb_model_.fromCameraInfo(rgb_info_msg); + + // Query tf2 for transform from (X,Y,Z) in depth camera frame to RGB camera frame + Eigen::Affine3d depth_to_rgb; + try { + tf2::TimePoint tf2_time(std::chrono::nanoseconds(depth_info_msg->header.stamp.nanosec) + + std::chrono::duration_cast( + std::chrono::seconds( + depth_info_msg->header.stamp.sec))); + geometry_msgs::msg::TransformStamped transform = tf_buffer_->lookupTransform( + rgb_info_msg->header.frame_id, depth_info_msg->header.frame_id, + tf2_time); + + depth_to_rgb = tf2::transformToEigen(transform); + } catch (tf2::TransformException & ex) { + RCLCPP_ERROR(get_logger(), "TF2 exception:\n%s", ex.what()); + return; + /// @todo Can take on order of a minute to register a disconnect callback when we + /// don't call publish() in this cb. What's going on roscpp? + } + + auto registered_msg = std::make_shared(); + registered_msg->header.stamp = + use_rgb_timestamp_ ? rgb_info_msg->header.stamp : depth_image_msg->header.stamp; + registered_msg->header.frame_id = rgb_info_msg->header.frame_id; + registered_msg->encoding = depth_image_msg->encoding; + + cv::Size resolution = rgb_model_.reducedResolution(); + registered_msg->height = resolution.height; + registered_msg->width = resolution.width; + // step and data set in convert(), depend on depth data type + + if (depth_image_msg->encoding == sensor_msgs::image_encodings::TYPE_16UC1) { + convert(depth_image_msg, registered_msg, depth_to_rgb); + } else if (depth_image_msg->encoding == sensor_msgs::image_encodings::TYPE_32FC1) { + convert(depth_image_msg, registered_msg, depth_to_rgb); + } else { + RCLCPP_ERROR( + get_logger(), "Depth image has unsupported encoding [%s]", + depth_image_msg->encoding.c_str()); + return; + } + + // Registered camera info is the same as the RGB info, but uses the depth timestamp + auto registered_info_msg = std::make_shared(*rgb_info_msg); + registered_info_msg->header.stamp = registered_msg->header.stamp; + + pub_registered_.publish(registered_msg, registered_info_msg); +} + +template +void RegisterNode::convert( + const Image::ConstSharedPtr & depth_msg, + const Image::SharedPtr & registered_msg, + const Eigen::Affine3d & depth_to_rgb) +{ + // Allocate memory for registered depth image + registered_msg->step = registered_msg->width * sizeof(T); + registered_msg->data.resize(registered_msg->height * registered_msg->step); + // data is already zero-filled in the uint16 case, + // but for floats we want to initialize everything to NaN. + DepthTraits::initializeBuffer(registered_msg->data); + + // Extract all the parameters we need + double inv_depth_fx = 1.0 / depth_model_.fx(); + double inv_depth_fy = 1.0 / depth_model_.fy(); + double depth_cx = depth_model_.cx(), depth_cy = depth_model_.cy(); + double depth_Tx = depth_model_.Tx(), depth_Ty = depth_model_.Ty(); + double rgb_fx = rgb_model_.fx(), rgb_fy = rgb_model_.fy(); + double rgb_cx = rgb_model_.cx(), rgb_cy = rgb_model_.cy(); + double rgb_Tx = rgb_model_.Tx(), rgb_Ty = rgb_model_.Ty(); + + // Transform the depth values into the RGB frame + /// @todo When RGB is higher res, interpolate by rasterizing depth triangles onto the + // registered image + const T * depth_row = reinterpret_cast(&depth_msg->data[0]); + int row_step = depth_msg->step / sizeof(T); + T * registered_data = reinterpret_cast(®istered_msg->data[0]); + int raw_index = 0; + for (unsigned v = 0; v < depth_msg->height; ++v, depth_row += row_step) { + for (unsigned u = 0; u < depth_msg->width; ++u, ++raw_index) { + T raw_depth = depth_row[u]; + if (!DepthTraits::valid(raw_depth)) { + continue; + } + + double depth = DepthTraits::toMeters(raw_depth); + + if (fill_upsampling_holes_ == false) { + /// @todo Combine all operations into one matrix multiply on (u,v,d) + // Reproject (u,v,Z) to (X,Y,Z,1) in depth camera frame + Eigen::Vector4d xyz_depth; + xyz_depth << ((u - depth_cx) * depth - depth_Tx) * inv_depth_fx, + ((v - depth_cy) * depth - depth_Ty) * inv_depth_fy, + depth, + 1; + + // Transform to RGB camera frame + Eigen::Vector4d xyz_rgb = depth_to_rgb * xyz_depth; + + // Project to (u,v) in RGB image + double inv_Z = 1.0 / xyz_rgb.z(); + int u_rgb = (rgb_fx * xyz_rgb.x() + rgb_Tx) * inv_Z + rgb_cx + 0.5; + int v_rgb = (rgb_fy * xyz_rgb.y() + rgb_Ty) * inv_Z + rgb_cy + 0.5; + + if (u_rgb < 0 || u_rgb >= static_cast(registered_msg->width) || + v_rgb < 0 || v_rgb >= static_cast(registered_msg->height)) + { + continue; + } + + T & reg_depth = registered_data[v_rgb * registered_msg->width + u_rgb]; + T new_depth = DepthTraits::fromMeters(xyz_rgb.z()); + // Validity and Z-buffer checks + if (!DepthTraits::valid(reg_depth) || reg_depth > new_depth) { + reg_depth = new_depth; + } + } else { + // Reproject (u,v,Z) to (X,Y,Z,1) in depth camera frame + Eigen::Vector4d xyz_depth_1, xyz_depth_2; + xyz_depth_1 << ((u - 0.5f - depth_cx) * depth - depth_Tx) * inv_depth_fx, + ((v - 0.5f - depth_cy) * depth - depth_Ty) * inv_depth_fy, + depth, + 1; + xyz_depth_2 << ((u + 0.5f - depth_cx) * depth - depth_Tx) * inv_depth_fx, + ((v + 0.5f - depth_cy) * depth - depth_Ty) * inv_depth_fy, + depth, + 1; + + // Transform to RGB camera frame + Eigen::Vector4d xyz_rgb_1 = depth_to_rgb * xyz_depth_1; + Eigen::Vector4d xyz_rgb_2 = depth_to_rgb * xyz_depth_2; + + // Project to (u,v) in RGB image + double inv_Z = 1.0 / xyz_rgb_1.z(); + int u_rgb_1 = (rgb_fx * xyz_rgb_1.x() + rgb_Tx) * inv_Z + rgb_cx + 0.5; + int v_rgb_1 = (rgb_fy * xyz_rgb_1.y() + rgb_Ty) * inv_Z + rgb_cy + 0.5; + inv_Z = 1.0 / xyz_rgb_2.z(); + int u_rgb_2 = (rgb_fx * xyz_rgb_2.x() + rgb_Tx) * inv_Z + rgb_cx + 0.5; + int v_rgb_2 = (rgb_fy * xyz_rgb_2.y() + rgb_Ty) * inv_Z + rgb_cy + 0.5; + + if (u_rgb_1 < 0 || u_rgb_2 >= static_cast(registered_msg->width) || + v_rgb_1 < 0 || v_rgb_2 >= static_cast(registered_msg->height)) + { + continue; + } + + for (int nv = v_rgb_1; nv <= v_rgb_2; ++nv) { + for (int nu = u_rgb_1; nu <= u_rgb_2; ++nu) { + T & reg_depth = registered_data[nv * registered_msg->width + nu]; + T new_depth = DepthTraits::fromMeters(0.5 * (xyz_rgb_1.z() + xyz_rgb_2.z())); + // Validity and Z-buffer checks + if (!DepthTraits::valid(reg_depth) || reg_depth > new_depth) { + reg_depth = new_depth; + } + } + } + } + } + } +} + +} // namespace depth_image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(depth_image_proc::RegisterNode) diff --git a/src/image_pipeline/image_pipeline/CHANGELOG.rst b/src/image_pipeline/image_pipeline/CHANGELOG.rst new file mode 100644 index 000000000..fabf65d41 --- /dev/null +++ b/src/image_pipeline/image_pipeline/CHANGELOG.rst @@ -0,0 +1,93 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package image_pipeline +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +4.0.0 (2022-12-24) +------------------ +* add myself as a maintainer (`#846 `_) +* Contributors: Michael Ferguson + +3.0.1 (2022-12-04) +------------------ + +3.0.0 (2022-04-29) +------------------ +* Add maintainer (`#667 `_) +* Contributors: Jacob Perron + +2.2.1 (2020-08-27) +------------------ +* remove email blasts from steve macenski (`#596 `_) +* [Foxy] Use ament_auto Macros (`#573 `_) +* Contributors: Joshua Whitley, Steve Macenski + +2.2.0 (2020-07-27) +------------------ + +* Initial ROS2 commit. +* Contributors: Michael Carroll + +1.12.23 (2018-05-10) +-------------------- + +1.12.22 (2017-12-08) +-------------------- + +1.12.21 (2017-11-05) +-------------------- + +1.12.20 (2017-04-30) +-------------------- +* Update package.xml (`#263 `_) +* Contributors: Kei Okada + +1.12.19 (2016-07-24) +-------------------- + +1.12.18 (2016-07-12) +-------------------- + +1.12.17 (2016-07-11) +-------------------- + +1.12.16 (2016-03-19) +-------------------- + +1.12.15 (2016-01-17) +-------------------- + +1.12.14 (2015-07-22) +-------------------- + +1.12.13 (2015-04-06) +-------------------- + +1.12.12 (2014-12-31) +-------------------- + +1.12.11 (2014-10-26) +-------------------- + +1.12.10 (2014-09-28) +-------------------- + +1.12.9 (2014-09-21) +------------------- + +1.12.8 (2014-08-19) +------------------- + +1.12.6 (2014-07-27) +------------------- + +1.12.4 (2014-04-28) +------------------- + +1.12.3 (2014-04-12) +------------------- + +1.12.2 (2014-04-08) +------------------- + +1.11.7 (2014-03-28) +------------------- diff --git a/src/image_pipeline/image_pipeline/CMakeLists.txt b/src/image_pipeline/image_pipeline/CMakeLists.txt new file mode 100644 index 000000000..5f9638d16 --- /dev/null +++ b/src/image_pipeline/image_pipeline/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.5) +project(image_pipeline) + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake REQUIRED) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/image_pipeline/image_pipeline/package.xml b/src/image_pipeline/image_pipeline/package.xml new file mode 100644 index 000000000..97b766179 --- /dev/null +++ b/src/image_pipeline/image_pipeline/package.xml @@ -0,0 +1,38 @@ + + + + image_pipeline + 4.0.0 + image_pipeline fills the gap between getting raw images from a camera driver and higher-level vision processing. + + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/image_pipeline/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Patrick Mihelich + James Bowman + + ament_cmake + + + camera_calibration + depth_image_proc + image_proc + image_publisher + image_rotate + image_view + stereo_image_proc + + ament_lint_auto + ament_cmake_lint_cmake + ament_cmake_xmllint + + + ament_cmake + + diff --git a/src/image_pipeline/image_proc/CHANGELOG.rst b/src/image_pipeline/image_proc/CHANGELOG.rst new file mode 100644 index 000000000..22698ec2d --- /dev/null +++ b/src/image_pipeline/image_proc/CHANGELOG.rst @@ -0,0 +1,179 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package image_proc +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +4.0.0 (2022-12-24) +------------------ +* [backport iron] Removed cfg files related with ROS 1 parameters (`#911 `_) (`#914 `_) + Removed cfg files related with ROS 1 parameters. Backport + https://github.com/ros-perception/image_pipeline/pull/911 +* [backport iron] ROS 2: Merged resize.cpp: fix memory leak (`#874 `_) (`#892 `_) + backport `#874 `_ +* allow use as component or node (`#858 `_) + Backport `#852 `_ to Iron +* add myself as a maintainer (`#846 `_) +* Use the same QoS profiles as publishers in image_proc +* fix to allow remapping resize and image topics +* Contributors: Alejandro Hernández Cordero, Joe Schornak, Michael Ferguson, Michal Wojcik + +3.0.1 (2022-12-04) +------------------ +* Replace deprecated headers + Fixing compiler warnings. +* add NOLINT to keep cpplint happy about curly brace being on new line +* Add conversion from YUV422-YUY2 +* Contributors: Jacob Perron, Kenji Brameld, Tillmann Falck + +3.0.0 (2022-04-29) +------------------ +* Cleanup of image_proc. +* Some small fixes noticed while reviewing. +* Remove unnecessary find_package +* Deal with uncrustify and cpplint +* LTTng instrument image_proc::RectifyNode and image_proc::ResizeNode +* bring over ros1 fix for missing roi resize +* Add maintainer (`#667 `_) +* Fix build with later versions of OpenCV 3 +* Refactor image_proc and stereo_image_proc launch files (`#583 `_) +* Contributors: Chris Lalancette, Evan Flynn, Jacob Perron, Scott K Logan, Víctor Mayoral Vilches + +2.2.1 (2020-08-27) +------------------ +* make crop_decimate work (`#593 `_) +* remove email blasts from steve macenski (`#596 `_) +* Disable "Publish Color!" debug_info (`#577 `_) +* [Foxy] Use ament_auto Macros (`#573 `_) +* Contributors: Dereck Wonnacott, Joshua Whitley, Michael Ferguson, Steve Macenski + +2.2.0 (2020-07-27) +------------------ +* Replacing deprecated header includes with new HPP versions. (`#566 `_) +* Opencv 3 compatibility (`#564 `_) + * Remove GTK from image_view. + * Reinstate OpenCV 3 compatibility. +* Fix bad quotes in image_proc launch file (`#563 `_) + This fixes a flake8 error. +* Contributors: Chris Lalancette, Jacob Perron, Joshua Whitley + +* Initial ROS2 commit. +* Contributors: Michael Carroll + +1.12.23 (2018-05-10) +-------------------- + +1.12.22 (2017-12-08) +-------------------- +* Merge pull request `#311 `_ from knorth55/revert-299 + Revert "Fix image_resize nodelet (`#299 `_)" + This reverts commit 32e19697ebce47101b063c6a02b95dfa2c5dbc52. +* Contributors: Shingo Kitagawa, Tully Foote + +1.12.21 (2017-11-05) +-------------------- +* Fix image_resize nodelet (`#299 `_) + Update interpolation types + Add arguments to enable disable each nodelet + Add default arguments for image_resize and image_rect + Use toCVShare instead of toCVCopy + Include image_resize in image_proc +* Updated fix for traits change. (`#303 `_) +* Fix C++11 compilation + This fixes `#292 `_ and `#291 `_ +* [image_proc][crop_decimate] support changing target image frame_id (`#276 `_) +* Contributors: Furushchev, Mike Purvis, Vincent Rabaud, bikramak + +1.12.20 (2017-04-30) +-------------------- +* Add nodelet to resize image and camera_info (`#273 `_) + * Add nodelet to resize image and camera_info + * Depends on nodelet_topic_tools + * Use recursive_mutex for mutex guard for dynamic reconfiguring +* Fix nodelet name: crop_nonZero -> crop_non_zero (`#269 `_) + Fix https://github.com/ros-perception/image_pipeline/issues/217 +* Fix permission of executable files unexpectedly (`#260 `_) +* address gcc6 build error + With gcc6, compiling fails with `stdlib.h: No such file or directory`, + as including '-isystem /usr/include' breaks with gcc6, cf., + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70129. + This commit addresses this issue for this package in the same way + it was addressed in various other ROS packages. A list of related + commits and pull requests is at: + https://github.com/ros/rosdistro/issues/12783 + Signed-off-by: Lukas Bulwahn +* Contributors: Kentaro Wada, Lukas Bulwahn + +1.12.19 (2016-07-24) +-------------------- + +1.12.18 (2016-07-12) +-------------------- + +1.12.17 (2016-07-11) +-------------------- + +1.12.16 (2016-03-19) +-------------------- +* clean OpenCV dependency in package.xml +* issue `#180 `_ Check if all distortion coefficients are zero. + Test with: + rostest --reuse-master --text image_proc test_rectify.xml + Can also test interactively with vimjay image_rect.launch, which brings up an rqt gui and camera info distortion coefficients can be dynamically reconfigured. +* Add a feature to crop the largest valid (non zero) area + Remove unnecessary headers + change a filename to fit for the ROS convention +* Contributors: Kenta Yonekura, Lucas Walter, Vincent Rabaud + +1.12.15 (2016-01-17) +-------------------- +* simplify OpenCV3 conversion +* Contributors: Vincent Rabaud + +1.12.14 (2015-07-22) +-------------------- + +1.12.13 (2015-04-06) +-------------------- +* fix dependencies +* Contributors: Vincent Rabaud + +1.12.12 (2014-12-31) +-------------------- + +1.12.11 (2014-10-26) +-------------------- + +1.12.10 (2014-09-28) +-------------------- + +1.12.9 (2014-09-21) +------------------- +* get code to compile with OpenCV3 + fixes `#96 `_ +* Contributors: Vincent Rabaud + +1.12.8 (2014-08-19) +------------------- + +1.12.6 (2014-07-27) +------------------- + +1.12.4 (2014-04-28) +------------------- + +1.12.3 (2014-04-12) +------------------- + +1.12.2 (2014-04-08) +------------------- + +1.12.1 (2014-04-06) +------------------- +* get proper opencv dependency +* Contributors: Vincent Rabaud + +1.11.7 (2014-03-28) +------------------- + +1.11.6 (2014-01-29 00:38:55 +0100) +---------------------------------- +- fix bad OpenCV linkage (#53) diff --git a/src/image_pipeline/image_proc/CMakeLists.txt b/src/image_pipeline/image_proc/CMakeLists.txt new file mode 100644 index 000000000..c11a7d071 --- /dev/null +++ b/src/image_pipeline/image_proc/CMakeLists.txt @@ -0,0 +1,115 @@ +cmake_minimum_required(VERSION 3.5) +project(image_proc) + +# ROS2 Flags +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake_auto REQUIRED) +ament_auto_find_build_dependencies() + +find_package(OpenCV REQUIRED) +if(OpenCV_VERSION VERSION_LESS "3.2.0") + message(FATAL "Minimum OpenCV version is 3.2.0 (found version ${OpenCV_VERSION})") +endif() + +# image_proc library +ament_auto_add_library(${PROJECT_NAME} SHARED + src/${PROJECT_NAME}/processor.cpp +) +target_link_libraries(${PROJECT_NAME} + ${OpenCV_LIBRARIES} +) + +# rectify component and node +ament_auto_add_library(rectify SHARED + src/rectify.cpp) +target_compile_definitions(rectify + PRIVATE "COMPOSITION_BUILDING_DLL" +) +rclcpp_components_register_node(rectify + PLUGIN "image_proc::RectifyNode" + EXECUTABLE rectify_node +) + +# debayer component and node +ament_auto_add_library(debayer SHARED + src/debayer.cpp + src/edge_aware.cpp +) +target_compile_definitions(debayer + PRIVATE "COMPOSITION_BUILDING_DLL" +) +rclcpp_components_register_node(debayer + PLUGIN "image_proc::DebayerNode" + EXECUTABLE debayer_node +) + +# resize component and node +ament_auto_add_library(resize SHARED + src/resize.cpp +) +target_compile_definitions(resize + PRIVATE "COMPOSITION_BUILDING_DLL" +) +rclcpp_components_register_node(resize + PLUGIN "image_proc::ResizeNode" + EXECUTABLE resize_node +) + +# crop_decimate component and node +ament_auto_add_library(crop_decimate SHARED + src/crop_decimate.cpp +) +target_compile_definitions(crop_decimate + PRIVATE "COMPOSITION_BUILDING_DLL" +) +rclcpp_components_register_node(crop_decimate + PLUGIN "image_proc::CropDecimateNode" + EXECUTABLE crop_decimate_node +) + +# crop_non_zero component and node +ament_auto_add_library(crop_non_zero SHARED + src/crop_non_zero.cpp +) +target_compile_definitions(crop_non_zero + PRIVATE "COMPOSITION_BUILDING_DLL" +) +rclcpp_components_register_node(crop_non_zero + PLUGIN "image_proc::CropNonZeroNode" + EXECUTABLE crop_non_zero_node +) + +# image_proc example node +ament_auto_add_executable(image_proc_exe + src/image_proc.cpp +) +target_link_libraries(image_proc_exe + debayer + rectify + ament_index_cpp::ament_index_cpp +) +set_target_properties(image_proc_exe PROPERTIES OUTPUT_NAME image_proc) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_link_libraries(image_proc "stdc++fs") +endif() + +install(TARGETS image_proc_exe + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_auto_package(INSTALL_TO_SHARE launch) diff --git a/src/image_pipeline/image_proc/include/image_proc/crop_decimate.hpp b/src/image_pipeline/image_proc/include/image_proc/crop_decimate.hpp new file mode 100644 index 000000000..95e24e874 --- /dev/null +++ b/src/image_pipeline/image_proc/include/image_proc/crop_decimate.hpp @@ -0,0 +1,81 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Steve Macenski, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__CROP_DECIMATE_HPP_ +#define IMAGE_PROC__CROP_DECIMATE_HPP_ + +#include + +#include "cv_bridge/cv_bridge.hpp" + +#include +#include +#include +#include + +namespace image_proc +{ + +enum class CropDecimateModes +{ + CropDecimate_NN = 0, + CropDecimate_Linear = 1, + CropDecimate_Cubic = 2, + CropDecimate_Area = 3, + CropDecimate_Lanczos4 = 4 +}; + +using cv_bridge::CvImage; +using cv_bridge::CvImageConstPtr; +using cv_bridge::toCvShare; + +class CropDecimateNode : public rclcpp::Node +{ +public: + explicit CropDecimateNode(const rclcpp::NodeOptions &); + +private: + image_transport::CameraSubscriber sub_; + image_transport::CameraPublisher pub_; + int queue_size_; + std::string target_frame_id_; + int decimation_x_, decimation_y_, offset_x_, offset_y_, width_, height_; + CropDecimateModes interpolation_; + + void imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr info_msg); +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__CROP_DECIMATE_HPP_ diff --git a/src/image_pipeline/image_proc/include/image_proc/crop_non_zero.hpp b/src/image_pipeline/image_proc/include/image_proc/crop_non_zero.hpp new file mode 100644 index 000000000..9b6b81bf2 --- /dev/null +++ b/src/image_pipeline/image_proc/include/image_proc/crop_non_zero.hpp @@ -0,0 +1,62 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Steve Macenski, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__CROP_NON_ZERO_HPP_ +#define IMAGE_PROC__CROP_NON_ZERO_HPP_ + +#include + +#include +#include +#include + +namespace image_proc +{ + +class CropNonZeroNode : public rclcpp::Node +{ +public: + explicit CropNonZeroNode(const rclcpp::NodeOptions &); + +private: + // Subscriptions + image_transport::Subscriber sub_raw_; + + // Publications + std::mutex connect_mutex_; + + image_transport::Publisher pub_; + + void imageCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg); +}; +} // namespace image_proc +#endif // IMAGE_PROC__CROP_NON_ZERO_HPP_ diff --git a/src/image_pipeline/image_proc/include/image_proc/debayer.hpp b/src/image_pipeline/image_proc/include/image_proc/debayer.hpp new file mode 100644 index 000000000..f4847fc2c --- /dev/null +++ b/src/image_pipeline/image_proc/include/image_proc/debayer.hpp @@ -0,0 +1,68 @@ +// Copyright 2008, 2019, Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__DEBAYER_HPP_ +#define IMAGE_PROC__DEBAYER_HPP_ + +#include +#include +#include + +namespace image_proc +{ + +class DebayerNode + : public rclcpp::Node +{ +public: + explicit DebayerNode(const rclcpp::NodeOptions &); + +private: + image_transport::Subscriber sub_raw_; + + int debayer_; + + int debayer_bilinear_ = 0; + int debayer_edgeaware_ = 1; + int debayer_edgeaware_weighted_ = 2; + int debayer_vng_ = 3; + + image_transport::Publisher pub_mono_; + image_transport::Publisher pub_color_; + + void connectCb(); + void imageCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg); +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__DEBAYER_HPP_ diff --git a/src/image_pipeline/image_proc/include/image_proc/edge_aware.hpp b/src/image_pipeline/image_proc/include/image_proc/edge_aware.hpp new file mode 100644 index 000000000..3f5bb2288 --- /dev/null +++ b/src/image_pipeline/image_proc/include/image_proc/edge_aware.hpp @@ -0,0 +1,48 @@ +// Copyright 2008, 2019, Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__EDGE_AWARE_HPP_ +#define IMAGE_PROC__EDGE_AWARE_HPP_ + +#include + +// Edge-aware debayering algorithms, intended for eventual inclusion in OpenCV. + +namespace image_proc +{ + +void debayerEdgeAware(const cv::Mat & bayer, cv::Mat & color); +void debayerEdgeAwareWeighted(const cv::Mat & bayer, cv::Mat & color); + +} // namespace image_proc + +#endif // IMAGE_PROC__EDGE_AWARE_HPP_ diff --git a/src/image_pipeline/image_proc/include/image_proc/processor.hpp b/src/image_pipeline/image_proc/include/image_proc/processor.hpp new file mode 100644 index 000000000..b3480f038 --- /dev/null +++ b/src/image_pipeline/image_proc/include/image_proc/processor.hpp @@ -0,0 +1,82 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__PROCESSOR_HPP_ +#define IMAGE_PROC__PROCESSOR_HPP_ + +#include + +#include "image_geometry/pinhole_camera_model.hpp" + +#include +#include + +namespace image_proc +{ + +struct ImageSet +{ + std::string color_encoding; + cv::Mat mono; + cv::Mat rect; + cv::Mat color; + cv::Mat rect_color; +}; + +class Processor +{ +public: + Processor() + : interpolation_(cv::INTER_LINEAR) + { + } + + int interpolation_; + + enum + { + MONO = 1 << 0, + RECT = 1 << 1, + COLOR = 1 << 2, + RECT_COLOR = 1 << 3, + ALL = MONO | RECT | COLOR | RECT_COLOR + }; + + bool process( + const sensor_msgs::msg::Image::ConstSharedPtr & raw_image, + const image_geometry::PinholeCameraModel & model, + ImageSet & output, int flags = ALL) const; +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__PROCESSOR_HPP_ diff --git a/src/image_pipeline/image_proc/include/image_proc/rectify.hpp b/src/image_pipeline/image_proc/include/image_proc/rectify.hpp new file mode 100644 index 000000000..4b80b25b2 --- /dev/null +++ b/src/image_pipeline/image_proc/include/image_proc/rectify.hpp @@ -0,0 +1,73 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__RECTIFY_HPP_ +#define IMAGE_PROC__RECTIFY_HPP_ + +#include + +#include "image_geometry/pinhole_camera_model.hpp" + +#include +#include +#include +#include + +namespace image_proc +{ + +class RectifyNode + : public rclcpp::Node +{ +public: + explicit RectifyNode(const rclcpp::NodeOptions &); + +private: + image_transport::CameraSubscriber sub_camera_; + + int queue_size_; + int interpolation; + std::mutex connect_mutex_; + image_transport::Publisher pub_rect_; + + // Processing state (note: only safe because we're using single-threaded NodeHandle!) + image_geometry::PinholeCameraModel model_; + + void subscribeToCamera(const rmw_qos_profile_t & qos_profile); + void imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr & image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__RECTIFY_HPP_ diff --git a/src/image_pipeline/image_proc/include/image_proc/resize.hpp b/src/image_pipeline/image_proc/include/image_proc/resize.hpp new file mode 100644 index 000000000..d51bca4b9 --- /dev/null +++ b/src/image_pipeline/image_proc/include/image_proc/resize.hpp @@ -0,0 +1,76 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__RESIZE_HPP_ +#define IMAGE_PROC__RESIZE_HPP_ + +#include + +#include +#include +#include +#include + +namespace image_proc +{ + +class ResizeNode + : public rclcpp::Node +{ +public: + explicit ResizeNode(const rclcpp::NodeOptions &); + +protected: + image_transport::CameraPublisher pub_image_; + image_transport::CameraSubscriber sub_image_; + + int interpolation_; + bool use_scale_; + double scale_height_; + double scale_width_; + int height_; + int width_; + + cv_bridge::CvImage scaled_cv_; + + std::mutex connect_mutex_; + + void connectCb(); + + void imageCb( + sensor_msgs::msg::Image::ConstSharedPtr image_msg, + sensor_msgs::msg::CameraInfo::ConstSharedPtr info_msg); +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__RESIZE_HPP_ diff --git a/src/image_pipeline/image_proc/include/image_proc/utils.hpp b/src/image_pipeline/image_proc/include/image_proc/utils.hpp new file mode 100644 index 000000000..6be4b88f7 --- /dev/null +++ b/src/image_pipeline/image_proc/include/image_proc/utils.hpp @@ -0,0 +1,69 @@ +// Copyright 2023 Willow Garage, Inc., Michal Wojcik +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__UTILS_HPP_ +#define IMAGE_PROC__UTILS_HPP_ + +#include + +#include + +namespace image_proc +{ + +rmw_qos_profile_t getTopicQosProfile(rclcpp::Node * node, const std::string & topic) +{ + /** + * Given a topic name, get the QoS profile with which it is being published. + * Replaces history and depth settings with default sensor values since they cannot be retrieved. + * @param node pointer to the ROS node + * @param topic name of the topic + * @returns QoS profile of the publisher to the topic. If there are several publishers, it returns + * returns the profile of the first one on the list. If no publishers exist, it returns + * the sensor data profile. + */ + std::string topic_resolved = node->get_node_base_interface()->resolve_topic_or_service_name( + topic, false); + auto topics_info = node->get_publishers_info_by_topic(topic_resolved); + if (topics_info.size()) { + auto profile = topics_info[0].qos_profile().get_rmw_qos_profile(); + profile.history = rmw_qos_profile_sensor_data.history; + profile.depth = rmw_qos_profile_sensor_data.depth; + return profile; + } else { + return rmw_qos_profile_sensor_data; + } +} + +} // namespace image_proc + +#endif // IMAGE_PROC__UTILS_HPP_ diff --git a/src/image_pipeline/image_proc/launch/image_proc.launch.py b/src/image_pipeline/image_proc/launch/image_proc.launch.py new file mode 100644 index 000000000..91af35f7f --- /dev/null +++ b/src/image_pipeline/image_proc/launch/image_proc.launch.py @@ -0,0 +1,104 @@ +# Copyright (c) 2020, Open Source Robotics Foundation, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * 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 OWNER 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. + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.conditions import LaunchConfigurationEquals +from launch.conditions import LaunchConfigurationNotEquals +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import ComposableNodeContainer +from launch_ros.actions import LoadComposableNodes +from launch_ros.descriptions import ComposableNode + + +def generate_launch_description(): + composable_nodes = [ + ComposableNode( + package='image_proc', + plugin='image_proc::DebayerNode', + name='debayer_node', + ), + ComposableNode( + package='image_proc', + plugin='image_proc::RectifyNode', + name='rectify_mono_node', + # Remap subscribers and publishers + remappings=[ + ('image', 'image_mono'), + ('camera_info', 'camera_info'), + ('image_rect', 'image_rect') + ], + ), + ComposableNode( + package='image_proc', + plugin='image_proc::RectifyNode', + name='rectify_color_node', + # Remap subscribers and publishers + remappings=[ + ('image', 'image_color'), + ('image_rect', 'image_rect_color') + ], + ) + ] + + arg_container = DeclareLaunchArgument( + name='container', default_value='', + description=( + 'Name of an existing node container to load launched nodes into. ' + 'If unset, a new container will be created.' + ) + ) + + # If an existing container is not provided, start a container and load nodes into it + image_processing_container = ComposableNodeContainer( + condition=LaunchConfigurationEquals('container', ''), + name='image_proc_container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=composable_nodes, + output='screen' + ) + + # If an existing container name is provided, load composable nodes into it + # This will block until a container with the provided name is available and nodes are loaded + load_composable_nodes = LoadComposableNodes( + condition=LaunchConfigurationNotEquals('container', ''), + composable_node_descriptions=composable_nodes, + target_container=LaunchConfiguration('container'), + ) + + return LaunchDescription([ + arg_container, + image_processing_container, + load_composable_nodes, + ]) diff --git a/src/image_pipeline/image_proc/mainpage.dox b/src/image_pipeline/image_proc/mainpage.dox new file mode 100644 index 000000000..ee611a523 --- /dev/null +++ b/src/image_pipeline/image_proc/mainpage.dox @@ -0,0 +1,12 @@ +/** +@mainpage +@htmlinclude manifest.html + +@b image_proc provides a node for performing single image rectification and +color processing on the raw images produced by a camera. The outputs of +image_proc are suitable for visual processing by other nodes. See +http://www.ros.org/wiki/image_proc for documentation. + +Currently this package has no public code API. + +*/ diff --git a/src/image_pipeline/image_proc/package.xml b/src/image_pipeline/image_proc/package.xml new file mode 100644 index 000000000..e2afd43b2 --- /dev/null +++ b/src/image_pipeline/image_proc/package.xml @@ -0,0 +1,39 @@ + + + + image_proc + 4.0.0 + Single image rectification and color processing. + + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/image_proc/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Patrick Mihelich + Kurt Konolige + Jeremy Leibs + + ament_cmake_auto + + cv_bridge + image_geometry + image_transport + libopencv-dev + rclcpp + rclcpp_components + rcutils + sensor_msgs + tracetools_image_pipeline + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/image_pipeline/image_proc/src/crop_decimate.cpp b/src/image_pipeline/image_proc/src/crop_decimate.cpp new file mode 100644 index 000000000..c03bd3f75 --- /dev/null +++ b/src/image_pipeline/image_proc/src/crop_decimate.cpp @@ -0,0 +1,340 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Steve Macenski, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 + +#include +#include + +#include +#include +#include +#include +#include + +namespace image_proc +{ + +template +void debayer2x2toBGR( + const cv::Mat & src, cv::Mat & dst, + int R, int G1, int G2, int B) +{ + typedef cv::Vec DstPixel; // 8- or 16-bit BGR +#if CV_VERSION_MAJOR >= 4 || (CV_MAJOR_VERSION == 3 && CV_MINOR_VERSION > 2) + dst.create(src.rows / 2, src.cols / 2, cv::traits::Type::value); +#else + dst.create(src.rows / 2, src.cols / 2, cv::DataType::type); +#endif + + int src_row_step = src.step1(); + int dst_row_step = dst.step1(); + const T * src_row = src.ptr(); + T * dst_row = dst.ptr(); + + // 2x2 downsample and debayer at once + for (int y = 0; y < dst.rows; ++y) { + for (int x = 0; x < dst.cols; ++x) { + dst_row[x * 3 + 0] = src_row[x * 2 + B]; + dst_row[x * 3 + 1] = (src_row[x * 2 + G1] + src_row[x * 2 + G2]) / 2; + dst_row[x * 3 + 2] = src_row[x * 2 + R]; + } + + src_row += src_row_step * 2; + dst_row += dst_row_step; + } +} + +// Templated on pixel size, in bytes (MONO8 = 1, BGR8 = 3, RGBA16 = 8, ...) +template +void decimate(const cv::Mat & src, cv::Mat & dst, int decimation_x, int decimation_y) +{ + dst.create(src.rows / decimation_y, src.cols / decimation_x, src.type()); + + int src_row_step = src.step[0] * decimation_y; + int src_pixel_step = N * decimation_x; + int dst_row_step = dst.step[0]; + + const uint8_t * src_row = src.ptr(); + uint8_t * dst_row = dst.ptr(); + + for (int y = 0; y < dst.rows; ++y) { + const uint8_t * src_pixel = src_row; + uint8_t * dst_pixel = dst_row; + + for (int x = 0; x < dst.cols; ++x) { + memcpy(dst_pixel, src_pixel, N); // Should inline with small, fixed N + src_pixel += src_pixel_step; + dst_pixel += N; + } + + src_row += src_row_step; + dst_row += dst_row_step; + } +} + +CropDecimateNode::CropDecimateNode(const rclcpp::NodeOptions & options) +: Node("CropNonZeroNode", options) +{ + auto qos_profile = getTopicQosProfile(this, "in/image_raw"); + + queue_size_ = this->declare_parameter("queue_size", 5); + target_frame_id_ = this->declare_parameter("target_frame_id", std::string()); + + // default: do nothing + decimation_x_ = this->declare_parameter("decimation_x", 1); + decimation_y_ = this->declare_parameter("decimation_y", 1); + + // default: use full image + width_ = this->declare_parameter("width", 0); + height_ = this->declare_parameter("height", 0); + offset_x_ = this->declare_parameter("offset_x", 0); + offset_y_ = this->declare_parameter("offset_y", 0); + + // default: CropDecimate_NN + int interpolation = this->declare_parameter("interpolation", 0); + interpolation_ = static_cast(interpolation); + + pub_ = image_transport::create_camera_publisher(this, "out/image_raw", qos_profile); + sub_ = image_transport::create_camera_subscription( + this, "in/image_raw", std::bind( + &CropDecimateNode::imageCb, this, + std::placeholders::_1, std::placeholders::_2), "raw", qos_profile); +} + +void CropDecimateNode::imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr info_msg) +{ + /// @todo Check image dimensions match info_msg + + if (pub_.getNumSubscribers() < 1) { + return; + } + + int decimation_x = decimation_x_; + int decimation_y = decimation_y_; + + // Compute the ROI we'll actually use + bool is_bayer = sensor_msgs::image_encodings::isBayer(image_msg->encoding); + + if (is_bayer) { + // Odd offsets for Bayer images basically change the Bayer pattern, but that's + // unnecessarily complicated to support + offset_x_ &= ~0x1; + offset_y_ &= ~0x1; + width_ &= ~0x1; + height_ &= ~0x1; + } + + int max_width = image_msg->width - offset_x_; + + if (max_width <= 0) { + RCLCPP_WARN( + get_logger(), + "x offset is outside the input image width: " + "%i, x offset: %i.", image_msg->width, offset_x_); + return; + } + + int max_height = image_msg->height - offset_y_; + + if (max_height <= 0) { + RCLCPP_WARN( + get_logger(), + "y offset is outside the input image height: " + "%i, y offset: %i", image_msg->height, offset_y_); + return; + } + + int width = width_; + int height = height_; + + if (width == 0 || width > max_width) { + width = max_width; + } + + if (height == 0 || height > max_height) { + height = max_height; + } + + // On no-op, just pass the messages along + if ( + decimation_x == 1 && decimation_y == 1 && + offset_x_ == 0 && offset_y_ == 0 && + width == static_cast(image_msg->width) && + height == static_cast(image_msg->height)) + { + pub_.publish(image_msg, info_msg); + return; + } + + // Get a cv::Mat view of the source data + CvImageConstPtr source = toCvShare(image_msg); + + // Except in Bayer downsampling case, output has same encoding as the input + CvImage output(source->header, source->encoding); + // Apply ROI (no copy, still a view of the image_msg data) + output.image = source->image(cv::Rect(offset_x_, offset_y_, width, height)); + + // Special case: when decimating Bayer images, we first do a 2x2 decimation to BGR + if (is_bayer && (decimation_x > 1 || decimation_y > 1)) { + if (decimation_x % 2 != 0 || decimation_y % 2 != 0) { + RCLCPP_ERROR( + get_logger(), + "Odd decimation not supported for Bayer images"); + return; + } + + cv::Mat bgr; + int step = output.image.step1(); + if (image_msg->encoding == sensor_msgs::image_encodings::BAYER_RGGB8) { + debayer2x2toBGR(output.image, bgr, 0, 1, step, step + 1); + } else if (image_msg->encoding == sensor_msgs::image_encodings::BAYER_BGGR8) { + debayer2x2toBGR(output.image, bgr, step + 1, 1, step, 0); + } else if (image_msg->encoding == sensor_msgs::image_encodings::BAYER_GBRG8) { + debayer2x2toBGR(output.image, bgr, step, 0, step + 1, 1); + } else if (image_msg->encoding == sensor_msgs::image_encodings::BAYER_GRBG8) { + debayer2x2toBGR(output.image, bgr, 1, 0, step + 1, step); + } else if (image_msg->encoding == sensor_msgs::image_encodings::BAYER_RGGB16) { + debayer2x2toBGR(output.image, bgr, 0, 1, step, step + 1); + } else if (image_msg->encoding == sensor_msgs::image_encodings::BAYER_BGGR16) { + debayer2x2toBGR(output.image, bgr, step + 1, 1, step, 0); + } else if (image_msg->encoding == sensor_msgs::image_encodings::BAYER_GBRG16) { + debayer2x2toBGR(output.image, bgr, step, 0, step + 1, 1); + } else if (image_msg->encoding == sensor_msgs::image_encodings::BAYER_GRBG16) { + debayer2x2toBGR(output.image, bgr, 1, 0, step + 1, step); + } else { + RCLCPP_ERROR( + get_logger(), "Unrecognized Bayer encoding '%s'", + image_msg->encoding.c_str()); + return; + } + + output.image = bgr; + output.encoding = (bgr.depth() == CV_8U) ? sensor_msgs::image_encodings::BGR8 : + sensor_msgs::image_encodings::BGR16; + decimation_x /= 2; + decimation_y /= 2; + } + + // Apply further downsampling, if necessary + if (decimation_x > 1 || decimation_y > 1) { + cv::Mat decimated; + + if (interpolation_ == image_proc::CropDecimateModes::CropDecimate_NN) { + // Use optimized method instead of OpenCV's more general NN resize + int pixel_size = output.image.elemSize(); + + switch (pixel_size) { + // Currently support up through 4-channel float + case 1: + decimate<1>(output.image, decimated, decimation_x, decimation_y); + break; + case 2: + decimate<2>(output.image, decimated, decimation_x, decimation_y); + break; + case 3: + decimate<3>(output.image, decimated, decimation_x, decimation_y); + break; + case 4: + decimate<4>(output.image, decimated, decimation_x, decimation_y); + break; + case 6: + decimate<6>(output.image, decimated, decimation_x, decimation_y); + break; + case 8: + decimate<8>(output.image, decimated, decimation_x, decimation_y); + break; + case 12: + decimate<12>(output.image, decimated, decimation_x, decimation_y); + break; + case 16: + decimate<16>(output.image, decimated, decimation_x, decimation_y); + break; + default: + RCLCPP_ERROR( + get_logger(), + "Unsupported pixel size, %d bytes", pixel_size); + return; + } + } else { + // Linear, cubic, area, ... + cv::Size size(output.image.cols / decimation_x, output.image.rows / decimation_y); + cv::resize(output.image, decimated, size, 0.0, 0.0, static_cast(interpolation_)); + } + + output.image = decimated; + } + + // Create output Image message + /// @todo Could save copies by allocating this above and having output.image alias it + sensor_msgs::msg::Image::SharedPtr out_image = output.toImageMsg(); + + // Create updated CameraInfo message + sensor_msgs::msg::CameraInfo::SharedPtr out_info = + std::make_shared(*info_msg); + int binning_x = std::max(static_cast(info_msg->binning_x), 1); + int binning_y = std::max(static_cast(info_msg->binning_y), 1); + out_info->binning_x = binning_x * decimation_x_; + out_info->binning_y = binning_y * decimation_y_; + out_info->roi.x_offset += offset_x_ * binning_x; + out_info->roi.y_offset += offset_y_ * binning_y; + out_info->roi.height = height * binning_y; + out_info->roi.width = width * binning_x; + + // If no ROI specified, leave do_rectify as-is. If ROI specified, set do_rectify = true. + if (width != static_cast(image_msg->width) || + height != static_cast(image_msg->height)) + { + out_info->roi.do_rectify = true; + } + + if (!target_frame_id_.empty()) { + out_image->header.frame_id = target_frame_id_; + out_info->header.frame_id = target_frame_id_; + } + + pub_.publish(out_image, out_info); +} + +} // namespace image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +// This acts as a sort of entry point, allowing the component to be discoverable when its library +// is being loaded into a running process. +RCLCPP_COMPONENTS_REGISTER_NODE(image_proc::CropDecimateNode) diff --git a/src/image_pipeline/image_proc/src/crop_non_zero.cpp b/src/image_pipeline/image_proc/src/crop_non_zero.cpp new file mode 100644 index 000000000..f94f9f7ae --- /dev/null +++ b/src/image_pipeline/image_proc/src/crop_non_zero.cpp @@ -0,0 +1,122 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Steve Macenski, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 + +#include "cv_bridge/cv_bridge.hpp" + +#include +#include + +#include +#include +#include +#include +#include + +namespace image_proc +{ + +CropNonZeroNode::CropNonZeroNode(const rclcpp::NodeOptions & options) +: Node("CropNonZeroNode", options) +{ + auto qos_profile = getTopicQosProfile(this, "image_raw"); + pub_ = image_transport::create_publisher(this, "image", qos_profile); + RCLCPP_INFO(this->get_logger(), "subscribe: %s", "image_raw"); + sub_raw_ = image_transport::create_subscription( + this, "image_raw", + std::bind( + &CropNonZeroNode::imageCb, + this, std::placeholders::_1), "raw", qos_profile); +} + +void CropNonZeroNode::imageCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg) +{ + cv_bridge::CvImagePtr cv_ptr; + try { + cv_ptr = cv_bridge::toCvCopy(raw_msg); + } catch (cv_bridge::Exception & e) { + RCLCPP_ERROR(this->get_logger(), "cv_bridge exception: %s", e.what()); + return; + } + + // Check the number of channels + if (sensor_msgs::image_encodings::numChannels(raw_msg->encoding) != 1) { + RCLCPP_ERROR( + this->get_logger(), "Only grayscale image is acceptable, got [%s]", + raw_msg->encoding.c_str()); + return; + } + std::vector> cnt; + cv::Mat1b m(raw_msg->width, raw_msg->height); + + if (raw_msg->encoding == sensor_msgs::image_encodings::TYPE_8UC1) { + m = cv_ptr->image; + } else { + double minVal, maxVal; + cv::minMaxIdx(cv_ptr->image, &minVal, &maxVal, 0, 0, cv_ptr->image != 0.); + double ra = maxVal - minVal; + + cv_ptr->image.convertTo(m, CV_8U, 255. / ra, -minVal * 255. / ra); + } + + cv::findContours(m, cnt, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); + + // search the largest area + std::vector>::iterator it = + std::max_element( + cnt.begin(), cnt.end(), [](std::vector a, + std::vector b) { + return a.size() < b.size(); + }); + + cv::Rect r = cv::boundingRect(cnt[std::distance(cnt.begin(), it)]); + + cv_bridge::CvImage out_msg; + out_msg.header = raw_msg->header; + out_msg.encoding = raw_msg->encoding; + out_msg.image = cv_ptr->image(r); + + pub_.publish(out_msg.toImageMsg()); +} + +} // namespace image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +// This acts as a sort of entry point, allowing the component to be discoverable when its library +// is being loaded into a running process. +RCLCPP_COMPONENTS_REGISTER_NODE(image_proc::CropNonZeroNode) diff --git a/src/image_pipeline/image_proc/src/debayer.cpp b/src/image_pipeline/image_proc/src/debayer.cpp new file mode 100644 index 000000000..24cb168b4 --- /dev/null +++ b/src/image_pipeline/image_proc/src/debayer.cpp @@ -0,0 +1,227 @@ +// Copyright 2008, 2019, Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 "cv_bridge/cv_bridge.hpp" + +#include +#include +// Until merged into OpenCV +#include +#include +#include +#include +#include + +namespace image_proc +{ + +DebayerNode::DebayerNode(const rclcpp::NodeOptions & options) +: Node("DebayerNode", options) +{ + auto qos_profile = getTopicQosProfile(this, "image_raw"); + sub_raw_ = image_transport::create_subscription( + this, "image_raw", + std::bind( + &DebayerNode::imageCb, this, + std::placeholders::_1), "raw", qos_profile); + + pub_mono_ = image_transport::create_publisher(this, "image_mono", qos_profile); + pub_color_ = image_transport::create_publisher(this, "image_color", qos_profile); + debayer_ = this->declare_parameter("debayer", 3); +} + +void DebayerNode::imageCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg) +{ + int bit_depth = sensor_msgs::image_encodings::bitDepth(raw_msg->encoding); + // TODO(someone): Fix as soon as bitDepth fixes it + if (raw_msg->encoding == sensor_msgs::image_encodings::YUV422) { + bit_depth = 8; + } + + // First publish to mono if needed + if (pub_mono_.getNumSubscribers()) { + if (sensor_msgs::image_encodings::isMono(raw_msg->encoding)) { + pub_mono_.publish(raw_msg); + } else { + if ((bit_depth != 8) && (bit_depth != 16)) { + RCLCPP_WARN( + this->get_logger(), + "Raw image data from topic '%s' has unsupported depth: %d", + sub_raw_.getTopic().c_str(), bit_depth); + } else { + // Use cv_bridge to convert to Mono. If a type is not supported, + // it will error out there + sensor_msgs::msg::Image::SharedPtr gray_msg; + + try { + if (bit_depth == 8) { + gray_msg = + cv_bridge::toCvCopy(raw_msg, sensor_msgs::image_encodings::MONO8)->toImageMsg(); + } else { + gray_msg = + cv_bridge::toCvCopy(raw_msg, sensor_msgs::image_encodings::MONO16)->toImageMsg(); + } + + pub_mono_.publish(gray_msg); + } catch (cv_bridge::Exception & e) { + RCLCPP_WARN(this->get_logger(), "cv_bridge conversion error: '%s'", e.what()); + } + } + } + } + + // Next, publish to color + if (!pub_color_.getNumSubscribers()) { + return; + } + + if (sensor_msgs::image_encodings::isMono(raw_msg->encoding)) { + // For monochrome, no processing needed! + pub_color_.publish(raw_msg); + + // Warn if the user asked for color + RCLCPP_WARN( + this->get_logger(), + "Color topic '%s' requested, but raw image data from topic '%s' is grayscale", + pub_color_.getTopic().c_str(), sub_raw_.getTopic().c_str()); + } else if (sensor_msgs::image_encodings::isColor(raw_msg->encoding)) { + pub_color_.publish(raw_msg); + } else if (sensor_msgs::image_encodings::isBayer(raw_msg->encoding)) { + int type = bit_depth == 8 ? CV_8U : CV_16U; + const cv::Mat bayer( + raw_msg->height, raw_msg->width, CV_MAKETYPE(type, 1), + const_cast(&raw_msg->data[0]), raw_msg->step); + + sensor_msgs::msg::Image::SharedPtr color_msg = + std::make_shared(); + color_msg->header = raw_msg->header; + color_msg->height = raw_msg->height; + color_msg->width = raw_msg->width; + color_msg->encoding = + bit_depth == 8 ? sensor_msgs::image_encodings::BGR8 : sensor_msgs::image_encodings::BGR16; + color_msg->step = color_msg->width * 3 * (bit_depth / 8); + color_msg->data.resize(color_msg->height * color_msg->step); + + cv::Mat color( + color_msg->height, color_msg->width, CV_MAKETYPE(type, 3), + &color_msg->data[0], color_msg->step); + + int algorithm; + // std::loc_guard loc(config_mutex_) + algorithm = debayer_; + + if (algorithm == debayer_edgeaware_ || + algorithm == debayer_edgeaware_weighted_) + { + // These algorithms are not in OpenCV yet + if (raw_msg->encoding != sensor_msgs::image_encodings::BAYER_GRBG8) { + RCLCPP_WARN( + this->get_logger(), "Edge aware algorithms currently only support GRBG8 Bayer. " + "Falling back to bilinear interpolation."); + algorithm = debayer_bilinear_; + } else { + if (algorithm == debayer_edgeaware_) { + debayerEdgeAware(bayer, color); + } else { + debayerEdgeAwareWeighted(bayer, color); + } + } + } + + if (algorithm == debayer_bilinear_ || algorithm == debayer_vng_) { + int code = -1; + + if (raw_msg->encoding == sensor_msgs::image_encodings::BAYER_RGGB8 || + raw_msg->encoding == sensor_msgs::image_encodings::BAYER_RGGB16) + { + code = cv::COLOR_BayerBG2BGR; + } else if (raw_msg->encoding == sensor_msgs::image_encodings::BAYER_BGGR8 || // NOLINT + raw_msg->encoding == sensor_msgs::image_encodings::BAYER_BGGR16) + { + code = cv::COLOR_BayerRG2BGR; + } else if (raw_msg->encoding == sensor_msgs::image_encodings::BAYER_GBRG8 || // NOLINT + raw_msg->encoding == sensor_msgs::image_encodings::BAYER_GBRG16) + { + code = cv::COLOR_BayerGR2BGR; + } else if (raw_msg->encoding == sensor_msgs::image_encodings::BAYER_GRBG8 || // NOLINT + raw_msg->encoding == sensor_msgs::image_encodings::BAYER_GRBG16) + { + code = cv::COLOR_BayerGB2BGR; + } + + if (algorithm == debayer_vng_) { + code += cv::COLOR_BayerBG2BGR_VNG - cv::COLOR_BayerBG2BGR; + } + + cv::cvtColor(bayer, color, code); + } + + pub_color_.publish(color_msg); + } else if (raw_msg->encoding == sensor_msgs::image_encodings::YUV422 || // NOLINT + raw_msg->encoding == sensor_msgs::image_encodings::YUV422_YUY2) + { + // Use cv_bridge to convert to BGR8 + sensor_msgs::msg::Image::SharedPtr color_msg; + + try { + color_msg = cv_bridge::toCvCopy(raw_msg, sensor_msgs::image_encodings::BGR8)->toImageMsg(); + pub_color_.publish(color_msg); + } catch (const cv_bridge::Exception & e) { + RCLCPP_WARN(this->get_logger(), "cv_bridge conversion error: '%s'", e.what()); + } + } else if (raw_msg->encoding == sensor_msgs::image_encodings::TYPE_8UC3) { + // 8UC3 does not specify a color encoding. Is it BGR, RGB, HSV, XYZ, LUV...? + RCLCPP_WARN( + this->get_logger(), + "Raw image topic '%s' has ambiguous encoding '8UC3'. The " + "source should set the encoding to 'bgr8' or 'rgb8'.", + sub_raw_.getTopic().c_str()); + } else { + RCLCPP_WARN( + this->get_logger(), "Raw image topic '%s' has unsupported encoding '%s'", + sub_raw_.getTopic().c_str(), raw_msg->encoding.c_str()); + } +} + +} // namespace image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +// This acts as a sort of entry point, allowing the component to be discoverable when its library +// is being loaded into a running process. +RCLCPP_COMPONENTS_REGISTER_NODE(image_proc::DebayerNode) diff --git a/src/image_pipeline/image_proc/src/edge_aware.cpp b/src/image_pipeline/image_proc/src/edge_aware.cpp new file mode 100644 index 000000000..f3239955e --- /dev/null +++ b/src/image_pipeline/image_proc/src/edge_aware.cpp @@ -0,0 +1,928 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 "image_proc/edge_aware.hpp" + +namespace image_proc +{ + +constexpr int avg(int a, int b) +{ + return (a + b) >> 1; +} + +constexpr int avg3(int a, int b, int c) +{ + return (a + b + c) / 3; +} + +constexpr int avg4(int a, int b, int c, int d) +{ + return (a + b + c + d) >> 2; +} + +constexpr int wavg4(int a, int b, int c, int d, int x, int y) +{ + return ((a + b) * x + (c + d) * y) / (2 * (x + y)); +} + +void debayerEdgeAware(const cv::Mat & bayer, cv::Mat & color) +{ + unsigned width = bayer.cols; + unsigned height = bayer.rows; + unsigned rgb_line_step = color.step[0]; + unsigned rgb_line_skip = rgb_line_step - width * 3; + int bayer_line_step = bayer.step[0]; + int bayer_line_step2 = bayer_line_step * 2; + + unsigned char * rgb_buffer = color.data; + unsigned char * bayer_pixel = bayer.data; + unsigned yIdx, xIdx; + + int dh, dv; + + // first two pixel values for first two lines + // Bayer 0 1 2 + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // 0 g R g + // line_step b g b + // line_step2 g r g + // rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = avg3(bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step] = + avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = + avg3(bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[bayer_line_step2]); + // rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + // rgb_pixel[rgb_line_step + 3] = avg(bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = avg(bayer_pixel[line_step] , bayer_pixel[line_step+2] ); + + rgb_buffer += 6; + bayer_pixel += 2; + // rest of the first two lines + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) { + // GRGR line + // Bayer -1 0 1 2 + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = bayer_pixel[bayer_line_step + 1]; + + // Bayer -1 0 1 2 + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg3(bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step] = avg4( + bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], + bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = avg4( + bayer_pixel[0], bayer_pixel[bayer_line_step2], + bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = avg(bayer_pixel[line_step] , bayer_pixel[line_step+2] ); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = + rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg(bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + // rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step] = avg4( + bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], + bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = avg4( + bayer_pixel[0], bayer_pixel[bayer_line_step2], + bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + // rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + + // main processing + for (yIdx = 2; yIdx < height - 2; yIdx += 2) { + // first two pixel values + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[2] = avg(bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + // line_step2 g r g + // rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = avg4( + bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], + bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = avg4( + bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], + bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step] = + avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = avg3( + bayer_pixel[0], bayer_pixel[bayer_line_step + 1], bayer_pixel[bayer_line_step2]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + // rgb_pixel[rgb_line_step + 3] = avg(bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + + // continue with rest of the line + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) { + // GRGR line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = avg(bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + + dh = std::abs(bayer_pixel[0] - bayer_pixel[2]); + dv = std::abs(bayer_pixel[-bayer_line_step + 1] - bayer_pixel[bayer_line_step + 1]); + + if (dh > dv) { + rgb_buffer[4] = avg(bayer_pixel[-bayer_line_step + 1], bayer_pixel[bayer_line_step + 1]); + } else if (dv > dh) { + rgb_buffer[4] = avg(bayer_pixel[0], bayer_pixel[2]); + } else { + rgb_buffer[4] = avg4( + bayer_pixel[-bayer_line_step + 1], bayer_pixel[bayer_line_step + 1], + bayer_pixel[0], bayer_pixel[2]); + } + + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[5] = avg4( + bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step], + bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step] = avg4( + bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], + bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + dv = std::abs(bayer_pixel[0] - bayer_pixel[bayer_line_step2]); + dh = std::abs(bayer_pixel[bayer_line_step - 1] - bayer_pixel[bayer_line_step + 1]); + + if (dv > dh) { + rgb_buffer[rgb_line_step + 1] = + avg(bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + } else if (dh > dv) { + rgb_buffer[rgb_line_step + 1] = avg(bayer_pixel[0], bayer_pixel[bayer_line_step2]); + } else { + rgb_buffer[rgb_line_step + 1] = avg4( + bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], + bayer_pixel[bayer_line_step + 1]); + } + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixels of the line + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = + rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg(bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + // rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step] = avg4( + bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], + bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = avg4( + bayer_pixel[0], bayer_pixel[bayer_line_step2], + bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + // rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + } + + // last two lines + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step] = + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + // rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = avg4( + bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], + bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = avg4( + bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], + bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step B g b + // rgb_pixel[rgb_line_step] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 1] = avg(bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step b G b + // rgb_pixel[rgb_line_step + 3] = avg(bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + + // rest of the last two lines + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) { + // GRGR line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r G r g + // line_step g b g b + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = avg(bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g R g + // line_step g b g b + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg4( + bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], + bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = avg4( + bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], + bayer_pixel[-bayer_line_step], bayer_pixel[-bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g B g b + rgb_buffer[rgb_line_step] = avg(bayer_pixel[-1], bayer_pixel[1]); + rgb_buffer[rgb_line_step + 1] = avg3( + bayer_pixel[0], bayer_pixel[bayer_line_step - 1], + bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + // rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // -1 g b g + // 0 r G r + // line_step g b g + rgb_buffer[rgb_line_step] = rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[5] = rgb_buffer[2] = avg(bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 + // -1 g b g + // 0 r g R + // line_step g b g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg3( + bayer_pixel[0], bayer_pixel[bayer_line_step + 1], + bayer_pixel[-bayer_line_step + 1]); + // rgb_pixel[5] = avg(bayer_pixel[line_step], bayer_pixel[-line_step] ); + + // BGBG line + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g B g + // rgb_pixel[rgb_line_step] = avg2(bayer_pixel[-1], bayer_pixel[1] ); + rgb_buffer[rgb_line_step + 1] = avg3( + bayer_pixel[0], bayer_pixel[bayer_line_step - 1], + bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g b G + // rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; +} + +void debayerEdgeAwareWeighted(const cv::Mat & bayer, cv::Mat & color) +{ + unsigned width = bayer.cols; + unsigned height = bayer.rows; + unsigned rgb_line_step = color.step[0]; + unsigned rgb_line_skip = rgb_line_step - width * 3; + int bayer_line_step = bayer.step[0]; + int bayer_line_step2 = bayer_line_step * 2; + + unsigned char * rgb_buffer = color.data; + unsigned char * bayer_pixel = bayer.data; + unsigned yIdx, xIdx; + + int dh, dv; + + // first two pixel values for first two lines + // Bayer 0 1 2 + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // 0 g R g + // line_step b g b + // line_step2 g r g + // rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = avg3(bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step] = + avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = avg3( + bayer_pixel[0], bayer_pixel[bayer_line_step + 1], + bayer_pixel[bayer_line_step2]); + // rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + // rgb_pixel[rgb_line_step + 3] = avg(bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = avg(bayer_pixel[line_step] , bayer_pixel[line_step+2]); + + rgb_buffer += 6; + bayer_pixel += 2; + + // rest of the first two lines + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) { + // GRGR line + // Bayer -1 0 1 2 + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = bayer_pixel[bayer_line_step + 1]; + + // Bayer -1 0 1 2 + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg3(bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step] = avg4( + bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], + bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = avg4( + bayer_pixel[0], bayer_pixel[bayer_line_step2], + bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 2 + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = avg(bayer_pixel[line_step] , bayer_pixel[line_step+2] ); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = + rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg(bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + // rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step] = avg4( + bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], + bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = avg4( + bayer_pixel[0], bayer_pixel[bayer_line_step2], + bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + // rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + + // main processing + for (yIdx = 2; yIdx < height - 2; yIdx += 2) { + // first two pixel values + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + // line_step2 g r g + + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[2] = avg(bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + // line_step2 g r g + // rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = avg4( + bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], + bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = avg4( + bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], + bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // 0 g r g + // line_step B g b + // line_step2 g r g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step] = + avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 1] = avg3( + bayer_pixel[0], bayer_pixel[bayer_line_step + 1], + bayer_pixel[bayer_line_step2]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // pixel (1, 1) 0 1 2 + // 0 g r g + // line_step b G b + // line_step2 g r g + // rgb_pixel[rgb_line_step + 3] = avg(bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + + // continue with rest of the line + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) { + // GRGR line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r G r g + // line_step g b g b + // line_step2 r g r g + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = avg(bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g R g + // line_step g b g b + // line_step2 r g r g + + dh = std::abs(bayer_pixel[0] - bayer_pixel[2]); + dv = std::abs(bayer_pixel[-bayer_line_step + 1] - bayer_pixel[bayer_line_step + 1]); + + if (dv == 0 && dh == 0) { + rgb_buffer[4] = avg4( + bayer_pixel[1 - bayer_line_step], bayer_pixel[1 + bayer_line_step], + bayer_pixel[0], bayer_pixel[2]); + } else { + rgb_buffer[4] = wavg4( + bayer_pixel[1 - bayer_line_step], bayer_pixel[1 + bayer_line_step], + bayer_pixel[0], bayer_pixel[2], dh, dv); + } + + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[5] = avg4( + bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step], + bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g B g b + // line_step2 r g r g + rgb_buffer[rgb_line_step] = avg4( + bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], + bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + dv = std::abs(bayer_pixel[0] - bayer_pixel[bayer_line_step2]); + dh = std::abs(bayer_pixel[bayer_line_step - 1] - bayer_pixel[bayer_line_step + 1]); + + if (dv == 0 && dh == 0) { + rgb_buffer[rgb_line_step + 1] = avg4( + bayer_pixel[0], bayer_pixel[bayer_line_step2], + bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + } else { + rgb_buffer[rgb_line_step + 1] = wavg4( + bayer_pixel[0], bayer_pixel[bayer_line_step2], bayer_pixel[bayer_line_step - 1], + bayer_pixel[bayer_line_step + 1], dh, dv); + } + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + // line_step2 r g r g + rgb_buffer[rgb_line_step + 3] = + avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixels of the line + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // 0 r G r + // line_step g b g + // line_step2 r g r + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = + rgb_buffer[5] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // 0 r g R + // line_step g b g + // line_step2 r g r + rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg(bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + // rgb_pixel[5] = bayer_pixel[line_step]; + + // BGBG line + // Bayer -1 0 1 + // 0 r g r + // line_step g B g + // line_step2 r g r + rgb_buffer[rgb_line_step] = avg4( + bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1], + bayer_pixel[-1], bayer_pixel[bayer_line_step2 - 1]); + rgb_buffer[rgb_line_step + 1] = avg4( + bayer_pixel[0], bayer_pixel[bayer_line_step2], + bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + // rgb_pixel[rgb_line_step + 2] = bayer_pixel[line_step]; + + // Bayer -1 0 1 + // 0 r g r + // line_step g b G + // line_step2 r g r + rgb_buffer[rgb_line_step + 3] = avg(bayer_pixel[1], bayer_pixel[bayer_line_step2 + 1]); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; + + bayer_pixel += bayer_line_step + 2; + rgb_buffer += rgb_line_step + 6 + rgb_line_skip; + } + + // last two lines + // Bayer 0 1 2 + // -1 b g b + // 0 G r g + // line_step b g b + + rgb_buffer[rgb_line_step + 3] = rgb_buffer[rgb_line_step] = + rgb_buffer[3] = rgb_buffer[0] = bayer_pixel[1]; // red pixel + rgb_buffer[1] = bayer_pixel[0]; // green pixel + rgb_buffer[rgb_line_step + 2] = rgb_buffer[2] = bayer_pixel[bayer_line_step]; // blue; + + // Bayer 0 1 2 + // -1 b g b + // 0 g R g + // line_step b g b + // rgb_pixel[3] = bayer_pixel[1]; + rgb_buffer[4] = avg4( + bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], + bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = avg4( + bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], + bayer_pixel[-bayer_line_step], bayer_pixel[2 - bayer_line_step]); + + // BGBG line + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step B g b + // rgb_pixel[rgb_line_step] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 1] = avg(bayer_pixel[0], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer 0 1 2 + // -1 b g b + // 0 g r g + // line_step b G b + // rgb_pixel[rgb_line_step + 3] = avg(bayer_pixel[1] , bayer_pixel[line_step2+1] ); + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + + rgb_buffer += 6; + bayer_pixel += 2; + + // rest of the last two lines + for (xIdx = 2; xIdx < width - 2; xIdx += 2, rgb_buffer += 6, bayer_pixel += 2) { + // GRGR line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r G r g + // line_step g b g b + rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[2] = avg(bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g R g + // line_step g b g b + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg4( + bayer_pixel[0], bayer_pixel[2], bayer_pixel[bayer_line_step + 1], + bayer_pixel[1 - bayer_line_step]); + rgb_buffer[5] = avg4( + bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2], + bayer_pixel[-bayer_line_step], bayer_pixel[-bayer_line_step + 2]); + + // BGBG line + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g B g b + rgb_buffer[rgb_line_step] = avg(bayer_pixel[-1], bayer_pixel[1]); + rgb_buffer[rgb_line_step + 1] = + avg3(bayer_pixel[0], bayer_pixel[bayer_line_step - 1], bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 2 + // -1 g b g b + // 0 r g r g + // line_step g b G b + // rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + rgb_buffer[rgb_line_step + 5] = + avg(bayer_pixel[bayer_line_step], bayer_pixel[bayer_line_step + 2]); + } + + // last two pixel values for first two lines + // GRGR line + // Bayer -1 0 1 + // -1 g b g + // 0 r G r + // line_step g b g + rgb_buffer[rgb_line_step] = rgb_buffer[0] = avg(bayer_pixel[1], bayer_pixel[-1]); + rgb_buffer[1] = bayer_pixel[0]; + rgb_buffer[5] = rgb_buffer[2] = avg(bayer_pixel[bayer_line_step], bayer_pixel[-bayer_line_step]); + + // Bayer -1 0 1 + // -1 g b g + // 0 r g R + // line_step g b g + rgb_buffer[rgb_line_step + 3] = rgb_buffer[3] = bayer_pixel[1]; + rgb_buffer[4] = avg3( + bayer_pixel[0], bayer_pixel[bayer_line_step + 1], + bayer_pixel[-bayer_line_step + 1]); + // rgb_pixel[5] = avg(bayer_pixel[line_step], bayer_pixel[-line_step] ); + + // BGBG line + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g B g + // rgb_pixel[rgb_line_step] = avg2(bayer_pixel[-1], bayer_pixel[1] ); + rgb_buffer[rgb_line_step + 1] = avg3( + bayer_pixel[0], bayer_pixel[bayer_line_step - 1], + bayer_pixel[bayer_line_step + 1]); + rgb_buffer[rgb_line_step + 5] = rgb_buffer[rgb_line_step + 2] = bayer_pixel[bayer_line_step]; + + // Bayer -1 0 1 + // -1 g b g + // 0 r g r + // line_step g b G + // rgb_pixel[rgb_line_step + 3] = bayer_pixel[1]; + rgb_buffer[rgb_line_step + 4] = bayer_pixel[bayer_line_step + 1]; + // rgb_pixel[rgb_line_step + 5] = bayer_pixel[line_step]; +} + +} // namespace image_proc diff --git a/src/image_pipeline/image_proc/src/image_proc.cpp b/src/image_pipeline/image_proc/src/image_proc.cpp new file mode 100644 index 000000000..639e66ea9 --- /dev/null +++ b/src/image_pipeline/image_proc/src/image_proc.cpp @@ -0,0 +1,65 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 + +int main(int argc, char * argv[]) +{ + // Force flush of the stdout buffer. + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + + rclcpp::init(argc, argv); + + rclcpp::executors::SingleThreadedExecutor exec; + const rclcpp::NodeOptions options; + + // Debayer component, image_raw -> image_mono, image_color + auto debayer_node = std::make_shared(options); + + // Rectify component, image_mono -> image_rect + auto rectify_mono_node = std::make_shared(options); + + // Rectify component, image_color -> image_rect_color + auto rectify_color_node = std::make_shared(options); + + exec.add_node(debayer_node); + exec.add_node(rectify_mono_node); + exec.add_node(rectify_color_node); + exec.spin(); + + rclcpp::shutdown(); + return 0; +} diff --git a/src/image_pipeline/image_proc/src/image_proc/processor.cpp b/src/image_pipeline/image_proc/src/image_proc/processor.cpp new file mode 100644 index 000000000..612ae0359 --- /dev/null +++ b/src/image_pipeline/image_proc/src/image_proc/processor.cpp @@ -0,0 +1,141 @@ +// Copyright 2008 Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 "image_geometry/pinhole_camera_model.hpp" +#include "rcutils/logging_macros.h" + +#include +#include +#include +#include + +namespace image_proc +{ + +bool Processor::process( + const sensor_msgs::msg::Image::ConstSharedPtr & raw_image, + const image_geometry::PinholeCameraModel & model, + ImageSet & output, int flags) const +{ + static const int MONO_EITHER = MONO | RECT; + static const int COLOR_EITHER = COLOR | RECT_COLOR; + if (!(flags & ALL)) { + return true; + } + + // Check if raw_image is color + const std::string & raw_encoding = raw_image->encoding; + int raw_type = CV_8UC1; + if (raw_encoding == sensor_msgs::image_encodings::BGR8 || + raw_encoding == sensor_msgs::image_encodings::RGB8) + { + raw_type = CV_8UC3; + output.color_encoding = raw_encoding; + } + // Construct cv::Mat pointing to raw_image data + const cv::Mat raw( + raw_image->height, raw_image->width, raw_type, + const_cast(&raw_image->data[0]), raw_image->step); + + /////////////////////////////////////////////////////// + // Construct colorized (unrectified) images from raw // + /////////////////////////////////////////////////////// + + // Bayer case + if (raw_encoding.find("bayer") != std::string::npos) { + // Convert to color BGR + // TODO(unknown): Faster to convert directly to mono when color is not requested, + // but OpenCV doesn't support + int code = 0; + if (raw_encoding == sensor_msgs::image_encodings::BAYER_RGGB8) { + code = cv::COLOR_BayerBG2BGR; + } else if (raw_encoding == sensor_msgs::image_encodings::BAYER_BGGR8) { + code = cv::COLOR_BayerRG2BGR; + } else if (raw_encoding == sensor_msgs::image_encodings::BAYER_GBRG8) { + code = cv::COLOR_BayerGR2BGR; + } else if (raw_encoding == sensor_msgs::image_encodings::BAYER_GRBG8) { + code = cv::COLOR_BayerGB2BGR; + } else { + RCUTILS_LOG_ERROR("[image_proc] Unsupported encoding '%s'", raw_encoding.c_str()); + return false; + } + cv::cvtColor(raw, output.color, code); + output.color_encoding = sensor_msgs::image_encodings::BGR8; + + if (flags & MONO_EITHER) { + cv::cvtColor(output.color, output.mono, cv::COLOR_BGR2GRAY); + } + } else if (raw_type == CV_8UC3) { // Color case + output.color = raw; + if (flags & MONO_EITHER) { + int code = + (raw_encoding == + sensor_msgs::image_encodings::BGR8) ? cv::COLOR_BGR2GRAY : cv::COLOR_RGB2GRAY; + cv::cvtColor(output.color, output.mono, code); + } + } else if (raw_encoding == sensor_msgs::image_encodings::MONO8) { // Mono case + output.mono = raw; + if (flags & COLOR_EITHER) { + output.color_encoding = sensor_msgs::image_encodings::MONO8; + output.color = raw; + } + } else if (raw_encoding == sensor_msgs::image_encodings::TYPE_8UC3) { + // 8UC3 does not specify a color encoding. Is it BGR, RGB, HSV, XYZ, LUV...? + RCUTILS_LOG_ERROR( + "[image_proc] Ambiguous encoding '8UC3'. " + "The camera driver should set the encoding to 'bgr8' or 'rgb8'."); + return false; + } else { + // Something else we can't handle + RCUTILS_LOG_ERROR("[image_proc] Unsupported encoding '%s'", raw_encoding.c_str()); + return false; + } + + ////////////////////////////////////////////////////// + // Construct rectified images from colorized images // + ////////////////////////////////////////////////////// + + // TODO(unknown): If no distortion, could just point to the colorized data. + // But copy is already way faster than remap. + if (flags & RECT) { + model.rectifyImage(output.mono, output.rect, interpolation_); + } + if (flags & RECT_COLOR) { + model.rectifyImage(output.color, output.rect_color, interpolation_); + } + + return true; +} + +} // namespace image_proc diff --git a/src/image_pipeline/image_proc/src/rectify.cpp b/src/image_pipeline/image_proc/src/rectify.cpp new file mode 100644 index 000000000..faeb738bb --- /dev/null +++ b/src/image_pipeline/image_proc/src/rectify.cpp @@ -0,0 +1,164 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 "cv_bridge/cv_bridge.hpp" +#include "tracetools_image_pipeline/tracetools.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace image_proc +{ + +RectifyNode::RectifyNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("RectifyNode", options) +{ + auto qos_profile = getTopicQosProfile(this, "image"); + queue_size_ = this->declare_parameter("queue_size", 5); + interpolation = this->declare_parameter("interpolation", 1); + pub_rect_ = image_transport::create_publisher(this, "image_rect"); + subscribeToCamera(qos_profile); +} + +// Handles (un)subscribing when clients (un)subscribe +void RectifyNode::subscribeToCamera(const rmw_qos_profile_t & qos_profile) +{ + std::lock_guard lock(connect_mutex_); + + /* + * SubscriberStatusCallback not yet implemented + * + if (pub_rect_.getNumSubscribers() == 0) + sub_camera_.shutdown(); + else if (!sub_camera_) + { + */ + sub_camera_ = image_transport::create_camera_subscription( + this, "image", std::bind( + &RectifyNode::imageCb, + this, std::placeholders::_1, std::placeholders::_2), "raw", qos_profile); + // } +} + +void RectifyNode::imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr & image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info_msg) +{ + TRACEPOINT( + image_proc_rectify_init, + static_cast(this), + static_cast(&(*image_msg)), + static_cast(&(*info_msg))); + + if (pub_rect_.getNumSubscribers() < 1) { + TRACEPOINT( + image_proc_rectify_fini, + static_cast(this), + static_cast(&(*image_msg)), + static_cast(&(*info_msg))); + + return; + } + + // Verify camera is actually calibrated + if (info_msg->k[0] == 0.0) { + RCLCPP_ERROR( + this->get_logger(), "Rectified topic '%s' requested but camera publishing '%s' " + "is uncalibrated", pub_rect_.getTopic().c_str(), sub_camera_.getInfoTopic().c_str()); + TRACEPOINT( + image_proc_rectify_fini, + static_cast(this), + static_cast(&(*image_msg)), + static_cast(&(*info_msg))); + return; + } + + // If zero distortion, just pass the message along + bool zero_distortion = true; + + for (size_t i = 0; i < info_msg->d.size(); ++i) { + if (info_msg->d[i] != 0.0) { + zero_distortion = false; + break; + } + } + + // This will be true if D is empty/zero sized + if (zero_distortion) { + pub_rect_.publish(image_msg); + TRACEPOINT( + image_proc_rectify_fini, + static_cast(this), + static_cast(&(*image_msg)), + static_cast(&(*info_msg))); + return; + } + + // Update the camera model + model_.fromCameraInfo(info_msg); + + // Create cv::Mat views onto both buffers + const cv::Mat image = cv_bridge::toCvShare(image_msg)->image; + cv::Mat rect; + + // Rectify and publish + model_.rectifyImage(image, rect, interpolation); + + // Allocate new rectified image message + sensor_msgs::msg::Image::SharedPtr rect_msg = + cv_bridge::CvImage(image_msg->header, image_msg->encoding, rect).toImageMsg(); + pub_rect_.publish(rect_msg); + + TRACEPOINT( + image_proc_rectify_fini, + static_cast(this), + static_cast(&(*image_msg)), + static_cast(&(*info_msg))); +} + +} // namespace image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +// This acts as a sort of entry point, allowing the component to be discoverable when its library +// is being loaded into a running process. +RCLCPP_COMPONENTS_REGISTER_NODE(image_proc::RectifyNode) diff --git a/src/image_pipeline/image_proc/src/resize.cpp b/src/image_pipeline/image_proc/src/resize.cpp new file mode 100644 index 000000000..638526f0d --- /dev/null +++ b/src/image_pipeline/image_proc/src/resize.cpp @@ -0,0 +1,165 @@ +// Copyright 2017, 2019 Kentaro Wada, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 "cv_bridge/cv_bridge.hpp" +#include "tracetools_image_pipeline/tracetools.h" + +#include +#include + +#include +#include +#include +#include +#include + +namespace image_proc +{ + +ResizeNode::ResizeNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("ResizeNode", options) +{ + auto qos_profile = getTopicQosProfile(this, "image/image_raw"); + // Create image pub + pub_image_ = image_transport::create_camera_publisher(this, "resize/image_raw", qos_profile); + // Create image sub + sub_image_ = image_transport::create_camera_subscription( + this, "image/image_raw", + std::bind( + &ResizeNode::imageCb, this, + std::placeholders::_1, + std::placeholders::_2), "raw", qos_profile); + + interpolation_ = this->declare_parameter("interpolation", 1); + use_scale_ = this->declare_parameter("use_scale", true); + scale_height_ = this->declare_parameter("scale_height", 1.0); + scale_width_ = this->declare_parameter("scale_width", 1.0); + height_ = this->declare_parameter("height", -1); + width_ = this->declare_parameter("width", -1); +} + +void ResizeNode::imageCb( + sensor_msgs::msg::Image::ConstSharedPtr image_msg, + sensor_msgs::msg::CameraInfo::ConstSharedPtr info_msg) +{ + // getNumSubscribers has a bug/doesn't work + // Eventually revisit and figure out how to make this work + // if (pub_image_.getNumSubscribers() < 1) { + // return; + //} + + TRACEPOINT( + image_proc_resize_init, + static_cast(this), + static_cast(&(*image_msg)), + static_cast(&(*info_msg))); + + cv_bridge::CvImageConstPtr cv_ptr; + + try { + cv_ptr = cv_bridge::toCvShare(image_msg); + } catch (cv_bridge::Exception & e) { + TRACEPOINT( + image_proc_resize_fini, + static_cast(this), + static_cast(&(*image_msg)), + static_cast(&(*info_msg))); + RCLCPP_ERROR(this->get_logger(), "cv_bridge exception: %s", e.what()); + return; + } + + if (use_scale_) { + cv::resize( + cv_ptr->image, scaled_cv_.image, cv::Size(0, 0), scale_width_, + scale_height_, interpolation_); + } else { + int height = height_ == -1 ? image_msg->height : height_; + int width = width_ == -1 ? image_msg->width : width_; + cv::resize(cv_ptr->image, scaled_cv_.image, cv::Size(width, height), 0, 0, interpolation_); + } + + sensor_msgs::msg::CameraInfo::SharedPtr dst_info_msg = + std::make_shared(*info_msg); + + double scale_y; + double scale_x; + + if (use_scale_) { + scale_y = scale_height_; + scale_x = scale_width_; + dst_info_msg->height = static_cast(info_msg->height * scale_height_); + dst_info_msg->width = static_cast(info_msg->width * scale_width_); + } else { + scale_y = static_cast(height_) / info_msg->height; + scale_x = static_cast(width_) / info_msg->width; + dst_info_msg->height = height_; + dst_info_msg->width = width_; + } + + dst_info_msg->k[0] = dst_info_msg->k[0] * scale_x; // fx + dst_info_msg->k[2] = dst_info_msg->k[2] * scale_x; // cx + dst_info_msg->k[4] = dst_info_msg->k[4] * scale_y; // fy + dst_info_msg->k[5] = dst_info_msg->k[5] * scale_y; // cy + + dst_info_msg->p[0] = dst_info_msg->p[0] * scale_x; // fx + dst_info_msg->p[2] = dst_info_msg->p[2] * scale_x; // cx + dst_info_msg->p[3] = dst_info_msg->p[3] * scale_x; // T + dst_info_msg->p[5] = dst_info_msg->p[5] * scale_y; // fy + dst_info_msg->p[6] = dst_info_msg->p[6] * scale_y; // cy + + dst_info_msg->roi.x_offset = static_cast(dst_info_msg->roi.x_offset * scale_x); + dst_info_msg->roi.y_offset = static_cast(dst_info_msg->roi.y_offset * scale_y); + dst_info_msg->roi.width = static_cast(dst_info_msg->roi.width * scale_x); + dst_info_msg->roi.height = static_cast(dst_info_msg->roi.height * scale_y); + + scaled_cv_.header = image_msg->header; + scaled_cv_.encoding = image_msg->encoding; + pub_image_.publish(*scaled_cv_.toImageMsg(), *dst_info_msg); + + TRACEPOINT( + image_proc_resize_fini, + static_cast(this), + static_cast(&(*image_msg)), + static_cast(&(*info_msg))); +} + +} // namespace image_proc + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +// This acts as a sort of entry point, allowing the component to be discoverable when its library +// is being loaded into a running process. +RCLCPP_COMPONENTS_REGISTER_NODE(image_proc::ResizeNode) diff --git a/src/image_pipeline/image_proc/test/CMakeLists.txt b/src/image_pipeline/image_proc/test/CMakeLists.txt new file mode 100644 index 000000000..52fa28569 --- /dev/null +++ b/src/image_pipeline/image_proc/test/CMakeLists.txt @@ -0,0 +1,5 @@ +find_package(rostest REQUIRED) +#catkin_add_gtest(image_proc_rostest rostest.cpp) +#target_link_libraries(image_proc_rostest ${catkin_LIBRARIES} ${Boost_LIBRARIES}) +add_rostest_gtest(image_proc_test_rectify test_rectify.xml test_rectify.cpp) +target_link_libraries(image_proc_test_rectify ${catkin_LIBRARIES}) diff --git a/src/image_pipeline/image_proc/test/rostest.cpp b/src/image_pipeline/image_proc/test/rostest.cpp new file mode 100644 index 000000000..32cb9214d --- /dev/null +++ b/src/image_pipeline/image_proc/test/rostest.cpp @@ -0,0 +1,146 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Steve Macenski, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 "ros/ros.h>" +#include "gtest/gtest.h" +#include "cv_bridge/cv_bridge.hpp" + +#include +#include +#include + +#include + +class ImageProcTest + : public testing::Test +{ +protected: + virtual void SetUp() + { + ros::NodeHandle local_nh("~"); + + // Determine topic names + std::string camera_ns = nh.resolveName("camera") + "/"; + + if (camera_ns == "/camera") { + throw "Must remap 'camera' to the camera namespace."; + } + + topic_raw = camera_ns + "image_raw"; + topic_mono = camera_ns + "image_mono"; + topic_rect = camera_ns + "image_rect"; + topic_color = camera_ns + "image_color"; + topic_rect_color = camera_ns + "image_rect_color"; + + // Load raw image and cam info + /// @todo Make these cmd-line args instead? + std::string raw_image_file, cam_info_file; + + if (!local_nh.getParam("raw_image_file", raw_image_file)) { + throw "Must set parameter ~raw_image_file."; + } + + if (!local_nh.getParam("camera_info_file", cam_info_file)) { + throw "Must set parameter ~camera_info_file."; + } + + /// @todo Test variety of encodings for raw image (bayer, mono, color) + cv::Mat img = cv::imread(raw_image_file, 0); + raw_image = cv_bridge::CvImage(std_msgs::Header(), "mono8", img).toImageMsg(); + std::string cam_name; + + if (!camera_calibration_parsers::readCalibration(cam_info_file, cam_name, cam_info)) { + throw "Failed to read camera info file."; + } + + // Create raw camera publisher + image_transport::ImageTransport it(nh); + cam_pub = it.advertiseCamera(topic_raw, 1); + + // Wait for image_proc to be operational + ros::master::V_TopicInfo topics; + while (true) { + if (ros::master::getTopics(topics)) { + BOOST_FOREACH(ros::master::TopicInfo & topic, topics) { + if (topic.name == topic_rect_color) { + return; + } + } + } + + ros::Duration(0.5).sleep(); + } + } + + ros::NodeHandle nh; + std::string topic_raw; + std::string topic_mono; + std::string topic_rect; + std::string topic_color; + std::string topic_rect_color; + + sensor_msgs::ImagePtr raw_image; + sensor_msgs::CameraInfo cam_info; + image_transport::CameraPublisher cam_pub; + + void publishRaw() + { + cam_pub.publish(*raw_image, cam_info); + } +}; + +void callback(const sensor_msgs::ImageConstPtr & msg) +{ + ROS_FATAL("Got an image"); + ros::shutdown(); +} + +TEST_F(ImageProcTest, monoSubscription) +{ + ROS_INFO("In test. Subscribing."); + ros::Subscriber mono_sub = nh.subscribe(topic_mono, 1, callback); + ROS_INFO("Publishing."); + publishRaw(); + + ROS_INFO("Spinning."); + ros::spin(); + ROS_INFO("Done."); +} + +int main(int argc, char ** argv) +{ + ros::init(argc, argv, "imageproc_rostest"); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/image_pipeline/image_proc/test/test_bayer.xml b/src/image_pipeline/image_proc/test/test_bayer.xml new file mode 100644 index 000000000..a6d2a1a34 --- /dev/null +++ b/src/image_pipeline/image_proc/test/test_bayer.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/image_pipeline/image_proc/test/test_rectify.cpp b/src/image_pipeline/image_proc/test/test_rectify.cpp new file mode 100644 index 000000000..2566f4409 --- /dev/null +++ b/src/image_pipeline/image_proc/test/test_rectify.cpp @@ -0,0 +1,265 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Steve Macenski, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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 "ros/ros.h" +#include "gtest/gtest.h" +#include "cv_bridge/cv_bridge.hpp" + +#include +#include +#include +#include +#include + +class ImageProcRectifyTest + : public testing::Test +{ +protected: + virtual void SetUp() + { + // Determine topic names + std::string camera_ns = nh_.resolveName("camera") + "/"; + + if (camera_ns == "/camera") { + throw "Must remap 'camera' to the camera namespace."; + } + + topic_raw_ = camera_ns + "image_raw"; + topic_mono_ = camera_ns + "image_mono"; + topic_rect_ = camera_ns + "image_rect"; + topic_color_ = camera_ns + "image_color"; + topic_rect_color_ = camera_ns + "image_rect_color"; + + // Taken from vision_opencv/image_geometry/test/utest.cpp + double D[] = + { + -0.363528858080088, 0.16117037733986861, + -8.1109585007538829e-05, -0.00044776712298447841, 0.0 + }; + + double K[] = + { + 430.15433020105519, 0.0, 311.71339830549732, + 0.0, 430.60920415473657, 221.06824942698509, + 0.0, 0.0, 1.0 + }; + + double R[] = + { + 0.99806560714807102, 0.0068562422224214027, 0.061790256276695904, + -0.0067522959054715113, 0.99997541519165112, -0.0018909025066874664, + -0.061801701660692349, 0.0014700186639396652, 0.99808736527268516 + }; + + double P[] = + { + 295.53402059708782, 0.0, 285.55760765075684, 0.0, + 0.0, 295.53402059708782, 223.29617881774902, 0.0, + 0.0, 0.0, 1.0, 0.0 + }; + + cam_info_.header.frame_id = "tf_frame"; + cam_info_.height = 480; + cam_info_.width = 640; + // No ROI + cam_info_.D.resize(5); + std::copy(D, D + 5, cam_info_.D.begin()); + std::copy(K, K + 9, cam_info_.K.begin()); + std::copy(R, R + 9, cam_info_.R.begin()); + std::copy(P, P + 12, cam_info_.P.begin()); + cam_info_.distortion_model = sensor_msgs::distortion_models::PLUMB_BOB; + + distorted_image_ = cv::Mat(cv::Size(cam_info_.width, cam_info_.height), CV_8UC3); + // draw a grid + const cv::Scalar color = cv::Scalar(255, 255, 255); + // draw the lines thick so the proportion of error due to + // interpolation is reduced + const int thickness = 7; + const int type = 8; + for (size_t y = 0; y <= cam_info_.height; y += cam_info_.height / 10) { + cv::line( + distorted_image_, cv::Point(0, y), cv::Point(cam_info_.width, y), + color, type, thickness); + } + + for (size_t x = 0; x <= cam_info_.width; x += cam_info_.width / 10) { + // draw the lines thick so the prorportion of interpolation error is reduced + cv::line( + distorted_image_, cv::Point(x, 0), cv::Point(x, cam_info_.height), + color, type, thickness); + } + + raw_image_ = cv_bridge::CvImage( + std_msgs::Header(), "bgr8", distorted_image_).toImageMsg(); + + // Create raw camera subscriber and publisher + image_transport::ImageTransport it(nh_); + cam_pub_ = it.advertiseCamera(topic_raw_, 1); + } + + ros::NodeHandle nh_; + std::string topic_raw_; + std::string topic_mono_; + std::string topic_rect_; + std::string topic_color_; + std::string topic_rect_color_; + + cv::Mat distorted_image_; + sensor_msgs::ImagePtr raw_image_; + bool has_new_image_; + cv::Mat received_image_; + sensor_msgs::CameraInfo cam_info_; + image_transport::CameraPublisher cam_pub_; + image_transport::Subscriber cam_sub_; + +public: + void imageCallback(const sensor_msgs::ImageConstPtr & msg) + { + cv_bridge::CvImageConstPtr cv_ptr; + + try { + cv_ptr = cv_bridge::toCvShare(msg, sensor_msgs::image_encodings::BGR8); + } catch (cv_bridge::Exception & e) { + ROS_FATAL("cv_bridge exception: %s", e.what()); + return; + } + + received_image_ = cv_ptr->image.clone(); + has_new_image_ = true; + } + + void publishRaw() + { + has_new_image_ = false; + cam_pub_.publish(*raw_image_, cam_info_); + } +}; + +TEST_F(ImageProcRectifyTest, rectifyTest) +{ + ROS_INFO("In test. Subscribing."); + image_transport::ImageTransport it(nh_); + cam_sub_ = it.subscribe( + topic_rect_, 1, &ImageProcRectifyTest::imageCallback, + dynamic_cast(this)); + + // Wait for image_proc to be operational + bool wait_for_topic = true; + + while (wait_for_topic) { + // @todo this fails without the additional 0.5 second sleep after the + // publisher comes online, which means on a slower or more heavily + // loaded system it may take longer than 0.5 seconds, and the test + // would hang until the timeout is reached and fail. + if (cam_sub_.getNumPublishers() > 0) { + wait_for_topic = false; + } + + ros::Duration(0.5).sleep(); + } + + // All the tests are the same as from + // vision_opencv/image_geometry/test/utest.cpp + // default cam info + + // Just making this number up, maybe ought to be larger + // since a completely different image would be on the order of + // width * height * 255 = 78e6 + const double diff_threshold = 10000.0; + double error; + + // use original cam_info + publishRaw(); + while (!has_new_image_) { + ros::spinOnce(); + ros::Duration(0.5).sleep(); + } + + // Test that rectified image is sufficiently different + // using default distortion + error = cv::norm(distorted_image_, received_image_, cv::NORM_L1); + // Just making this number up, maybe ought to be larger + EXPECT_GT(error, diff_threshold); + + // Test that rectified image is sufficiently different + // using default distortion but with first element zeroed + // out. + sensor_msgs::CameraInfo cam_info_orig = cam_info_; + cam_info_.D[0] = 0.0; + publishRaw(); + + while (!has_new_image_) { + ros::spinOnce(); + ros::Duration(0.5).sleep(); + } + + error = cv::norm(distorted_image_, received_image_, cv::NORM_L1); + EXPECT_GT(error, diff_threshold); + + // Test that rectified image is the same using zero distortion + cam_info_.D.assign(cam_info_.D.size(), 0); + publishRaw(); + + while (!has_new_image_) { + ros::spinOnce(); + ros::Duration(0.5).sleep(); + } + + error = cv::norm(distorted_image_, received_image_, cv::NORM_L1); + EXPECT_EQ(error, 0); + + // Test that rectified image is the same using empty distortion + cam_info_.D.clear(); + publishRaw(); + + while (!has_new_image_) { + ros::spinOnce(); + ros::Duration(0.5).sleep(); + } + + error = cv::norm(distorted_image_, received_image_, cv::NORM_L1); + + EXPECT_EQ(error, 0); + + // restore the original cam_info for other tests added in the future + cam_info_ = cam_info_orig; +} + +int main(int argc, char ** argv) +{ + ros::init(argc, argv, "image_proc_test_rectify"); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/image_pipeline/image_proc/test/test_rectify.xml b/src/image_pipeline/image_proc/test/test_rectify.xml new file mode 100644 index 000000000..f1a451c32 --- /dev/null +++ b/src/image_pipeline/image_proc/test/test_rectify.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/image_pipeline/image_rotate/CHANGELOG.rst b/src/image_pipeline/image_rotate/CHANGELOG.rst new file mode 100644 index 000000000..5dd2f4bd2 --- /dev/null +++ b/src/image_pipeline/image_rotate/CHANGELOG.rst @@ -0,0 +1,138 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package image_rotate +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +4.0.0 (2022-12-24) +------------------ +* [backport iron] Removed cfg files related with ROS 1 parameters (`#911 `_) (`#914 `_) + Removed cfg files related with ROS 1 parameters. Backport + https://github.com/ros-perception/image_pipeline/pull/911 +* load image_rotate::ImageRotateNode as component (`#856 `_) + This is a fixed version of `#820 `_ - targeting iron +* add myself as a maintainer (`#846 `_) +* Contributors: Alejandro Hernández Cordero, Michael Ferguson + +3.0.1 (2022-12-04) +------------------ +* Replace deprecated headers + Fixing compiler warnings. +* Contributors: Jacob Perron + +3.0.0 (2022-04-29) +------------------ +* Cleanup the image_rotate package. +* Replace deprecated geometry2 headers +* revert a293252 +* Replace deprecated geometry2 headers +* Add maintainer (`#667 `_) +* Contributors: Chris Lalancette, Jacob Perron, Patrick Musau + +2.2.1 (2020-08-27) +------------------ +* remove email blasts from steve macenski (`#596 `_) +* [Foxy] Use ament_auto Macros (`#573 `_) +* Contributors: Joshua Whitley, Steve Macenski + +2.2.0 (2020-07-27) +------------------ +* Replacing deprecated header includes with new HPP versions. (`#566 `_) +* Use newer 'add_on_set_parameters_callback' API (`#562 `_) + The old API was deprecated in Foxy and since removed in https://github.com/ros2/rclcpp/pull/1199. +* Contributors: Jacob Perron, Joshua Whitley + +* Initial ROS2 commit. +* Contributors: Michael Carroll + +1.12.23 (2018-05-10) +-------------------- + +1.12.22 (2017-12-08) +-------------------- + +1.12.21 (2017-11-05) +-------------------- +* [image_rotate] Added TF timeout so that transforms only need to be newer than last frame. (`#293 `_) +* Contributors: mhosmar-cpr + +1.12.20 (2017-04-30) +-------------------- +* Fix CMake warnings about Eigen. +* address gcc6 build error + With gcc6, compiling fails with `stdlib.h: No such file or directory`, + as including '-isystem /usr/include' breaks with gcc6, cf., + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70129. + This commit addresses this issue for this package in the same way + it was addressed in various other ROS packages. A list of related + commits and pull requests is at: + https://github.com/ros/rosdistro/issues/12783 + Signed-off-by: Lukas Bulwahn +* Contributors: Lukas Bulwahn, Vincent Rabaud + +1.12.19 (2016-07-24) +-------------------- +* Fix frames if it is empty to rotate image +* Contributors: Kentaro Wada + +1.12.18 (2016-07-12) +-------------------- + +1.12.17 (2016-07-11) +-------------------- + +1.12.16 (2016-03-19) +-------------------- +* clean OpenCV dependency in package.xml +* Contributors: Vincent Rabaud + +1.12.15 (2016-01-17) +-------------------- + +1.12.14 (2015-07-22) +-------------------- + +1.12.13 (2015-04-06) +-------------------- + +1.12.12 (2014-12-31) +-------------------- + +1.12.11 (2014-10-26) +-------------------- + +1.12.10 (2014-09-28) +-------------------- + +1.12.9 (2014-09-21) +------------------- + +1.12.8 (2014-08-19) +------------------- + +1.12.6 (2014-07-27) +------------------- + +1.12.4 (2014-04-28) +------------------- + +1.12.3 (2014-04-12) +------------------- + +1.12.2 (2014-04-08) +------------------- +* use NODELET_** macros instead of ROS_** macros +* use getNodeHandle rather than getPrivateNodeHandle +* add executable to load image_rotate/image_rotate nodelet. + add xml file to export nodelet definition. + Conflicts: + image_rotate/package.xml +* make image_rotate nodelet class + Conflicts: + image_rotate/CMakeLists.txt + image_rotate/package.xml + image_rotate/src/nodelet/image_rotate_nodelet.cpp +* move image_rotate.cpp to nodelet directory according to the directory convenstion of image_pipeline +* Contributors: Ryohei Ueda + +1.12.1 (2014-04-06) +------------------- +* replace tf usage by tf2 usage diff --git a/src/image_pipeline/image_rotate/CMakeLists.txt b/src/image_pipeline/image_rotate/CMakeLists.txt new file mode 100644 index 000000000..49c3b94fc --- /dev/null +++ b/src/image_pipeline/image_rotate/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.5) +project(image_rotate) + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake_auto REQUIRED) +ament_auto_find_build_dependencies() + +find_package(OpenCV REQUIRED core imgproc) + +ament_auto_add_library(${PROJECT_NAME} SHARED src/image_rotate_node.cpp) +target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBRARIES}) +rclcpp_components_register_nodes(${PROJECT_NAME} "${PROJECT_NAME}::ImageRotateNode") +set(node_plugins "${node_plugins}${PROJECT_NAME}::ImageRotateNode;$\n") + +ament_auto_add_executable(image_rotate_bin src/image_rotate.cpp) +set_target_properties(image_rotate_bin PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) +target_link_libraries(image_rotate_bin ${OpenCV_LIBRARIES} ${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() +endif() + +ament_auto_package(INSTALL_TO_SHARE launch) diff --git a/src/image_pipeline/image_rotate/include/image_rotate/image_rotate_node.hpp b/src/image_pipeline/image_rotate/include/image_rotate/image_rotate_node.hpp new file mode 100644 index 000000000..8482a1c72 --- /dev/null +++ b/src/image_pipeline/image_rotate/include/image_rotate/image_rotate_node.hpp @@ -0,0 +1,112 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef IMAGE_ROTATE__IMAGE_ROTATE_NODE_HPP_ +#define IMAGE_ROTATE__IMAGE_ROTATE_NODE_HPP_ + +#include +#include + +#include "tf2_ros/buffer.h" +#include "tf2_ros/transform_listener.h" +#include "tf2_ros/transform_broadcaster.h" + +#include +#include +#include +#include +#include + +#include "image_rotate/visibility.h" + +namespace image_rotate +{ + +struct ImageRotateConfig +{ + std::string target_frame_id; + double target_x; + double target_y; + double target_z; + std::string source_frame_id; + double source_x; + double source_y; + double source_z; + std::string output_frame_id; + std::string input_frame_id; + bool use_camera_info; + double max_angular_rate; + double output_image_size; +}; + +class ImageRotateNode : public rclcpp::Node +{ +public: + IMAGE_ROTATE_PUBLIC ImageRotateNode(const rclcpp::NodeOptions & options); + +private: + const std::string frameWithDefault(const std::string & frame, const std::string & image_frame); + void imageCallbackWithInfo( + const sensor_msgs::msg::Image::ConstSharedPtr & msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & cam_info); + void imageCallback(const sensor_msgs::msg::Image::ConstSharedPtr & msg); + void do_work( + const sensor_msgs::msg::Image::ConstSharedPtr & msg, + const std::string input_frame_from_msg); + void subscribe(); + void unsubscribe(); + void connectCb(); + void disconnectCb(); + void onInit(); + + rclcpp::Node::OnSetParametersCallbackHandle::SharedPtr on_set_parameters_callback_handle_; + + std::shared_ptr tf_buffer_; + std::shared_ptr tf_sub_; + std::shared_ptr tf_pub_; + + image_rotate::ImageRotateConfig config_; + + image_transport::Publisher img_pub_; + image_transport::Subscriber img_sub_; + image_transport::CameraSubscriber cam_sub_; + + geometry_msgs::msg::Vector3Stamped target_vector_; + geometry_msgs::msg::Vector3Stamped source_vector_; + + int subscriber_count_; + double angle_; + tf2::TimePoint prev_stamp_; +}; +} // namespace image_rotate + +#endif // IMAGE_ROTATE__IMAGE_ROTATE_NODE_HPP_ diff --git a/src/image_pipeline/image_rotate/include/image_rotate/visibility.h b/src/image_pipeline/image_rotate/include/image_rotate/visibility.h new file mode 100644 index 000000000..643108bb1 --- /dev/null +++ b/src/image_pipeline/image_rotate/include/image_rotate/visibility.h @@ -0,0 +1,75 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef IMAGE_ROTATE__VISIBILITY_H_ +#define IMAGE_ROTATE__VISIBILITY_H_ + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + + #ifdef __GNUC__ + #define IMAGE_ROTATE_EXPORT __attribute__ ((dllexport)) + #define IMAGE_ROTATE_IMPORT __attribute__ ((dllimport)) + #else + #define IMAGE_ROTATE_EXPORT __declspec(dllexport) + #define IMAGE_ROTATE_IMPORT __declspec(dllimport) + #endif + + #ifdef IMAGE_ROTATE_DLL + #define IMAGE_ROTATE_PUBLIC IMAGE_ROTATE_EXPORT + #else + #define IMAGE_ROTATE_PUBLIC IMAGE_ROTATE_IMPORT + #endif + + #define IMAGE_ROTATE_PUBLIC_TYPE IMAGE_ROTATE_PUBLIC + + #define IMAGE_ROTATE_LOCAL + +#else + + #define IMAGE_ROTATE_EXPORT __attribute__ ((visibility("default"))) + #define IMAGE_ROTATE_IMPORT + + #if __GNUC__ >= 4 + #define IMAGE_ROTATE_PUBLIC __attribute__ ((visibility("default"))) + #define IMAGE_ROTATE_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define IMAGE_ROTATE_PUBLIC + #define IMAGE_ROTATE_LOCAL + #endif + + #define IMAGE_ROTATE_PUBLIC_TYPE +#endif + +#endif // IMAGE_ROTATE__VISIBILITY_H_ diff --git a/src/image_pipeline/image_rotate/launch/image_rotate.launch.py b/src/image_pipeline/image_rotate/launch/image_rotate.launch.py new file mode 100644 index 000000000..7345a5ea8 --- /dev/null +++ b/src/image_pipeline/image_rotate/launch/image_rotate.launch.py @@ -0,0 +1,44 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +from launch import LaunchDescription +import launch_ros.actions + + +def generate_launch_description(): + return LaunchDescription([ + launch_ros.actions.Node( + package='image_rotate', node_executable='image_rotate', output='screen', + remappings=[('image', '/camera/color/image_raw'), + ('camera_info', '/camera/color/camera_info'), + ('rotated/image', '/camera/color/image_raw_rotated')]), + ]) diff --git a/src/image_pipeline/image_rotate/mainpage.dox b/src/image_pipeline/image_rotate/mainpage.dox new file mode 100644 index 000000000..43b15c925 --- /dev/null +++ b/src/image_pipeline/image_rotate/mainpage.dox @@ -0,0 +1,26 @@ +/** +\mainpage +\htmlinclude manifest.html + +\b image_rotate is ... + + + + +\section codeapi Code API + + + + +*/ diff --git a/src/image_pipeline/image_rotate/package.xml b/src/image_pipeline/image_rotate/package.xml new file mode 100644 index 000000000..192c440df --- /dev/null +++ b/src/image_pipeline/image_rotate/package.xml @@ -0,0 +1,60 @@ + + + + image_rotate + 4.0.0 + +

+ Contains a node that rotates an image stream in a way that minimizes + the angle between a vector in some arbitrary frame and a vector in the + camera frame. The frame of the outgoing image is published by the node. +

+

+ This node is intended to allow camera images to be visualized in an + orientation that is more intuitive than the hardware-constrained + orientation of the physical camera. This is particularly helpful, for + example, to show images from the PR2's forearm cameras with a + consistent up direction, despite the fact that the forearms need to + rotate in arbitrary ways during manipulation. +

+

+ It is not recommended to use the output from this node for further + computation, as it interpolates the source image, introduces black + borders, and does not output a camera_info. +

+
+ + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/image_rotate/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Blaise Gassend + + ament_cmake_auto + + class_loader + + cv_bridge + geometry_msgs + image_transport + libopencv-dev + rcl_interfaces + rclcpp + rclcpp_components + sensor_msgs + tf2 + tf2_geometry_msgs + tf2_ros + + ament_lint_auto + ament_lint_common + + + ament_cmake + +
diff --git a/src/image_pipeline/image_rotate/src/image_rotate.cpp b/src/image_pipeline/image_rotate/src/image_rotate.cpp new file mode 100644 index 000000000..909d34ef8 --- /dev/null +++ b/src/image_pipeline/image_rotate/src/image_rotate.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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 "image_rotate/image_rotate_node.hpp" +#include + +int main(int argc, char ** argv) +{ + // Force flush of the stdout buffer. + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + + if (argc <= 1) { + RCUTILS_LOG_WARN( + "Topic 'image' has not been remapped! Typical command-line usage:\n" + "\t$ ros2 run image_rotate image_rotate image:="); + return 1; + } + + rclcpp::init(argc, argv); + rclcpp::NodeOptions options; + auto node = std::make_shared(options); + + rclcpp::spin(node); + rclcpp::shutdown(); + + return 0; +} diff --git a/src/image_pipeline/image_rotate/src/image_rotate_node.cpp b/src/image_pipeline/image_rotate/src/image_rotate_node.cpp new file mode 100644 index 000000000..1935a9ec9 --- /dev/null +++ b/src/image_pipeline/image_rotate/src/image_rotate_node.cpp @@ -0,0 +1,367 @@ +// Copyright (c) 2014, JSK Lab. +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +/******************************************************************** +* image_rotate_node.cpp +* this is a forked version of image_rotate. +* this image_rotate_node supports: +* 1) node +* 2) tf and tf2 +*********************************************************************/ + +#include "image_rotate/image_rotate_node.hpp" + +#include +#include +#include +#include +#include + +#include "cv_bridge/cv_bridge.hpp" +#include "tf2/LinearMath/Vector3.h" +#include "tf2/LinearMath/Quaternion.h" +#include "tf2_ros/buffer.h" +#include "tf2_ros/transform_listener.h" +#include "tf2_ros/transform_broadcaster.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace image_rotate +{ + +ImageRotateNode::ImageRotateNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("ImageRotateNode", options) +{ + config_.target_frame_id = this->declare_parameter("target_frame_id", std::string("")); + config_.target_x = this->declare_parameter("target_x", 0.0); + config_.target_y = this->declare_parameter("target_y", 0.0); + config_.target_z = this->declare_parameter("target_z", 1.0); + + config_.source_frame_id = this->declare_parameter("source_frame_id", std::string("")); + config_.source_x = this->declare_parameter("source_x", 0.0); + config_.source_y = this->declare_parameter("source_y", -1.0); + config_.source_z = this->declare_parameter("source_z", 0.0); + + config_.output_frame_id = this->declare_parameter("output_frame_id", std::string("")); + config_.input_frame_id = this->declare_parameter("input_frame_id", std::string("")); + config_.use_camera_info = this->declare_parameter("use_camera_info", true); + config_.max_angular_rate = this->declare_parameter( + "max_angular_rate", + 10.0); + config_.output_image_size = this->declare_parameter( + "output_image_size", + 2.0); + + auto reconfigureCallback = + [this](std::vector parameters) -> rcl_interfaces::msg::SetParametersResult + { + RCLCPP_INFO(get_logger(), "reconfigureCallback"); + + auto result = rcl_interfaces::msg::SetParametersResult(); + result.successful = true; + for (auto parameter : parameters) { + if (parameter.get_name() == "target_x") { + config_.target_x = parameter.as_double(); + RCLCPP_INFO(get_logger(), "Reset target_x as '%lf'", config_.target_x); + } else if (parameter.get_name() == "target_y") { + config_.target_y = parameter.as_double(); + RCLCPP_INFO(get_logger(), "Reset target_y as '%lf'", config_.target_y); + } else if (parameter.get_name() == "target_z") { + config_.target_z = parameter.as_double(); + RCLCPP_INFO(get_logger(), "Reset target_z as '%lf'", config_.target_z); + } else if (parameter.get_name() == "source_x") { + config_.source_x = parameter.as_double(); + RCLCPP_INFO(get_logger(), "Reset source_x as '%lf'", config_.source_x); + } else if (parameter.get_name() == "source_y") { + config_.source_y = parameter.as_double(); + RCLCPP_INFO(get_logger(), "Reset source_y as '%lf'", config_.source_y); + } else if (parameter.get_name() == "source_z") { + config_.source_z = parameter.as_double(); + RCLCPP_INFO(get_logger(), "Reset source_z as '%lf'", config_.source_z); + } + } + + target_vector_.vector.x = config_.target_x; + target_vector_.vector.y = config_.target_y; + target_vector_.vector.z = config_.target_z; + + source_vector_.vector.x = config_.source_x; + source_vector_.vector.y = config_.source_y; + source_vector_.vector.z = config_.source_z; + if (subscriber_count_) { // @todo: Could do this without an interruption at some point. + unsubscribe(); + subscribe(); + } + return result; + }; + on_set_parameters_callback_handle_ = this->add_on_set_parameters_callback(reconfigureCallback); + onInit(); +} + +const std::string ImageRotateNode::frameWithDefault( + const std::string & frame, + const std::string & image_frame) +{ + if (frame.empty()) { + return image_frame; + } + return frame; +} + +void ImageRotateNode::imageCallbackWithInfo( + const sensor_msgs::msg::Image::ConstSharedPtr & msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & cam_info) +{ + do_work(msg, cam_info->header.frame_id); +} + +void ImageRotateNode::imageCallback(const sensor_msgs::msg::Image::ConstSharedPtr & msg) +{ + do_work(msg, msg->header.frame_id); +} + +void ImageRotateNode::do_work( + const sensor_msgs::msg::Image::ConstSharedPtr & msg, + const std::string input_frame_from_msg) +{ + try { + std::string input_frame_id = frameWithDefault(config_.input_frame_id, input_frame_from_msg); + std::string target_frame_id = frameWithDefault(config_.target_frame_id, input_frame_from_msg); + std::string source_frame_id = frameWithDefault(config_.source_frame_id, input_frame_from_msg); + + // Transform the target vector into the image frame. + target_vector_.header.stamp = msg->header.stamp; + target_vector_.header.frame_id = target_frame_id; + geometry_msgs::msg::Vector3Stamped target_vector_transformed; + tf2::TimePoint tf2_time = tf2_ros::fromMsg(msg->header.stamp); + + geometry_msgs::msg::TransformStamped transform = tf_buffer_->lookupTransform( + target_frame_id, input_frame_id, tf2_time, tf2_time - prev_stamp_); + tf2::doTransform(target_vector_, target_vector_transformed, transform); + + // Transform the source vector into the image frame. + source_vector_.header.stamp = msg->header.stamp; + source_vector_.header.frame_id = source_frame_id; + geometry_msgs::msg::Vector3Stamped source_vector_transformed; + transform = tf_buffer_->lookupTransform( + source_frame_id, input_frame_id, tf2_time, tf2_time - prev_stamp_); + tf2::doTransform(source_vector_, source_vector_transformed, transform); + + // RCUTILS_LOG_INFO("target: %f %f %f", target_vector_.x, target_vector_.y, target_vector_.z); + // RCUTILS_LOG_INFO("target_transformed: %f %f %f", target_vector_transformed.x, " + // "target_vector_transformed.y, target_vector_transformed.z"); + // RCUTILS_LOG_INFO("source: %f %f %f", source_vector_.x, source_vector_.y, source_vector_.z); + // RCUTILS_LOG_INFO("source_transformed: %f %f %f", source_vector_transformed.x, " + // "source_vector_transformed.y, source_vector_transformed.z"); + + // Calculate the angle of the rotation. + double angle = angle_; + if ((target_vector_transformed.vector.x != 0 || target_vector_transformed.vector.y != 0) && + (source_vector_transformed.vector.x != 0 || source_vector_transformed.vector.y != 0)) + { + angle = atan2(target_vector_transformed.vector.y, target_vector_transformed.vector.x); + angle -= atan2(source_vector_transformed.vector.y, source_vector_transformed.vector.x); + } + + // Rate limit the rotation. + if (config_.max_angular_rate == 0) { + angle_ = angle; + } else { + double delta = fmod(angle - angle_, 2.0 * M_PI); + if (delta > M_PI) { + delta -= 2.0 * M_PI; + } else if (delta < -M_PI) { + delta += 2.0 * M_PI; + } + + double max_delta = config_.max_angular_rate * + (tf2_ros::timeToSec(msg->header.stamp) - tf2::timeToSec(prev_stamp_)); + if (delta > max_delta) { + delta = max_delta; + } else if (delta < -max_delta) { + delta = -max_delta; + } + + angle_ += delta; + } + angle_ = fmod(angle_, 2.0 * M_PI); + } catch (const tf2::TransformException & e) { + RCUTILS_LOG_ERROR("Transform error: %s", e.what()); + } + + // RCUTILS_LOG_INFO("angle: %f", 180 * angle_ / M_PI); + + // Publish the transform. + geometry_msgs::msg::TransformStamped transform; + transform.transform.translation.x = 0; + transform.transform.translation.y = 0; + transform.transform.translation.z = 0; + transform.transform.rotation = tf2::toMsg(tf2::Quaternion(tf2::Vector3(0.0, 0.0, 1.0), angle_)); + transform.header.frame_id = msg->header.frame_id; + transform.child_frame_id = frameWithDefault( + config_.output_frame_id, msg->header.frame_id + "_rotated"); + transform.header.stamp = msg->header.stamp; + + if (tf_pub_) { + tf_pub_->sendTransform(transform); + } + + // Transform the image. + try { + // Convert the image into something opencv can handle. + cv::Mat in_image = cv_bridge::toCvShare(msg, msg->encoding)->image; + + // Compute the output image size. + int max_dim = in_image.cols > in_image.rows ? in_image.cols : in_image.rows; + int min_dim = in_image.cols < in_image.rows ? in_image.cols : in_image.rows; + int noblack_dim = min_dim / sqrt(2); + int diag_dim = sqrt(in_image.cols * in_image.cols + in_image.rows * in_image.rows); + int out_size; + // diag_dim repeated to simplify limit case. + int candidates[] = {noblack_dim, min_dim, max_dim, diag_dim, diag_dim}; + int step = config_.output_image_size; + out_size = candidates[step] + (candidates[step + 1] - candidates[step]) * + (config_.output_image_size - step); + // RCUTILS_LOG_INFO("out_size: %d", out_size); + + // Compute the rotation matrix. + cv::Mat rot_matrix = cv::getRotationMatrix2D( + cv::Point2f(in_image.cols / 2.0, in_image.rows / 2.0), 180 * angle_ / M_PI, 1); + cv::Mat translation = rot_matrix.col(2); + rot_matrix.at(0, 2) += (out_size - in_image.cols) / 2.0; + rot_matrix.at(1, 2) += (out_size - in_image.rows) / 2.0; + + // Do the rotation + cv::Mat out_image; + cv::warpAffine(in_image, out_image, rot_matrix, cv::Size(out_size, out_size)); + + // Publish the image. + sensor_msgs::msg::Image::SharedPtr out_img = + cv_bridge::CvImage(msg->header, msg->encoding, out_image).toImageMsg(); + out_img->header.frame_id = transform.child_frame_id; + img_pub_.publish(out_img); + } catch (const cv::Exception & e) { + RCUTILS_LOG_ERROR( + "Image processing error: %s %s %s %i", + e.err.c_str(), e.func.c_str(), e.file.c_str(), e.line); + } + + prev_stamp_ = tf2_ros::fromMsg(msg->header.stamp); +} + +void ImageRotateNode::subscribe() +{ + RCUTILS_LOG_DEBUG("Subscribing to image topic."); + if (config_.use_camera_info && config_.input_frame_id.empty()) { + auto custom_qos = rmw_qos_profile_system_default; + custom_qos.depth = 3; + + cam_sub_ = image_transport::create_camera_subscription( + this, + "image", + std::bind( + &ImageRotateNode::imageCallbackWithInfo, this, + std::placeholders::_1, std::placeholders::_2), + "raw", + custom_qos); + } else { + auto custom_qos = rmw_qos_profile_system_default; + custom_qos.depth = 3; + img_sub_ = image_transport::create_subscription( + this, + "image", + std::bind(&ImageRotateNode::imageCallback, this, std::placeholders::_1), + "raw", + custom_qos); + } +} + +void ImageRotateNode::unsubscribe() +{ + RCUTILS_LOG_DEBUG("Unsubscribing from image topic."); + img_sub_.shutdown(); + cam_sub_.shutdown(); +} + +void ImageRotateNode::connectCb() +{ + if (subscriber_count_++ == 0) { + subscribe(); + } +} + +void ImageRotateNode::disconnectCb() +{ + subscriber_count_--; + if (subscriber_count_ == 0) { + unsubscribe(); + } +} + +void ImageRotateNode::onInit() +{ + subscriber_count_ = 0; + angle_ = 0; + prev_stamp_ = tf2::get_now(); + rclcpp::Clock::SharedPtr clock = this->get_clock(); + tf_buffer_ = std::make_shared(clock); + tf_sub_ = std::make_shared(*tf_buffer_); + // TODO(yechun1): Implement when SubscriberStatusCallback is available + // image_transport::SubscriberStatusCallback connect_cb = + // boost::bind(&ImageRotateNode::connectCb, this, _1); + // image_transport::SubscriberStatusCallback connect_cb = + // std::bind(&CropForemostNode::connectCb, this); + // image_transport::SubscriberStatusCallback disconnect_cb = + // boost::bind(&ImageRotateNode::disconnectCb, this, _1); + // img_pub_ = image_transport::ImageTransport(ros::NodeHandle(nh_, "rotated")).advertise( + // "image", 1, connect_cb, disconnect_cb); + connectCb(); + img_pub_ = image_transport::create_publisher(this, "rotated/image"); + tf_pub_ = std::make_shared(*this); +} +} // namespace image_rotate + +#include "rclcpp_components/register_node_macro.hpp" + +// Register the component with class_loader. +RCLCPP_COMPONENTS_REGISTER_NODE(image_rotate::ImageRotateNode) diff --git a/src/image_pipeline/install/.colcon_install_layout b/src/image_pipeline/install/.colcon_install_layout new file mode 100644 index 000000000..3aad5336a --- /dev/null +++ b/src/image_pipeline/install/.colcon_install_layout @@ -0,0 +1 @@ +isolated diff --git a/src/image_pipeline/install/COLCON_IGNORE b/src/image_pipeline/install/COLCON_IGNORE new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/_local_setup_util_ps1.py b/src/image_pipeline/install/_local_setup_util_ps1.py new file mode 100644 index 000000000..98348eebc --- /dev/null +++ b/src/image_pipeline/install/_local_setup_util_ps1.py @@ -0,0 +1,404 @@ +# Copyright 2016-2019 Dirk Thomas +# Licensed under the Apache License, Version 2.0 + +import argparse +from collections import OrderedDict +import os +from pathlib import Path +import sys + + +FORMAT_STR_COMMENT_LINE = '# {comment}' +FORMAT_STR_SET_ENV_VAR = 'Set-Item -Path "Env:{name}" -Value "{value}"' +FORMAT_STR_USE_ENV_VAR = '$env:{name}' +FORMAT_STR_INVOKE_SCRIPT = '_colcon_prefix_powershell_source_script "{script_path}"' +FORMAT_STR_REMOVE_LEADING_SEPARATOR = '' +FORMAT_STR_REMOVE_TRAILING_SEPARATOR = '' + +DSV_TYPE_APPEND_NON_DUPLICATE = 'append-non-duplicate' +DSV_TYPE_PREPEND_NON_DUPLICATE = 'prepend-non-duplicate' +DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS = 'prepend-non-duplicate-if-exists' +DSV_TYPE_SET = 'set' +DSV_TYPE_SET_IF_UNSET = 'set-if-unset' +DSV_TYPE_SOURCE = 'source' + + +def main(argv=sys.argv[1:]): # noqa: D103 + parser = argparse.ArgumentParser( + description='Output shell commands for the packages in topological ' + 'order') + parser.add_argument( + 'primary_extension', + help='The file extension of the primary shell') + parser.add_argument( + 'additional_extension', nargs='?', + help='The additional file extension to be considered') + parser.add_argument( + '--merged-install', action='store_true', + help='All install prefixes are merged into a single location') + args = parser.parse_args(argv) + + packages = get_packages(Path(__file__).parent, args.merged_install) + + ordered_packages = order_packages(packages) + for pkg_name in ordered_packages: + if _include_comments(): + print( + FORMAT_STR_COMMENT_LINE.format_map( + {'comment': 'Package: ' + pkg_name})) + prefix = os.path.abspath(os.path.dirname(__file__)) + if not args.merged_install: + prefix = os.path.join(prefix, pkg_name) + for line in get_commands( + pkg_name, prefix, args.primary_extension, + args.additional_extension + ): + print(line) + + for line in _remove_ending_separators(): + print(line) + + +def get_packages(prefix_path, merged_install): + """ + Find packages based on colcon-specific files created during installation. + + :param Path prefix_path: The install prefix path of all packages + :param bool merged_install: The flag if the packages are all installed + directly in the prefix or if each package is installed in a subdirectory + named after the package + :returns: A mapping from the package name to the set of runtime + dependencies + :rtype: dict + """ + packages = {} + # since importing colcon_core isn't feasible here the following constant + # must match colcon_core.location.get_relative_package_index_path() + subdirectory = 'share/colcon-core/packages' + if merged_install: + # return if workspace is empty + if not (prefix_path / subdirectory).is_dir(): + return packages + # find all files in the subdirectory + for p in (prefix_path / subdirectory).iterdir(): + if not p.is_file(): + continue + if p.name.startswith('.'): + continue + add_package_runtime_dependencies(p, packages) + else: + # for each subdirectory look for the package specific file + for p in prefix_path.iterdir(): + if not p.is_dir(): + continue + if p.name.startswith('.'): + continue + p = p / subdirectory / p.name + if p.is_file(): + add_package_runtime_dependencies(p, packages) + + # remove unknown dependencies + pkg_names = set(packages.keys()) + for k in packages.keys(): + packages[k] = {d for d in packages[k] if d in pkg_names} + + return packages + + +def add_package_runtime_dependencies(path, packages): + """ + Check the path and if it exists extract the packages runtime dependencies. + + :param Path path: The resource file containing the runtime dependencies + :param dict packages: A mapping from package names to the sets of runtime + dependencies to add to + """ + content = path.read_text() + dependencies = set(content.split(os.pathsep) if content else []) + packages[path.name] = dependencies + + +def order_packages(packages): + """ + Order packages topologically. + + :param dict packages: A mapping from package name to the set of runtime + dependencies + :returns: The package names + :rtype: list + """ + # select packages with no dependencies in alphabetical order + to_be_ordered = list(packages.keys()) + ordered = [] + while to_be_ordered: + pkg_names_without_deps = [ + name for name in to_be_ordered if not packages[name]] + if not pkg_names_without_deps: + reduce_cycle_set(packages) + raise RuntimeError( + 'Circular dependency between: ' + ', '.join(sorted(packages))) + pkg_names_without_deps.sort() + pkg_name = pkg_names_without_deps[0] + to_be_ordered.remove(pkg_name) + ordered.append(pkg_name) + # remove item from dependency lists + for k in list(packages.keys()): + if pkg_name in packages[k]: + packages[k].remove(pkg_name) + return ordered + + +def reduce_cycle_set(packages): + """ + Reduce the set of packages to the ones part of the circular dependency. + + :param dict packages: A mapping from package name to the set of runtime + dependencies which is modified in place + """ + last_depended = None + while len(packages) > 0: + # get all remaining dependencies + depended = set() + for pkg_name, dependencies in packages.items(): + depended = depended.union(dependencies) + # remove all packages which are not dependent on + for name in list(packages.keys()): + if name not in depended: + del packages[name] + if last_depended: + # if remaining packages haven't changed return them + if last_depended == depended: + return packages.keys() + # otherwise reduce again + last_depended = depended + + +def _include_comments(): + # skipping comment lines when COLCON_TRACE is not set speeds up the + # processing especially on Windows + return bool(os.environ.get('COLCON_TRACE')) + + +def get_commands(pkg_name, prefix, primary_extension, additional_extension): + commands = [] + package_dsv_path = os.path.join(prefix, 'share', pkg_name, 'package.dsv') + if os.path.exists(package_dsv_path): + commands += process_dsv_file( + package_dsv_path, prefix, primary_extension, additional_extension) + return commands + + +def process_dsv_file( + dsv_path, prefix, primary_extension=None, additional_extension=None +): + commands = [] + if _include_comments(): + commands.append(FORMAT_STR_COMMENT_LINE.format_map({'comment': dsv_path})) + with open(dsv_path, 'r') as h: + content = h.read() + lines = content.splitlines() + + basenames = OrderedDict() + for i, line in enumerate(lines): + # skip over empty or whitespace-only lines + if not line.strip(): + continue + try: + type_, remainder = line.split(';', 1) + except ValueError: + raise RuntimeError( + "Line %d in '%s' doesn't contain a semicolon separating the " + 'type from the arguments' % (i + 1, dsv_path)) + if type_ != DSV_TYPE_SOURCE: + # handle non-source lines + try: + commands += handle_dsv_types_except_source( + type_, remainder, prefix) + except RuntimeError as e: + raise RuntimeError( + "Line %d in '%s' %s" % (i + 1, dsv_path, e)) from e + else: + # group remaining source lines by basename + path_without_ext, ext = os.path.splitext(remainder) + if path_without_ext not in basenames: + basenames[path_without_ext] = set() + assert ext.startswith('.') + ext = ext[1:] + if ext in (primary_extension, additional_extension): + basenames[path_without_ext].add(ext) + + # add the dsv extension to each basename if the file exists + for basename, extensions in basenames.items(): + if not os.path.isabs(basename): + basename = os.path.join(prefix, basename) + if os.path.exists(basename + '.dsv'): + extensions.add('dsv') + + for basename, extensions in basenames.items(): + if not os.path.isabs(basename): + basename = os.path.join(prefix, basename) + if 'dsv' in extensions: + # process dsv files recursively + commands += process_dsv_file( + basename + '.dsv', prefix, primary_extension=primary_extension, + additional_extension=additional_extension) + elif primary_extension in extensions and len(extensions) == 1: + # source primary-only files + commands += [ + FORMAT_STR_INVOKE_SCRIPT.format_map({ + 'prefix': prefix, + 'script_path': basename + '.' + primary_extension})] + elif additional_extension in extensions: + # source non-primary files + commands += [ + FORMAT_STR_INVOKE_SCRIPT.format_map({ + 'prefix': prefix, + 'script_path': basename + '.' + additional_extension})] + + return commands + + +def handle_dsv_types_except_source(type_, remainder, prefix): + commands = [] + if type_ in (DSV_TYPE_SET, DSV_TYPE_SET_IF_UNSET): + try: + env_name, value = remainder.split(';', 1) + except ValueError: + raise RuntimeError( + "doesn't contain a semicolon separating the environment name " + 'from the value') + try_prefixed_value = os.path.join(prefix, value) if value else prefix + if os.path.exists(try_prefixed_value): + value = try_prefixed_value + if type_ == DSV_TYPE_SET: + commands += _set(env_name, value) + elif type_ == DSV_TYPE_SET_IF_UNSET: + commands += _set_if_unset(env_name, value) + else: + assert False + elif type_ in ( + DSV_TYPE_APPEND_NON_DUPLICATE, + DSV_TYPE_PREPEND_NON_DUPLICATE, + DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS + ): + try: + env_name_and_values = remainder.split(';') + except ValueError: + raise RuntimeError( + "doesn't contain a semicolon separating the environment name " + 'from the values') + env_name = env_name_and_values[0] + values = env_name_and_values[1:] + for value in values: + if not value: + value = prefix + elif not os.path.isabs(value): + value = os.path.join(prefix, value) + if ( + type_ == DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS and + not os.path.exists(value) + ): + comment = f'skip extending {env_name} with not existing ' \ + f'path: {value}' + if _include_comments(): + commands.append( + FORMAT_STR_COMMENT_LINE.format_map({'comment': comment})) + elif type_ == DSV_TYPE_APPEND_NON_DUPLICATE: + commands += _append_unique_value(env_name, value) + else: + commands += _prepend_unique_value(env_name, value) + else: + raise RuntimeError( + 'contains an unknown environment hook type: ' + type_) + return commands + + +env_state = {} + + +def _append_unique_value(name, value): + global env_state + if name not in env_state: + if os.environ.get(name): + env_state[name] = set(os.environ[name].split(os.pathsep)) + else: + env_state[name] = set() + # append even if the variable has not been set yet, in case a shell script sets the + # same variable without the knowledge of this Python script. + # later _remove_ending_separators() will cleanup any unintentional leading separator + extend = FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + os.pathsep + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': extend + value}) + if value not in env_state[name]: + env_state[name].add(value) + else: + if not _include_comments(): + return [] + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +def _prepend_unique_value(name, value): + global env_state + if name not in env_state: + if os.environ.get(name): + env_state[name] = set(os.environ[name].split(os.pathsep)) + else: + env_state[name] = set() + # prepend even if the variable has not been set yet, in case a shell script sets the + # same variable without the knowledge of this Python script. + # later _remove_ending_separators() will cleanup any unintentional trailing separator + extend = os.pathsep + FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value + extend}) + if value not in env_state[name]: + env_state[name].add(value) + else: + if not _include_comments(): + return [] + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +# generate commands for removing prepended underscores +def _remove_ending_separators(): + # do nothing if the shell extension does not implement the logic + if FORMAT_STR_REMOVE_TRAILING_SEPARATOR is None: + return [] + + global env_state + commands = [] + for name in env_state: + # skip variables that already had values before this script started prepending + if name in os.environ: + continue + commands += [ + FORMAT_STR_REMOVE_LEADING_SEPARATOR.format_map({'name': name}), + FORMAT_STR_REMOVE_TRAILING_SEPARATOR.format_map({'name': name})] + return commands + + +def _set(name, value): + global env_state + env_state[name] = value + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value}) + return [line] + + +def _set_if_unset(name, value): + global env_state + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value}) + if env_state.get(name, os.environ.get(name)): + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +if __name__ == '__main__': # pragma: no cover + try: + rc = main() + except RuntimeError as e: + print(str(e), file=sys.stderr) + rc = 1 + sys.exit(rc) diff --git a/src/image_pipeline/install/_local_setup_util_sh.py b/src/image_pipeline/install/_local_setup_util_sh.py new file mode 100644 index 000000000..35c017b25 --- /dev/null +++ b/src/image_pipeline/install/_local_setup_util_sh.py @@ -0,0 +1,404 @@ +# Copyright 2016-2019 Dirk Thomas +# Licensed under the Apache License, Version 2.0 + +import argparse +from collections import OrderedDict +import os +from pathlib import Path +import sys + + +FORMAT_STR_COMMENT_LINE = '# {comment}' +FORMAT_STR_SET_ENV_VAR = 'export {name}="{value}"' +FORMAT_STR_USE_ENV_VAR = '${name}' +FORMAT_STR_INVOKE_SCRIPT = 'COLCON_CURRENT_PREFIX="{prefix}" _colcon_prefix_sh_source_script "{script_path}"' +FORMAT_STR_REMOVE_LEADING_SEPARATOR = 'if [ "$(echo -n ${name} | head -c 1)" = ":" ]; then export {name}=${{{name}#?}} ; fi' +FORMAT_STR_REMOVE_TRAILING_SEPARATOR = 'if [ "$(echo -n ${name} | tail -c 1)" = ":" ]; then export {name}=${{{name}%?}} ; fi' + +DSV_TYPE_APPEND_NON_DUPLICATE = 'append-non-duplicate' +DSV_TYPE_PREPEND_NON_DUPLICATE = 'prepend-non-duplicate' +DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS = 'prepend-non-duplicate-if-exists' +DSV_TYPE_SET = 'set' +DSV_TYPE_SET_IF_UNSET = 'set-if-unset' +DSV_TYPE_SOURCE = 'source' + + +def main(argv=sys.argv[1:]): # noqa: D103 + parser = argparse.ArgumentParser( + description='Output shell commands for the packages in topological ' + 'order') + parser.add_argument( + 'primary_extension', + help='The file extension of the primary shell') + parser.add_argument( + 'additional_extension', nargs='?', + help='The additional file extension to be considered') + parser.add_argument( + '--merged-install', action='store_true', + help='All install prefixes are merged into a single location') + args = parser.parse_args(argv) + + packages = get_packages(Path(__file__).parent, args.merged_install) + + ordered_packages = order_packages(packages) + for pkg_name in ordered_packages: + if _include_comments(): + print( + FORMAT_STR_COMMENT_LINE.format_map( + {'comment': 'Package: ' + pkg_name})) + prefix = os.path.abspath(os.path.dirname(__file__)) + if not args.merged_install: + prefix = os.path.join(prefix, pkg_name) + for line in get_commands( + pkg_name, prefix, args.primary_extension, + args.additional_extension + ): + print(line) + + for line in _remove_ending_separators(): + print(line) + + +def get_packages(prefix_path, merged_install): + """ + Find packages based on colcon-specific files created during installation. + + :param Path prefix_path: The install prefix path of all packages + :param bool merged_install: The flag if the packages are all installed + directly in the prefix or if each package is installed in a subdirectory + named after the package + :returns: A mapping from the package name to the set of runtime + dependencies + :rtype: dict + """ + packages = {} + # since importing colcon_core isn't feasible here the following constant + # must match colcon_core.location.get_relative_package_index_path() + subdirectory = 'share/colcon-core/packages' + if merged_install: + # return if workspace is empty + if not (prefix_path / subdirectory).is_dir(): + return packages + # find all files in the subdirectory + for p in (prefix_path / subdirectory).iterdir(): + if not p.is_file(): + continue + if p.name.startswith('.'): + continue + add_package_runtime_dependencies(p, packages) + else: + # for each subdirectory look for the package specific file + for p in prefix_path.iterdir(): + if not p.is_dir(): + continue + if p.name.startswith('.'): + continue + p = p / subdirectory / p.name + if p.is_file(): + add_package_runtime_dependencies(p, packages) + + # remove unknown dependencies + pkg_names = set(packages.keys()) + for k in packages.keys(): + packages[k] = {d for d in packages[k] if d in pkg_names} + + return packages + + +def add_package_runtime_dependencies(path, packages): + """ + Check the path and if it exists extract the packages runtime dependencies. + + :param Path path: The resource file containing the runtime dependencies + :param dict packages: A mapping from package names to the sets of runtime + dependencies to add to + """ + content = path.read_text() + dependencies = set(content.split(os.pathsep) if content else []) + packages[path.name] = dependencies + + +def order_packages(packages): + """ + Order packages topologically. + + :param dict packages: A mapping from package name to the set of runtime + dependencies + :returns: The package names + :rtype: list + """ + # select packages with no dependencies in alphabetical order + to_be_ordered = list(packages.keys()) + ordered = [] + while to_be_ordered: + pkg_names_without_deps = [ + name for name in to_be_ordered if not packages[name]] + if not pkg_names_without_deps: + reduce_cycle_set(packages) + raise RuntimeError( + 'Circular dependency between: ' + ', '.join(sorted(packages))) + pkg_names_without_deps.sort() + pkg_name = pkg_names_without_deps[0] + to_be_ordered.remove(pkg_name) + ordered.append(pkg_name) + # remove item from dependency lists + for k in list(packages.keys()): + if pkg_name in packages[k]: + packages[k].remove(pkg_name) + return ordered + + +def reduce_cycle_set(packages): + """ + Reduce the set of packages to the ones part of the circular dependency. + + :param dict packages: A mapping from package name to the set of runtime + dependencies which is modified in place + """ + last_depended = None + while len(packages) > 0: + # get all remaining dependencies + depended = set() + for pkg_name, dependencies in packages.items(): + depended = depended.union(dependencies) + # remove all packages which are not dependent on + for name in list(packages.keys()): + if name not in depended: + del packages[name] + if last_depended: + # if remaining packages haven't changed return them + if last_depended == depended: + return packages.keys() + # otherwise reduce again + last_depended = depended + + +def _include_comments(): + # skipping comment lines when COLCON_TRACE is not set speeds up the + # processing especially on Windows + return bool(os.environ.get('COLCON_TRACE')) + + +def get_commands(pkg_name, prefix, primary_extension, additional_extension): + commands = [] + package_dsv_path = os.path.join(prefix, 'share', pkg_name, 'package.dsv') + if os.path.exists(package_dsv_path): + commands += process_dsv_file( + package_dsv_path, prefix, primary_extension, additional_extension) + return commands + + +def process_dsv_file( + dsv_path, prefix, primary_extension=None, additional_extension=None +): + commands = [] + if _include_comments(): + commands.append(FORMAT_STR_COMMENT_LINE.format_map({'comment': dsv_path})) + with open(dsv_path, 'r') as h: + content = h.read() + lines = content.splitlines() + + basenames = OrderedDict() + for i, line in enumerate(lines): + # skip over empty or whitespace-only lines + if not line.strip(): + continue + try: + type_, remainder = line.split(';', 1) + except ValueError: + raise RuntimeError( + "Line %d in '%s' doesn't contain a semicolon separating the " + 'type from the arguments' % (i + 1, dsv_path)) + if type_ != DSV_TYPE_SOURCE: + # handle non-source lines + try: + commands += handle_dsv_types_except_source( + type_, remainder, prefix) + except RuntimeError as e: + raise RuntimeError( + "Line %d in '%s' %s" % (i + 1, dsv_path, e)) from e + else: + # group remaining source lines by basename + path_without_ext, ext = os.path.splitext(remainder) + if path_without_ext not in basenames: + basenames[path_without_ext] = set() + assert ext.startswith('.') + ext = ext[1:] + if ext in (primary_extension, additional_extension): + basenames[path_without_ext].add(ext) + + # add the dsv extension to each basename if the file exists + for basename, extensions in basenames.items(): + if not os.path.isabs(basename): + basename = os.path.join(prefix, basename) + if os.path.exists(basename + '.dsv'): + extensions.add('dsv') + + for basename, extensions in basenames.items(): + if not os.path.isabs(basename): + basename = os.path.join(prefix, basename) + if 'dsv' in extensions: + # process dsv files recursively + commands += process_dsv_file( + basename + '.dsv', prefix, primary_extension=primary_extension, + additional_extension=additional_extension) + elif primary_extension in extensions and len(extensions) == 1: + # source primary-only files + commands += [ + FORMAT_STR_INVOKE_SCRIPT.format_map({ + 'prefix': prefix, + 'script_path': basename + '.' + primary_extension})] + elif additional_extension in extensions: + # source non-primary files + commands += [ + FORMAT_STR_INVOKE_SCRIPT.format_map({ + 'prefix': prefix, + 'script_path': basename + '.' + additional_extension})] + + return commands + + +def handle_dsv_types_except_source(type_, remainder, prefix): + commands = [] + if type_ in (DSV_TYPE_SET, DSV_TYPE_SET_IF_UNSET): + try: + env_name, value = remainder.split(';', 1) + except ValueError: + raise RuntimeError( + "doesn't contain a semicolon separating the environment name " + 'from the value') + try_prefixed_value = os.path.join(prefix, value) if value else prefix + if os.path.exists(try_prefixed_value): + value = try_prefixed_value + if type_ == DSV_TYPE_SET: + commands += _set(env_name, value) + elif type_ == DSV_TYPE_SET_IF_UNSET: + commands += _set_if_unset(env_name, value) + else: + assert False + elif type_ in ( + DSV_TYPE_APPEND_NON_DUPLICATE, + DSV_TYPE_PREPEND_NON_DUPLICATE, + DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS + ): + try: + env_name_and_values = remainder.split(';') + except ValueError: + raise RuntimeError( + "doesn't contain a semicolon separating the environment name " + 'from the values') + env_name = env_name_and_values[0] + values = env_name_and_values[1:] + for value in values: + if not value: + value = prefix + elif not os.path.isabs(value): + value = os.path.join(prefix, value) + if ( + type_ == DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS and + not os.path.exists(value) + ): + comment = f'skip extending {env_name} with not existing ' \ + f'path: {value}' + if _include_comments(): + commands.append( + FORMAT_STR_COMMENT_LINE.format_map({'comment': comment})) + elif type_ == DSV_TYPE_APPEND_NON_DUPLICATE: + commands += _append_unique_value(env_name, value) + else: + commands += _prepend_unique_value(env_name, value) + else: + raise RuntimeError( + 'contains an unknown environment hook type: ' + type_) + return commands + + +env_state = {} + + +def _append_unique_value(name, value): + global env_state + if name not in env_state: + if os.environ.get(name): + env_state[name] = set(os.environ[name].split(os.pathsep)) + else: + env_state[name] = set() + # append even if the variable has not been set yet, in case a shell script sets the + # same variable without the knowledge of this Python script. + # later _remove_ending_separators() will cleanup any unintentional leading separator + extend = FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + os.pathsep + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': extend + value}) + if value not in env_state[name]: + env_state[name].add(value) + else: + if not _include_comments(): + return [] + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +def _prepend_unique_value(name, value): + global env_state + if name not in env_state: + if os.environ.get(name): + env_state[name] = set(os.environ[name].split(os.pathsep)) + else: + env_state[name] = set() + # prepend even if the variable has not been set yet, in case a shell script sets the + # same variable without the knowledge of this Python script. + # later _remove_ending_separators() will cleanup any unintentional trailing separator + extend = os.pathsep + FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value + extend}) + if value not in env_state[name]: + env_state[name].add(value) + else: + if not _include_comments(): + return [] + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +# generate commands for removing prepended underscores +def _remove_ending_separators(): + # do nothing if the shell extension does not implement the logic + if FORMAT_STR_REMOVE_TRAILING_SEPARATOR is None: + return [] + + global env_state + commands = [] + for name in env_state: + # skip variables that already had values before this script started prepending + if name in os.environ: + continue + commands += [ + FORMAT_STR_REMOVE_LEADING_SEPARATOR.format_map({'name': name}), + FORMAT_STR_REMOVE_TRAILING_SEPARATOR.format_map({'name': name})] + return commands + + +def _set(name, value): + global env_state + env_state[name] = value + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value}) + return [line] + + +def _set_if_unset(name, value): + global env_state + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value}) + if env_state.get(name, os.environ.get(name)): + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +if __name__ == '__main__': # pragma: no cover + try: + rc = main() + except RuntimeError as e: + print(str(e), file=sys.stderr) + rc = 1 + sys.exit(rc) diff --git a/src/image_pipeline/install/camera_calibration/share/ament_index/resource_index/packages/camera_calibration b/src/image_pipeline/install/camera_calibration/share/ament_index/resource_index/packages/camera_calibration new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/ament_prefix_path.dsv b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/ament_prefix_path.dsv new file mode 100644 index 000000000..79d4c95b5 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/ament_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;AMENT_PREFIX_PATH; diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/ament_prefix_path.ps1 b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/ament_prefix_path.ps1 new file mode 100644 index 000000000..26b999757 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/ament_prefix_path.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value AMENT_PREFIX_PATH "$env:COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/ament_prefix_path.sh b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/ament_prefix_path.sh new file mode 100644 index 000000000..f3041f688 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/ament_prefix_path.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value AMENT_PREFIX_PATH "$COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/pythonpath.dsv b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/pythonpath.dsv new file mode 100644 index 000000000..257067d44 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/pythonpath.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;PYTHONPATH;lib/python3.10/site-packages diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/pythonpath.ps1 b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/pythonpath.ps1 new file mode 100644 index 000000000..caffe83fd --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/pythonpath.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value PYTHONPATH "$env:COLCON_CURRENT_PREFIX\lib/python3.10/site-packages" diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/pythonpath.sh b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/pythonpath.sh new file mode 100644 index 000000000..660c34836 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/hook/pythonpath.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value PYTHONPATH "$COLCON_CURRENT_PREFIX/lib/python3.10/site-packages" diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.bash b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.bash new file mode 100644 index 000000000..efeefaf1f --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.bash @@ -0,0 +1,31 @@ +# generated from colcon_bash/shell/template/package.bash.em + +# This script extends the environment for this package. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" +else + _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh script of this package +_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/camera_calibration/package.sh" + +unset _colcon_package_bash_source_script +unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.dsv b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.dsv new file mode 100644 index 000000000..1e07de350 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.dsv @@ -0,0 +1,6 @@ +source;share/camera_calibration/hook/pythonpath.ps1 +source;share/camera_calibration/hook/pythonpath.dsv +source;share/camera_calibration/hook/pythonpath.sh +source;share/camera_calibration/hook/ament_prefix_path.ps1 +source;share/camera_calibration/hook/ament_prefix_path.dsv +source;share/camera_calibration/hook/ament_prefix_path.sh diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.ps1 b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.ps1 new file mode 100644 index 000000000..a40166ca7 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.ps1 @@ -0,0 +1,116 @@ +# generated from colcon_powershell/shell/template/package.ps1.em + +# function to append a value to a variable +# which uses colons as separators +# duplicates as well as leading separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_append_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + $_duplicate="" + # start with no values + $_all_values="" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -eq $_value) { + $_duplicate="1" + } + if ($_all_values) { + $_all_values="${_all_values};$_" + } else { + $_all_values="$_" + } + } + } + } + # append only non-duplicates + if (!$_duplicate) { + # avoid leading separator + if ($_all_values) { + $_all_values="${_all_values};${_value}" + } else { + $_all_values="${_value}" + } + } + + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_prepend_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + # start with the new value + $_all_values="$_value" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -ne $_value) { + # keep non-duplicate values + $_all_values="${_all_values};$_" + } + } + } + } + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +function colcon_package_source_powershell_script { + param ( + $_colcon_package_source_powershell_script + ) + # source script with conditional trace output + if (Test-Path $_colcon_package_source_powershell_script) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_package_source_powershell_script'" + } + . "$_colcon_package_source_powershell_script" + } else { + Write-Error "not found: '$_colcon_package_source_powershell_script'" + } +} + + +# a powershell script is able to determine its own path +# the prefix is two levels up from the package specific share directory +$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName + +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/camera_calibration/hook/pythonpath.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/camera_calibration/hook/ament_prefix_path.ps1" + +Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.sh b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.sh new file mode 100644 index 000000000..31adfec31 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.sh @@ -0,0 +1,87 @@ +# generated from colcon_core/shell/template/package.sh.em + +# This script extends the environment for this package. + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prepend_unique_value_IFS=$IFS + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set in zsh + if [ "$(command -v colcon_zsh_convert_to_array)" ]; then + colcon_zsh_convert_to_array _values + fi + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS=$_colcon_prepend_unique_value_IFS + unset _colcon_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/DinoSage/rocket_league/src/image_pipeline/install/camera_calibration" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_package_sh_COLCON_CURRENT_PREFIX + return 1 + fi + COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" +fi +unset _colcon_package_sh_COLCON_CURRENT_PREFIX + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh hooks +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/camera_calibration/hook/pythonpath.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/camera_calibration/hook/ament_prefix_path.sh" + +unset _colcon_package_sh_source_script +unset COLCON_CURRENT_PREFIX + +# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.xml b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.xml new file mode 100644 index 000000000..e9335bf53 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.xml @@ -0,0 +1,40 @@ + + + + camera_calibration + 4.0.0 + + camera_calibration allows easy calibration of monocular or stereo + cameras using a checkerboard calibration target. + + + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/camera_calibration/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + James Bowman + Patrick Mihelich + + cv_bridge + image_geometry + message_filters + python3-opencv + rclpy + std_srvs + sensor_msgs + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + python3-requests + + + ament_python + + diff --git a/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.zsh b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.zsh new file mode 100644 index 000000000..f2ca1bde3 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/camera_calibration/package.zsh @@ -0,0 +1,42 @@ +# generated from colcon_zsh/shell/template/package.zsh.em + +# This script extends the environment for this package. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" +else + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +colcon_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# source sh script of this package +_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/camera_calibration/package.sh" +unset convert_zsh_to_array + +unset _colcon_package_zsh_source_script +unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/camera_calibration/share/colcon-core/packages/camera_calibration b/src/image_pipeline/install/camera_calibration/share/colcon-core/packages/camera_calibration new file mode 100644 index 000000000..a8718ca64 --- /dev/null +++ b/src/image_pipeline/install/camera_calibration/share/colcon-core/packages/camera_calibration @@ -0,0 +1 @@ +cv_bridge:image_geometry:message_filters:python3-opencv:rclpy:sensor_msgs:std_srvs \ No newline at end of file diff --git a/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/conversions.hpp b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/conversions.hpp new file mode 100644 index 000000000..eef392c63 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/conversions.hpp @@ -0,0 +1,155 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__CONVERSIONS_HPP_ +#define DEPTH_IMAGE_PROC__CONVERSIONS_HPP_ + +#include + +#include "image_geometry/pinhole_camera_model.hpp" + +#include + +#include +#include +#include +#include + +namespace depth_image_proc +{ + +// Handles float or uint16 depths +template +void convertDepth( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg, + const image_geometry::PinholeCameraModel & model, + double range_max = 0.0) +{ + // Use correct principal point from calibration + float center_x = model.cx(); + float center_y = model.cy(); + + // Combine unit conversion (if necessary) with scaling by focal length for computing (X,Y) + double unit_scaling = DepthTraits::toMeters(T(1) ); + float constant_x = unit_scaling / model.fx(); + float constant_y = unit_scaling / model.fy(); + float bad_point = std::numeric_limits::quiet_NaN(); + + sensor_msgs::PointCloud2Iterator iter_x(*cloud_msg, "x"); + sensor_msgs::PointCloud2Iterator iter_y(*cloud_msg, "y"); + sensor_msgs::PointCloud2Iterator iter_z(*cloud_msg, "z"); + const T * depth_row = reinterpret_cast(&depth_msg->data[0]); + int row_step = depth_msg->step / sizeof(T); + for (int v = 0; v < static_cast(cloud_msg->height); ++v, depth_row += row_step) { + for (int u = 0; u < static_cast(cloud_msg->width); ++u, ++iter_x, ++iter_y, ++iter_z) { + T depth = depth_row[u]; + + // Missing points denoted by NaNs + if (!DepthTraits::valid(depth)) { + if (range_max != 0.0) { + depth = DepthTraits::fromMeters(range_max); + } else { + *iter_x = *iter_y = *iter_z = bad_point; + continue; + } + } + + // Fill in XYZ + *iter_x = (u - center_x) * depth * constant_x; + *iter_y = (v - center_y) * depth * constant_y; + *iter_z = DepthTraits::toMeters(depth); + } + } +} + +// Handles float or uint16 depths +template +void convertDepthRadial( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg, + cv::Mat & transform) +{ + // Combine unit conversion (if necessary) with scaling by focal length for computing (X,Y) + float bad_point = std::numeric_limits::quiet_NaN(); + + sensor_msgs::PointCloud2Iterator iter_x(*cloud_msg, "x"); + sensor_msgs::PointCloud2Iterator iter_y(*cloud_msg, "y"); + sensor_msgs::PointCloud2Iterator iter_z(*cloud_msg, "z"); + const T * depth_row = reinterpret_cast(&depth_msg->data[0]); + int row_step = depth_msg->step / sizeof(T); + for (int v = 0; v < static_cast(cloud_msg->height); ++v, depth_row += row_step) { + for (int u = 0; u < static_cast(cloud_msg->width); ++u, ++iter_x, ++iter_y, ++iter_z) { + T depth = depth_row[u]; + + // Missing points denoted by NaNs + if (!DepthTraits::valid(depth)) { + *iter_x = *iter_y = *iter_z = bad_point; + continue; + } + const cv::Vec3f & cvPoint = transform.at(u, v) * DepthTraits::toMeters(depth); + // Fill in XYZ + *iter_x = cvPoint(0); + *iter_y = cvPoint(1); + *iter_z = cvPoint(2); + } + } +} + +// Handles float or uint16 depths +template +void convertIntensity( + const sensor_msgs::msg::Image::ConstSharedPtr & intensity_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg) +{ + sensor_msgs::PointCloud2Iterator iter_i(*cloud_msg, "intensity"); + const T * inten_row = reinterpret_cast(&intensity_msg->data[0]); + + const int i_row_step = intensity_msg->step / sizeof(T); + for (int v = 0; v < static_cast(cloud_msg->height); ++v, inten_row += i_row_step) { + for (int u = 0; u < static_cast(cloud_msg->width); ++u, ++iter_i) { + *iter_i = inten_row[u]; + } + } +} + +// Handles RGB8, BGR8, and MONO8 +void convertRgb( + const sensor_msgs::msg::Image::ConstSharedPtr & rgb_msg, + sensor_msgs::msg::PointCloud2::SharedPtr & cloud_msg, + int red_offset, int green_offset, int blue_offset, int color_step); + +cv::Mat initMatrix(cv::Mat cameraMatrix, cv::Mat distCoeffs, int width, int height, bool radial); + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__CONVERSIONS_HPP_ diff --git a/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/depth_traits.hpp b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/depth_traits.hpp new file mode 100644 index 000000000..7e3c39d3d --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/depth_traits.hpp @@ -0,0 +1,75 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__DEPTH_TRAITS_HPP_ +#define DEPTH_IMAGE_PROC__DEPTH_TRAITS_HPP_ + +#include +#include +#include +#include + +namespace depth_image_proc +{ + +// Encapsulate differences between processing float and uint16_t depths +template +struct DepthTraits {}; + +template<> +struct DepthTraits +{ + static inline bool valid(uint16_t depth) {return depth != 0;} + static inline float toMeters(uint16_t depth) {return depth * 0.001f;} // originally mm + static inline uint16_t fromMeters(float depth) {return (depth * 1000.0f) + 0.5f;} + // Do nothing - already zero-filled + static inline void initializeBuffer(std::vector & buffer) {(void) buffer;} +}; + +template<> +struct DepthTraits +{ + static inline bool valid(float depth) {return std::isfinite(depth);} + static inline float toMeters(float depth) {return depth;} + static inline float fromMeters(float depth) {return depth;} + + static inline void initializeBuffer(std::vector & buffer) + { + float * start = reinterpret_cast(&buffer[0]); + float * end = reinterpret_cast(&buffer[0] + buffer.size()); + std::fill(start, end, std::numeric_limits::quiet_NaN()); + } +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__DEPTH_TRAITS_HPP_ diff --git a/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyz.hpp b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyz.hpp new file mode 100644 index 000000000..397f4ab6d --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyz.hpp @@ -0,0 +1,85 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_HPP_ + +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace depth_image_proc +{ + +namespace enc = sensor_msgs::image_encodings; + +class PointCloudXyzNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyzNode(const rclcpp::NodeOptions & options); + +private: + using PointCloud2 = sensor_msgs::msg::PointCloud2; + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + + // Subscriptions + image_transport::CameraSubscriber sub_depth_; + int queue_size_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + image_geometry::PinholeCameraModel model_; + + void connectCb(); + + void depthCb( + const Image::ConstSharedPtr & depth_msg, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_HPP_ diff --git a/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyz_radial.hpp b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyz_radial.hpp new file mode 100644 index 000000000..2cfc44e03 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyz_radial.hpp @@ -0,0 +1,85 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_RADIAL_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_RADIAL_HPP_ + +#include +#include +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" + +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +class PointCloudXyzRadialNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyzRadialNode(const rclcpp::NodeOptions & options); + +private: + // Subscriptions + image_transport::CameraSubscriber sub_depth_; + int queue_size_; + + // Publications + std::mutex connect_mutex_; + using PointCloud = sensor_msgs::msg::PointCloud2; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + std::vector D_; + std::array K_; + + uint32_t width_; + uint32_t height_; + + cv::Mat transform_; + + void connectCb(); + + void depthCb( + const sensor_msgs::msg::Image::ConstSharedPtr & depth_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZ_RADIAL_HPP_ diff --git a/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzi.hpp b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzi.hpp new file mode 100644 index 000000000..cd2cbd6ea --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzi.hpp @@ -0,0 +1,90 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_HPP_ + +#include +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" +#include "message_filters/subscriber.h" +#include "message_filters/sync_policies/approximate_time.h" +#include "message_filters/synchronizer.h" + +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +namespace enc = sensor_msgs::image_encodings; + +class PointCloudXyziNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyziNode(const rclcpp::NodeOptions & options); + +private: + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + using PointCloud = sensor_msgs::msg::PointCloud2; + + // Subscriptions + image_transport::SubscriberFilter sub_depth_, sub_intensity_; + message_filters::Subscriber sub_info_; + using SyncPolicy = + message_filters::sync_policies::ApproximateTime; + using Synchronizer = message_filters::Synchronizer; + std::shared_ptr sync_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + image_geometry::PinholeCameraModel model_; + + void connectCb(); + + void imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & intensity_msg, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_HPP_ diff --git a/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzi_radial.hpp b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzi_radial.hpp new file mode 100644 index 000000000..98398f227 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzi_radial.hpp @@ -0,0 +1,103 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_RADIAL_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_RADIAL_HPP_ + +#include +#include +#include +#include + +#include "depth_image_proc/visibility.h" +#include "message_filters/subscriber.h" +#include "message_filters/synchronizer.h" +#include "message_filters/sync_policies/exact_time.h" + +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +using SyncPolicy = + message_filters::sync_policies::ExactTime< + sensor_msgs::msg::Image, + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo>; + +class PointCloudXyziRadialNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyziRadialNode(const rclcpp::NodeOptions & options); + +private: + using PointCloud = sensor_msgs::msg::PointCloud2; + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + + // Subscriptions + image_transport::SubscriberFilter sub_depth_, sub_intensity_; + message_filters::Subscriber sub_info_; + + int queue_size_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + using Synchronizer = message_filters::Synchronizer; + std::shared_ptr sync_; + + std::vector D_; + std::array K_; + + uint32_t width_; + uint32_t height_; + + cv::Mat transform_; + + void connectCb(); + + void imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & intensity_msg_in, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZI_RADIAL_HPP_ diff --git a/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb.hpp b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb.hpp new file mode 100644 index 000000000..e18db0cfc --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb.hpp @@ -0,0 +1,94 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_HPP_ + +#include +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" +#include "message_filters/subscriber.h" +#include "message_filters/synchronizer.h" +#include "message_filters/sync_policies/exact_time.h" +#include "message_filters/sync_policies/approximate_time.h" + +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +class PointCloudXyzrgbNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyzrgbNode(const rclcpp::NodeOptions & options); + +private: + using PointCloud2 = sensor_msgs::msg::PointCloud2; + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + + // Subscriptions + image_transport::SubscriberFilter sub_depth_, sub_rgb_; + message_filters::Subscriber sub_info_; + using SyncPolicy = + message_filters::sync_policies::ApproximateTime; + using ExactSyncPolicy = + message_filters::sync_policies::ExactTime; + using Synchronizer = message_filters::Synchronizer; + using ExactSynchronizer = message_filters::Synchronizer; + std::shared_ptr sync_; + std::shared_ptr exact_sync_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + image_geometry::PinholeCameraModel model_; + + void connectCb(); + + void imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & rgb_msg, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_HPP_ diff --git a/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb_radial.hpp b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb_radial.hpp new file mode 100644 index 000000000..ef07c31d5 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/point_cloud_xyzrgb_radial.hpp @@ -0,0 +1,106 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_RADIAL_HPP_ +#define DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_RADIAL_HPP_ + +#include +#include +#include +#include + +#include "depth_image_proc/visibility.h" +#include "image_geometry/pinhole_camera_model.hpp" +#include "message_filters/subscriber.h" +#include "message_filters/synchronizer.h" +#include "message_filters/sync_policies/exact_time.h" +#include "message_filters/sync_policies/approximate_time.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace depth_image_proc +{ + +class PointCloudXyzrgbRadialNode : public rclcpp::Node +{ +public: + DEPTH_IMAGE_PROC_PUBLIC PointCloudXyzrgbRadialNode(const rclcpp::NodeOptions & options); + +private: + using PointCloud2 = sensor_msgs::msg::PointCloud2; + using Image = sensor_msgs::msg::Image; + using CameraInfo = sensor_msgs::msg::CameraInfo; + + // Subscriptions + image_transport::SubscriberFilter sub_depth_, sub_rgb_; + message_filters::Subscriber sub_info_; + using SyncPolicy = + message_filters::sync_policies::ApproximateTime; + using ExactSyncPolicy = + message_filters::sync_policies::ExactTime; + using Synchronizer = message_filters::Synchronizer; + using ExactSynchronizer = message_filters::Synchronizer; + std::unique_ptr sync_; + std::unique_ptr exact_sync_; + + // Publications + std::mutex connect_mutex_; + rclcpp::Publisher::SharedPtr pub_point_cloud_; + + std::vector D_; + std::array K_; + + uint32_t width_; + uint32_t height_; + + cv::Mat transform_; + + image_geometry::PinholeCameraModel model_; + + void connectCb(); + + void imageCb( + const Image::ConstSharedPtr & depth_msg, + const Image::ConstSharedPtr & rgb_msg, + const CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace depth_image_proc + +#endif // DEPTH_IMAGE_PROC__POINT_CLOUD_XYZRGB_RADIAL_HPP_ diff --git a/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/visibility.h b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/visibility.h new file mode 100644 index 000000000..43d4d047a --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/include/depth_image_proc/visibility.h @@ -0,0 +1,83 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. +#ifndef DEPTH_IMAGE_PROC__VISIBILITY_H_ +#define DEPTH_IMAGE_PROC__VISIBILITY_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + + #ifdef __GNUC__ + #define DEPTH_IMAGE_PROC_EXPORT __attribute__ ((dllexport)) + #define DEPTH_IMAGE_PROC_IMPORT __attribute__ ((dllimport)) + #else + #define DEPTH_IMAGE_PROC_EXPORT __declspec(dllexport) + #define DEPTH_IMAGE_PROC_IMPORT __declspec(dllimport) + #endif + + #ifdef DEPTH_IMAGE_PROC_DLL + #define DEPTH_IMAGE_PROC_PUBLIC DEPTH_IMAGE_PROC_EXPORT + #else + #define DEPTH_IMAGE_PROC_PUBLIC DEPTH_IMAGE_PROC_IMPORT + #endif + + #define DEPTH_IMAGE_PROC_PUBLIC_TYPE DEPTH_IMAGE_PROC_PUBLIC + + #define DEPTH_IMAGE_PROC_LOCAL + +#else + + #define DEPTH_IMAGE_PROC_EXPORT __attribute__ ((visibility("default"))) + #define DEPTH_IMAGE_PROC_IMPORT + + #if __GNUC__ >= 4 + #define DEPTH_IMAGE_PROC_PUBLIC __attribute__ ((visibility("default"))) + #define DEPTH_IMAGE_PROC_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define DEPTH_IMAGE_PROC_PUBLIC + #define DEPTH_IMAGE_PROC_LOCAL + #endif + + #define DEPTH_IMAGE_PROC_PUBLIC_TYPE +#endif + +#ifdef __cplusplus +} +#endif + +#endif // DEPTH_IMAGE_PROC__VISIBILITY_H_ diff --git a/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/package_run_dependencies/depth_image_proc b/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/package_run_dependencies/depth_image_proc new file mode 100644 index 000000000..d26c2d502 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/package_run_dependencies/depth_image_proc @@ -0,0 +1 @@ +cv_bridge;image_geometry;image_transport;libopencv-dev;message_filters;rclcpp;rclcpp_components;sensor_msgs;stereo_msgs;tf2;tf2_eigen;tf2_ros;ament_lint_auto;ament_lint_common \ No newline at end of file diff --git a/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/packages/depth_image_proc b/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/packages/depth_image_proc new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/parent_prefix_path/depth_image_proc b/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/parent_prefix_path/depth_image_proc new file mode 100644 index 000000000..56c8678ea --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/parent_prefix_path/depth_image_proc @@ -0,0 +1 @@ +/opt/ros/iron \ No newline at end of file diff --git a/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/rclcpp_components/depth_image_proc b/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/rclcpp_components/depth_image_proc new file mode 100644 index 000000000..a962828f1 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/ament_index/resource_index/rclcpp_components/depth_image_proc @@ -0,0 +1,10 @@ +depth_image_proc::ConvertMetricNode;lib/libdepth_image_proc.so +depth_image_proc::CropForemostNode;lib/libdepth_image_proc.so +depth_image_proc::DisparityNode;lib/libdepth_image_proc.so +depth_image_proc::PointCloudXyzNode;lib/libdepth_image_proc.so +depth_image_proc::PointCloudXyzrgbNode;lib/libdepth_image_proc.so +depth_image_proc::PointCloudXyziNode;lib/libdepth_image_proc.so +depth_image_proc::PointCloudXyzRadialNode;lib/libdepth_image_proc.so +depth_image_proc::PointCloudXyziRadialNode;lib/libdepth_image_proc.so +depth_image_proc::PointCloudXyziRadialNode;lib/libdepth_image_proc.so +depth_image_proc::RegisterNode;lib/libdepth_image_proc.so diff --git a/src/image_pipeline/install/depth_image_proc/share/colcon-core/packages/depth_image_proc b/src/image_pipeline/install/depth_image_proc/share/colcon-core/packages/depth_image_proc new file mode 100644 index 000000000..ede010a9e --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/colcon-core/packages/depth_image_proc @@ -0,0 +1 @@ +cv_bridge:image_geometry:image_transport:libopencv-dev:message_filters:rclcpp:rclcpp_components:sensor_msgs:stereo_msgs:tf2:tf2_eigen:tf2_ros \ No newline at end of file diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/ament_cmake_export_dependencies-extras.cmake b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/ament_cmake_export_dependencies-extras.cmake new file mode 100644 index 000000000..ad9ce21f1 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/ament_cmake_export_dependencies-extras.cmake @@ -0,0 +1,92 @@ +# generated from ament_cmake_export_dependencies/cmake/ament_cmake_export_dependencies-extras.cmake.in + +set(_exported_dependencies "cv_bridge;image_geometry;image_transport;message_filters;rclcpp;rclcpp_components;sensor_msgs;stereo_msgs;tf2;tf2_eigen;tf2_ros") + +find_package(ament_cmake_libraries QUIET REQUIRED) + +# find_package() all dependencies +# and append their DEFINITIONS INCLUDE_DIRS, LIBRARIES, and LINK_FLAGS +# variables to depth_image_proc_DEFINITIONS, depth_image_proc_INCLUDE_DIRS, +# depth_image_proc_LIBRARIES, and depth_image_proc_LINK_FLAGS. +# Additionally collect the direct dependency names in +# depth_image_proc_DEPENDENCIES as well as the recursive dependency names +# in depth_image_proc_RECURSIVE_DEPENDENCIES. +if(NOT _exported_dependencies STREQUAL "") + find_package(ament_cmake_core QUIET REQUIRED) + set(depth_image_proc_DEPENDENCIES ${_exported_dependencies}) + set(depth_image_proc_RECURSIVE_DEPENDENCIES ${_exported_dependencies}) + set(_libraries) + foreach(_dep ${_exported_dependencies}) + if(NOT ${_dep}_FOUND) + find_package("${_dep}" QUIET REQUIRED) + endif() + # if a package provides modern CMake interface targets use them + # exclusively assuming the classic CMake variables only exist for + # backward compatibility + set(use_modern_cmake FALSE) + if(NOT "${${_dep}_TARGETS}" STREQUAL "") + foreach(_target ${${_dep}_TARGETS}) + # only use actual targets + # in case a package uses this variable for other content + if(TARGET "${_target}") + get_target_property(_include_dirs ${_target} INTERFACE_INCLUDE_DIRECTORIES) + if(_include_dirs) + list_append_unique(depth_image_proc_INCLUDE_DIRS "${_include_dirs}") + endif() + + get_target_property(_imported_configurations ${_target} IMPORTED_CONFIGURATIONS) + if(_imported_configurations) + string(TOUPPER "${_imported_configurations}" _imported_configurations) + if(DEBUG_CONFIGURATIONS) + string(TOUPPER "${DEBUG_CONFIGURATIONS}" _debug_configurations_uppercase) + else() + set(_debug_configurations_uppercase "DEBUG") + endif() + foreach(_imported_config ${_imported_configurations}) + get_target_property(_imported_implib ${_target} IMPORTED_IMPLIB_${_imported_config}) + if(_imported_implib) + set(_imported_implib_config "optimized") + if(${_imported_config} IN_LIST _debug_configurations_uppercase) + set(_imported_implib_config "debug") + endif() + list(APPEND _libraries ${_imported_implib_config} ${_imported_implib}) + else() + get_target_property(_imported_location ${_target} IMPORTED_LOCATION_${_imported_config}) + if(_imported_location) + list(APPEND _libraries "${_imported_location}") + endif() + endif() + endforeach() + endif() + + get_target_property(_link_libraries ${_target} INTERFACE_LINK_LIBRARIES) + if(_link_libraries) + list(APPEND _libraries "${_link_libraries}") + endif() + set(use_modern_cmake TRUE) + endif() + endforeach() + endif() + if(NOT use_modern_cmake) + if(${_dep}_DEFINITIONS) + list_append_unique(depth_image_proc_DEFINITIONS "${${_dep}_DEFINITIONS}") + endif() + if(${_dep}_INCLUDE_DIRS) + list_append_unique(depth_image_proc_INCLUDE_DIRS "${${_dep}_INCLUDE_DIRS}") + endif() + if(${_dep}_LIBRARIES) + list(APPEND _libraries "${${_dep}_LIBRARIES}") + endif() + if(${_dep}_LINK_FLAGS) + list_append_unique(depth_image_proc_LINK_FLAGS "${${_dep}_LINK_FLAGS}") + endif() + if(${_dep}_RECURSIVE_DEPENDENCIES) + list_append_unique(depth_image_proc_RECURSIVE_DEPENDENCIES "${${_dep}_RECURSIVE_DEPENDENCIES}") + endif() + endif() + if(_libraries) + ament_libraries_deduplicate(_libraries "${_libraries}") + list(APPEND depth_image_proc_LIBRARIES "${_libraries}") + endif() + endforeach() +endif() diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/ament_cmake_export_include_directories-extras.cmake b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/ament_cmake_export_include_directories-extras.cmake new file mode 100644 index 000000000..f08de9894 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/ament_cmake_export_include_directories-extras.cmake @@ -0,0 +1,16 @@ +# generated from ament_cmake_export_include_directories/cmake/ament_cmake_export_include_directories-extras.cmake.in + +set(_exported_include_dirs "${depth_image_proc_DIR}/../../../include") + +# append include directories to depth_image_proc_INCLUDE_DIRS +# warn about not existing paths +if(NOT _exported_include_dirs STREQUAL "") + find_package(ament_cmake_core QUIET REQUIRED) + foreach(_exported_include_dir ${_exported_include_dirs}) + if(NOT IS_DIRECTORY "${_exported_include_dir}") + message(WARNING "Package 'depth_image_proc' exports the include directory '${_exported_include_dir}' which doesn't exist") + endif() + normalize_path(_exported_include_dir "${_exported_include_dir}") + list(APPEND depth_image_proc_INCLUDE_DIRS "${_exported_include_dir}") + endforeach() +endif() diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/ament_cmake_export_libraries-extras.cmake b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/ament_cmake_export_libraries-extras.cmake new file mode 100644 index 000000000..0a511f2fe --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/ament_cmake_export_libraries-extras.cmake @@ -0,0 +1,141 @@ +# generated from ament_cmake_export_libraries/cmake/template/ament_cmake_export_libraries.cmake.in + +set(_exported_libraries "depth_image_proc") +set(_exported_library_names "") + +# populate depth_image_proc_LIBRARIES +if(NOT _exported_libraries STREQUAL "") + # loop over libraries, either target names or absolute paths + list(LENGTH _exported_libraries _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_libraries ${_i} _arg) + + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND depth_image_proc_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'depth_image_proc' passes the build configuration keyword '${_cfg}' as the last exported library") + endif() + list(GET _exported_libraries ${_i} _library) + else() + # the value is a library without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + if(NOT IS_ABSOLUTE "${_library}") + # search for library target relative to this CMake file + set(_lib "NOTFOUND") + find_library( + _lib NAMES "${_library}" + PATHS "${depth_image_proc_DIR}/../../../lib" + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + + if(NOT _lib) + # warn about not existing library and ignore it + message(FATAL_ERROR "Package 'depth_image_proc' exports the library '${_library}' which couldn't be found") + elseif(NOT IS_ABSOLUTE "${_lib}") + # the found library must be an absolute path + message(FATAL_ERROR "Package 'depth_image_proc' found the library '${_library}' at '${_lib}' which is not an absolute path") + elseif(NOT EXISTS "${_lib}") + # the found library must exist + message(FATAL_ERROR "Package 'depth_image_proc' found the library '${_lib}' which doesn't exist") + else() + list(APPEND depth_image_proc_LIBRARIES ${_cfg} "${_lib}") + endif() + + else() + if(NOT EXISTS "${_library}") + # the found library must exist + message(WARNING "Package 'depth_image_proc' exports the library '${_library}' which doesn't exist") + else() + list(APPEND depth_image_proc_LIBRARIES ${_cfg} "${_library}") + endif() + endif() + endwhile() +endif() + +# find_library() library names with optional LIBRARY_DIRS +# and add the libraries to depth_image_proc_LIBRARIES +if(NOT _exported_library_names STREQUAL "") + # loop over library names + # but remember related build configuration keyword if available + list(LENGTH _exported_library_names _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_library_names ${_i} _arg) + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND depth_image_proc_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library name + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'depth_image_proc' passes the build configuration keyword '${_cfg}' as the last exported target") + endif() + list(GET _exported_library_names ${_i} _library) + else() + # the value is a library target without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + # extract optional LIBRARY_DIRS from library name + string(REPLACE ":" ";" _library_dirs "${_library}") + list(GET _library_dirs 0 _library_name) + list(REMOVE_AT _library_dirs 0) + + set(_lib "NOTFOUND") + if(NOT _library_dirs) + # search for library in the common locations + find_library( + _lib + NAMES "${_library_name}" + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING "Package 'depth_image_proc' exports library '${_library_name}' which couldn't be found") + endif() + else() + # search for library in the specified directories + find_library( + _lib + NAMES "${_library_name}" + PATHS ${_library_dirs} + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING + "Package 'depth_image_proc' exports library '${_library_name}' with LIBRARY_DIRS '${_library_dirs}' which couldn't be found") + endif() + endif() + if(_lib) + list(APPEND depth_image_proc_LIBRARIES ${_cfg} "${_lib}") + endif() + endwhile() +endif() + +# TODO(dirk-thomas) deduplicate depth_image_proc_LIBRARIES +# while maintaining library order +# as well as build configuration keywords +# as well as linker flags diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/depth_image_procConfig-version.cmake b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/depth_image_procConfig-version.cmake new file mode 100644 index 000000000..81dc15e08 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/depth_image_procConfig-version.cmake @@ -0,0 +1,14 @@ +# generated from ament/cmake/core/templates/nameConfig-version.cmake.in +set(PACKAGE_VERSION "4.0.0") + +set(PACKAGE_VERSION_EXACT False) +set(PACKAGE_VERSION_COMPATIBLE False) + +if("${PACKAGE_FIND_VERSION}" VERSION_EQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT True) + set(PACKAGE_VERSION_COMPATIBLE True) +endif() + +if("${PACKAGE_FIND_VERSION}" VERSION_LESS "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE True) +endif() diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/depth_image_procConfig.cmake b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/depth_image_procConfig.cmake new file mode 100644 index 000000000..15878f30f --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/cmake/depth_image_procConfig.cmake @@ -0,0 +1,42 @@ +# generated from ament/cmake/core/templates/nameConfig.cmake.in + +# prevent multiple inclusion +if(_depth_image_proc_CONFIG_INCLUDED) + # ensure to keep the found flag the same + if(NOT DEFINED depth_image_proc_FOUND) + # explicitly set it to FALSE, otherwise CMake will set it to TRUE + set(depth_image_proc_FOUND FALSE) + elseif(NOT depth_image_proc_FOUND) + # use separate condition to avoid uninitialized variable warning + set(depth_image_proc_FOUND FALSE) + endif() + return() +endif() +set(_depth_image_proc_CONFIG_INCLUDED TRUE) + +# output package information +if(NOT depth_image_proc_FIND_QUIETLY) + message(STATUS "Found depth_image_proc: 4.0.0 (${depth_image_proc_DIR})") +endif() + +# warn when using a deprecated package +if(NOT "" STREQUAL "") + set(_msg "Package 'depth_image_proc' is deprecated") + # append custom deprecation text if available + if(NOT "" STREQUAL "TRUE") + set(_msg "${_msg} ()") + endif() + # optionally quiet the deprecation message + if(NOT depth_image_proc_DEPRECATED_QUIET) + message(DEPRECATION "${_msg}") + endif() +endif() + +# flag package as ament-based to distinguish it after being find_package()-ed +set(depth_image_proc_FOUND_AMENT_PACKAGE TRUE) + +# include all config extra files +set(_extras "ament_cmake_export_dependencies-extras.cmake;ament_cmake_export_include_directories-extras.cmake;ament_cmake_export_libraries-extras.cmake") +foreach(_extra ${_extras}) + include("${depth_image_proc_DIR}/${_extra}") +endforeach() diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/ament_prefix_path.dsv b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/ament_prefix_path.dsv new file mode 100644 index 000000000..79d4c95b5 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/ament_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;AMENT_PREFIX_PATH; diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/ament_prefix_path.sh b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/ament_prefix_path.sh new file mode 100644 index 000000000..02e441b75 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/ament_prefix_path.sh @@ -0,0 +1,4 @@ +# copied from +# ament_cmake_core/cmake/environment_hooks/environment/ament_prefix_path.sh + +ament_prepend_unique_value AMENT_PREFIX_PATH "$AMENT_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/library_path.dsv b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/library_path.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/library_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/library_path.sh b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/library_path.sh new file mode 100644 index 000000000..292e518f1 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/library_path.sh @@ -0,0 +1,16 @@ +# copied from ament_package/template/environment_hook/library_path.sh + +# detect if running on Darwin platform +_UNAME=`uname -s` +_IS_DARWIN=0 +if [ "$_UNAME" = "Darwin" ]; then + _IS_DARWIN=1 +fi +unset _UNAME + +if [ $_IS_DARWIN -eq 0 ]; then + ament_prepend_unique_value LD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +else + ament_prepend_unique_value DYLD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +fi +unset _IS_DARWIN diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/path.dsv b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/path.dsv new file mode 100644 index 000000000..b94426af0 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate-if-exists;PATH;bin diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/path.sh b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/path.sh new file mode 100644 index 000000000..e59b749a8 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/environment/path.sh @@ -0,0 +1,5 @@ +# copied from ament_cmake_core/cmake/environment_hooks/environment/path.sh + +if [ -d "$AMENT_CURRENT_PREFIX/bin" ]; then + ament_prepend_unique_value PATH "$AMENT_CURRENT_PREFIX/bin" +fi diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/cmake_prefix_path.dsv b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/cmake_prefix_path.dsv new file mode 100644 index 000000000..e119f32cb --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/cmake_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;CMAKE_PREFIX_PATH; diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/cmake_prefix_path.ps1 b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/cmake_prefix_path.ps1 new file mode 100644 index 000000000..d03facc1a --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/cmake_prefix_path.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value CMAKE_PREFIX_PATH "$env:COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/cmake_prefix_path.sh b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/cmake_prefix_path.sh new file mode 100644 index 000000000..a948e685b --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/cmake_prefix_path.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value CMAKE_PREFIX_PATH "$COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/ld_library_path_lib.dsv b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/ld_library_path_lib.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/ld_library_path_lib.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/ld_library_path_lib.ps1 b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/ld_library_path_lib.ps1 new file mode 100644 index 000000000..f6df601d0 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/ld_library_path_lib.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value LD_LIBRARY_PATH "$env:COLCON_CURRENT_PREFIX\lib" diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/ld_library_path_lib.sh b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/ld_library_path_lib.sh new file mode 100644 index 000000000..ca3c1020b --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/hook/ld_library_path_lib.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value LD_LIBRARY_PATH "$COLCON_CURRENT_PREFIX/lib" diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/convert_metric.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/convert_metric.launch.py new file mode 100644 index 000000000..37e6c09f2 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/convert_metric.launch.py @@ -0,0 +1,75 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/convert_metric.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::ConvertMetricNode', + name='convert_metric_node', + remappings=[('image_raw', '/camera/depth/image_rect_raw'), + ('camera_info', '/camera/depth/camera_info'), + ('image', '/camera/depth/converted_image')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/crop_foremost.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/crop_foremost.launch.py new file mode 100644 index 000000000..17485336f --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/crop_foremost.launch.py @@ -0,0 +1,74 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/crop_formost.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::CropForemostNode', + name='crop_foremost_node', + remappings=[('image_raw', '/camera/depth/image_rect_raw'), + ('camera_info', '/camera/depth/camera_info'), + ('image', '/camera/depth/converted_image')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/disparity.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/disparity.launch.py new file mode 100644 index 000000000..acf2af812 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/disparity.launch.py @@ -0,0 +1,77 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/disparity.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # we use realsense camera for test, realsense not support left and right topic + # so we remap to depth image only for interface test. + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::DisparityNode', + name='disparity_node', + remappings=[('left/image_rect', '/camera/depth/image_rect_raw'), + ('right/camera_info', '/camera/depth/camera_info'), + ('left/disparity', '/camera/left/disparity')] + ), + ], + output='screen', + ), + + # TODO: rviz could not display disparity(stereo_msgs) + # run stereo_view for display after image_view be ported + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyz.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyz.launch.py new file mode 100644 index 000000000..1428bcec1 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyz.launch.py @@ -0,0 +1,75 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyz.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyzNode', + name='point_cloud_xyz_node', + remappings=[('image_rect', '/camera/depth/image_rect_raw'), + ('camera_info', '/camera/depth/camera_info'), + ('image', '/camera/depth/converted_image')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyz_radial.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyz_radial.launch.py new file mode 100644 index 000000000..d578a05ce --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyz_radial.launch.py @@ -0,0 +1,75 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyz_radial.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyzRadialNode', + name='point_cloud_xyz_radial_node', + remappings=[('image_raw', '/camera/depth/image_rect_raw'), + ('camera_info', '/camera/depth/camera_info'), + ('image', '/camera/depth/converted_image')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzi.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzi.launch.py new file mode 100644 index 000000000..191b3f089 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzi.launch.py @@ -0,0 +1,77 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyzi.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # TODO: Realsense camera do not support intensity message + # use color image instead of intensity only for interface test + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyziNode', + name='point_cloud_xyzi', + remappings=[('depth/image_rect', '/camera/aligned_depth_to_color/image_raw'), + ('intensity/image_rect', '/camera/color/image_raw'), + ('intensity/camera_info', '/camera/color/camera_info'), + ('points', '/camera/depth/points')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzi_radial.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzi_radial.launch.py new file mode 100644 index 000000000..721387748 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzi_radial.launch.py @@ -0,0 +1,77 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyzi.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # TODO: Realsense camera do not support intensity message, + # use depth instead of intensity only for interface test + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyziRadialNode', + name='point_cloud_xyzi_radial_node', + remappings=[('depth/image_raw', '/camera/depth/image_rect_raw'), + ('intensity/image_raw', '/camera/depth/image_rect_raw'), + ('intensity/camera_info', '/camera/depth/camera_info'), + ('points', '/camera/depth/points')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzrgb.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzrgb.launch.py new file mode 100644 index 000000000..10b333ac2 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzrgb.launch.py @@ -0,0 +1,77 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyzrgb.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyzrgbNode', + name='point_cloud_xyzrgb_node', + remappings=[('rgb/camera_info', '/camera/color/camera_info'), + ('rgb/image_rect_color', '/camera/color/image_raw'), + ('depth_registered/image_rect', + '/camera/aligned_depth_to_color/image_raw'), + ('points', '/camera/depth_registered/points')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzrgb_radial.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzrgb_radial.launch.py new file mode 100644 index 000000000..263e2397c --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/point_cloud_xyzrgb_radial.launch.py @@ -0,0 +1,90 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +# uncomment if you want to launch rviz too in this launch +# import os +# from ament_index_python.packages import get_package_share_directory +import os + +from ament_index_python.packages import get_package_share_directory +import launch +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/point_cloud_xyzrgb.rviz') + return LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='rviz', default_value='False', + description='Launch RViz for viewing point cloud xyzrgb radial data' + ), + launch_ros.actions.Node( + condition=launch.conditions.IfCondition( + launch.substitutions.LaunchConfiguration('rviz') + ), + package='rviz2', + executable='rviz2', + output='screen', + arguments=['--display-config', default_rviz] + ), + # launch plugin through rclcpp_components container + # make sure remapped topics are available + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::PointCloudXyzrgbRadialNode', + name='point_cloud_xyzrgb_node', + remappings=[('rgb/camera_info', '/camera/color/camera_info'), + ('rgb/image_rect_color', '/camera/color/image_raw'), + ('depth_registered/image_rect', + '/camera/aligned_depth_to_color/image_raw'), + ('points', '/camera/depth_registered/points')] + ), + ], + output='screen', + ), + + # rviz + # launch_ros.actions.Node( + # package='rviz2', executable='rviz2', output='screen', + # arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/register.launch.py b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/register.launch.py new file mode 100644 index 000000000..0eab8878e --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/register.launch.py @@ -0,0 +1,79 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +import os + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription + +import launch_ros.actions +import launch_ros.descriptions + + +def generate_launch_description(): + default_rviz = os.path.join(get_package_share_directory('depth_image_proc'), + 'launch', 'rviz/register.rviz') + return LaunchDescription([ + # install realsense from https://github.com/intel/ros2_intel_realsense + launch_ros.actions.Node( + package='realsense_ros2_camera', node_executable='realsense_ros2_camera', + output='screen'), + + # launch plugin through rclcpp_components container + launch_ros.actions.ComposableNodeContainer( + name='container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=[ + # Driver itself + launch_ros.descriptions.ComposableNode( + package='depth_image_proc', + plugin='depth_image_proc::RegisterNode', + name='register_node', + remappings=[('depth/image_rect', '/camera/depth/image_rect_raw'), + ('depth/camera_info', '/camera/depth/camera_info'), + ('rgb/camera_info', '/camera/color/camera_info'), + ('depth_registered/image_rect', + '/camera/depth_registered/image_rect'), + ('depth_registered/camera_info', + '/camera/depth_registered/camera_info')] + ), + ], + output='screen', + ), + + # rviz + launch_ros.actions.Node( + package='rviz2', node_executable='rviz2', output='screen', + arguments=['--display-config', default_rviz]), + ]) diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/convert_metric.rviz b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/convert_metric.rviz new file mode 100644 index 000000000..d76e1637f --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/convert_metric.rviz @@ -0,0 +1,136 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_raw1 + - /image_raw1/Status1 + - /converted_image1 + - /converted_image1/Status1 + Splitter Ratio: 0.5 + Tree Height: 581 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_raw + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: converted_image + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/converted_image + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_color_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/XYOrbit + 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.7853981852531433 + Target Frame: + Value: XYOrbit (rviz_default_plugins) + Yaw: 0.7853981852531433 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d00000282000000c900fffffffb000000120069006d006100670065005f00720061007701000002c5000000c50000002800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000001e0063006f006e007600650072007400650064005f0069006d0061006700650100000390000000cc0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d00610067006500000003ad000000af0000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000003ae0000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1853 + X: 67 + Y: 27 + converted_image: + collapsed: false + image_raw: + collapsed: false diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/crop_formost.rviz b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/crop_formost.rviz new file mode 100644 index 000000000..f1cf786e6 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/crop_formost.rviz @@ -0,0 +1,134 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_raw1 + - /crop_name1 + Splitter Ratio: 0.5 + Tree Height: 581 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_raw + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: crop_name + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/crop_image + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_color_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/XYOrbit + 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.7853981852531433 + Target Frame: + Value: XYOrbit (rviz_default_plugins) + Yaw: 0.7853981852531433 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d00000282000000c900fffffffb000000120069006d006100670065005f00720061007701000002c5000000c50000002800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000001200630072006f0070005f006e0061006d00650100000390000000cc0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d00610067006500000003ad000000af0000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000003ae0000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1853 + X: 67 + Y: 27 + crop_name: + collapsed: false + image_raw: + collapsed: false diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/disparity.rviz b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/disparity.rviz new file mode 100644 index 000000000..6ffe90c5c --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/disparity.rviz @@ -0,0 +1,134 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /fake left/image_rect1 + - /fake left/disparity1 + Splitter Ratio: 0.5 + Tree Height: 581 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: fake left/image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: fake left/disparity + Normalize Range: true + Queue Size: 10 + Topic: /camera/left/disparity + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_color_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/XYOrbit + 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.7853981852531433 + Target Frame: + Value: XYOrbit (rviz_default_plugins) + Yaw: 0.7853981852531433 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: false + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d00000282000000c900fffffffb0000002800660061006b00650020006c006500660074002f0069006d006100670065005f007200650063007401000002c5000000c50000002800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000002600660061006b00650020006c006500660074002f0064006900730070006100720069007400790100000390000000cc0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d00610067006500000003ad000000af0000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073010000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000003ae0000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: false + Width: 1853 + X: 67 + Y: 27 + fake left/disparity: + collapsed: false + fake left/image_rect: + collapsed: false diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyz.rviz b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyz.rviz new file mode 100644 index 000000000..ae68f102a --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyz.rviz @@ -0,0 +1,151 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_rect1 + - /PointCloud21 + Splitter Ratio: 0.5 + Tree Height: 608 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: /camera/depth/points + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_depth_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 2.125654935836792 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.718582630157471 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d0000029d000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650000000256000000a20000000000000000fb000000140069006d006100670065005f007200650063007401000002e00000017c0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d006100670065000000038b000000d10000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + image_rect: + collapsed: false diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyz_radial.rviz b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyz_radial.rviz new file mode 100644 index 000000000..ae68f102a --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyz_radial.rviz @@ -0,0 +1,151 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_rect1 + - /PointCloud21 + Splitter Ratio: 0.5 + Tree Height: 608 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: /camera/depth/points + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_depth_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 2.125654935836792 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.718582630157471 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d0000029d000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650000000256000000a20000000000000000fb000000140069006d006100670065005f007200650063007401000002e00000017c0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d006100670065000000038b000000d10000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + image_rect: + collapsed: false diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyzi.rviz b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyzi.rviz new file mode 100644 index 000000000..ee87ced30 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyzi.rviz @@ -0,0 +1,151 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /image_rect1 + - /PointCloud21 + Splitter Ratio: 0.5 + Tree Height: 608 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: Intensity + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 255 + Min Color: 0; 0; 0 + Min Intensity: 2 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: /camera/depth/points + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_depth_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 2.125654935836792 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.718582630157471 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d0000029d000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650000000256000000a20000000000000000fb000000140069006d006100670065005f007200650063007401000002e00000017c0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d006100670065000000038b000000d10000000000000000000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + image_rect: + collapsed: false diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyzrgb.rviz b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyzrgb.rviz new file mode 100644 index 000000000..daba8ef8e --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/point_cloud_xyzrgb.rviz @@ -0,0 +1,179 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 78 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /color_image1 + - /depth_image1 + - /registered_depth_image1 + - /PointCloud21 + Splitter Ratio: 0.5 + Tree Height: 392 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: color_image + Normalize Range: true + Queue Size: 10 + Topic: /camera/color/image_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: depth_image + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: registered_depth_image + Normalize Range: true + Queue Size: 10 + Topic: /camera/aligned_depth_to_color/image_raw + Unreliable: false + Value: true + - Alpha: 1 + Autocompute Intensity Bounds: true + Autocompute Value Bounds: + Max Value: 10 + Min Value: -10 + Value: true + Axis: Z + Channel Name: intensity + Class: rviz_default_plugins/PointCloud2 + Color: 255; 255; 255 + Color Transformer: RGB8 + Decay Time: 0 + Enabled: true + Invert Rainbow: false + Max Color: 255; 255; 255 + Max Intensity: 4096 + Min Color: 0; 0; 0 + Min Intensity: 0 + Name: PointCloud2 + Position Transformer: XYZ + Queue Size: 10 + Selectable: true + Size (Pixels): 3 + Size (m): 0.009999999776482582 + Style: Flat Squares + Topic: /camera/depth_registered/points + Unreliable: false + Use Fixed Frame: true + Use rainbow: true + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_color_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 1.8979061841964722 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.718582630157471 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000bfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d00000213000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650100000256000000a20000002800fffffffb0000001600640065007000740068005f0069006d00610067006501000002fe000000a90000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d00610067006501000003ad000000af0000002800ffffff000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + color_image: + collapsed: false + depth_image: + collapsed: false + registered_depth_image: + collapsed: false diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/register.rviz b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/register.rviz new file mode 100644 index 000000000..6191de026 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/launch/rviz/register.rviz @@ -0,0 +1,148 @@ +Panels: + - Class: rviz_common/Displays + Help Height: 0 + Name: Displays + Property Tree Widget: + Expanded: + - /Global Options1 + - /Status1 + - /depth/image_rect1 + - /depth_registered1 + - /color image1 + Splitter Ratio: 0.5 + Tree Height: 366 + - Class: rviz_common/Selection + Name: Selection + - Class: rviz_common/Tool Properties + Expanded: + - /2D Nav Goal1 + - /Publish Point1 + Name: Tool Properties + Splitter Ratio: 0.5886790156364441 + - Class: rviz_common/Views + Expanded: + - /Current View1 + Name: Views + Splitter Ratio: 0.5 +Visualization Manager: + Class: "" + Displays: + - Alpha: 0.5 + Cell Size: 1 + Class: rviz_default_plugins/Grid + Color: 160; 160; 164 + Enabled: true + Line Style: + Line Width: 0.029999999329447746 + Value: Lines + Name: Grid + Normal Cell Count: 0 + Offset: + X: 0 + Y: 0 + Z: 0 + Plane: XY + Plane Cell Count: 10 + Reference Frame: + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: depth/image_rect + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth/image_rect_raw + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: depth_registered + Normalize Range: true + Queue Size: 10 + Topic: /camera/depth_registered/image_rect + Unreliable: false + Value: true + - Class: rviz_default_plugins/Image + Enabled: true + Max Value: 1 + Median window: 5 + Min Value: 0 + Name: color image + Normalize Range: true + Queue Size: 10 + Topic: /camera/color/image_raw + Unreliable: false + Value: true + Enabled: true + Global Options: + Background Color: 48; 48; 48 + Fixed Frame: camera_depth_optical_frame + Frame Rate: 30 + Name: root + Tools: + - 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 + Topic: /initialpose + - Class: rviz_default_plugins/SetGoal + Topic: /move_base_simple/goal + - Class: rviz_default_plugins/PublishPoint + Single click: true + Topic: /clicked_point + Transformation: + Current: + Class: rviz_default_plugins/TF + Value: true + Views: + Current: + Class: rviz_default_plugins/Orbit + Distance: 2.125654935836792 + 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: -1.5597962141036987 + Target Frame: + Value: Orbit (rviz) + Yaw: 4.723582744598389 + Saved: ~ +Window Geometry: + Displays: + collapsed: false + Height: 1145 + Hide Left Dock: false + Hide Right Dock: true + QMainWindow State: 000000ff00000000fd0000000400000000000002740000041ffc020000000dfb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d000001ab000000c900fffffffb0000002000640065007000740068002f0069006d006100670065005f007200650063007401000001ee000000c70000002800fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb000000160063006f006c006f0072005f0069006d0061006700650000000256000000a20000000000000000fb000000160063006f006c006f007200200069006d00610067006501000002bb000000cb0000002800fffffffb0000002c0072006500670069007300740065007200650064005f00640065007000740068005f0069006d006100670065000000038b000000d10000000000000000fb0000002000640065007000740068005f0072006500670069007300740065007200650064010000038c000000d00000002800ffffff000000010000010f0000041ffc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d0000041f000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004420000003efc0100000002fb0000000800540069006d00650100000000000004420000000000000000fb0000000800540069006d00650100000000000004500000000000000000000004c30000041f00000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 + Selection: + collapsed: false + Tool Properties: + collapsed: false + Views: + collapsed: true + Width: 1853 + X: 67 + Y: 27 + color image: + collapsed: false + depth/image_rect: + collapsed: false + depth_registered: + collapsed: false diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.bash b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.bash new file mode 100644 index 000000000..49782f246 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.bash @@ -0,0 +1,46 @@ +# generated from ament_package/template/package_level/local_setup.bash.in + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.dsv b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.dsv new file mode 100644 index 000000000..0c848242c --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.dsv @@ -0,0 +1,3 @@ +source;share/depth_image_proc/environment/ament_prefix_path.sh +source;share/depth_image_proc/environment/library_path.sh +source;share/depth_image_proc/environment/path.sh diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.sh b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.sh new file mode 100644 index 000000000..8e9fc8552 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.sh @@ -0,0 +1,185 @@ +# generated from ament_package/template/package_level/local_setup.sh.in + +# since this file is sourced use either the provided AMENT_CURRENT_PREFIX +# or fall back to the destination set at configure time +: ${AMENT_CURRENT_PREFIX:="/home/DinoSage/rocket_league/src/image_pipeline/install/depth_image_proc"} +if [ ! -d "$AMENT_CURRENT_PREFIX" ]; then + if [ -z "$COLCON_CURRENT_PREFIX" ]; then + echo "The compile time prefix path '$AMENT_CURRENT_PREFIX' doesn't " \ + "exist. Consider sourcing a different extension than '.sh'." 1>&2 + else + AMENT_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" + fi +fi + +# function to append values to environment variables +# using colons as separators and avoiding leading separators +ament_append_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # avoid leading separator + eval _values=\"\$$_listname\" + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + _ament_append_value_IFS=$IFS + unset IFS + eval export $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + IFS=$_ament_append_value_IFS + unset _ament_append_value_IFS + fi + unset _values + + unset _value + unset _listname +} + +# function to append non-duplicate values to environment variables +# using colons as separators and avoiding leading separators +ament_append_unique_value() { + # arguments + _listname=$1 + _value=$2 + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\$$_listname + _duplicate= + _ament_append_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ $_item = $_value ]; then + _duplicate=1 + fi + done + unset _item + + # append only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid leading separator + if [ -z "$_values" ]; then + eval $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + fi + fi + IFS=$_ament_append_unique_value_IFS + unset _ament_append_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# function to prepend non-duplicate values to environment variables +# using colons as separators and avoiding trailing separators +ament_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\"\$$_listname\" + _duplicate= + _ament_prepend_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ "$_item" = "$_value" ]; then + _duplicate=1 + fi + done + unset _item + + # prepend only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid trailing separator + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval export $_listname=\"$_value:\$$_listname\" + #eval echo "prepend list \$$_listname" + fi + fi + IFS=$_ament_prepend_unique_value_IFS + unset _ament_prepend_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# list all environment hooks of this package +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/depth_image_proc/environment/ament_prefix_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/depth_image_proc/environment/library_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/depth_image_proc/environment/path.sh" + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array AMENT_ENVIRONMENT_HOOKS + fi + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + if [ -f "$_hook" ]; then + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + # trace output + if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_hook\"" + fi + . "$_hook" + fi + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +# reset AMENT_CURRENT_PREFIX after each package +# allowing to source multiple package-level setup files +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.zsh b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.zsh new file mode 100644 index 000000000..fe161be53 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/local_setup.zsh @@ -0,0 +1,59 @@ +# generated from ament_package/template/package_level/local_setup.zsh.in + +AMENT_SHELL=zsh + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# function to convert array-like strings into arrays +# to wordaround SH_WORD_SPLIT not being set +ament_zsh_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +# the package-level local_setup file unsets AMENT_CURRENT_PREFIX +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.bash b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.bash new file mode 100644 index 000000000..d1bfaba59 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.bash @@ -0,0 +1,39 @@ +# generated from colcon_bash/shell/template/package.bash.em + +# This script extends the environment for this package. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" +else + _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh script of this package +_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/depth_image_proc/package.sh" + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_bash_COLCON_CURRENT_PREFIX" + +# source bash hooks +_colcon_package_bash_source_script "$COLCON_CURRENT_PREFIX/share/depth_image_proc/local_setup.bash" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_bash_source_script +unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.dsv b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.dsv new file mode 100644 index 000000000..8bc6dc726 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.dsv @@ -0,0 +1,11 @@ +source;share/depth_image_proc/hook/cmake_prefix_path.ps1 +source;share/depth_image_proc/hook/cmake_prefix_path.dsv +source;share/depth_image_proc/hook/cmake_prefix_path.sh +source;share/depth_image_proc/hook/ld_library_path_lib.ps1 +source;share/depth_image_proc/hook/ld_library_path_lib.dsv +source;share/depth_image_proc/hook/ld_library_path_lib.sh +source;share/depth_image_proc/local_setup.bash +source;share/depth_image_proc/local_setup.dsv +source;share/depth_image_proc/local_setup.ps1 +source;share/depth_image_proc/local_setup.sh +source;share/depth_image_proc/local_setup.zsh diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.ps1 b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.ps1 new file mode 100644 index 000000000..89996e32c --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.ps1 @@ -0,0 +1,117 @@ +# generated from colcon_powershell/shell/template/package.ps1.em + +# function to append a value to a variable +# which uses colons as separators +# duplicates as well as leading separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_append_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + $_duplicate="" + # start with no values + $_all_values="" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -eq $_value) { + $_duplicate="1" + } + if ($_all_values) { + $_all_values="${_all_values};$_" + } else { + $_all_values="$_" + } + } + } + } + # append only non-duplicates + if (!$_duplicate) { + # avoid leading separator + if ($_all_values) { + $_all_values="${_all_values};${_value}" + } else { + $_all_values="${_value}" + } + } + + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_prepend_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + # start with the new value + $_all_values="$_value" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -ne $_value) { + # keep non-duplicate values + $_all_values="${_all_values};$_" + } + } + } + } + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +function colcon_package_source_powershell_script { + param ( + $_colcon_package_source_powershell_script + ) + # source script with conditional trace output + if (Test-Path $_colcon_package_source_powershell_script) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_package_source_powershell_script'" + } + . "$_colcon_package_source_powershell_script" + } else { + Write-Error "not found: '$_colcon_package_source_powershell_script'" + } +} + + +# a powershell script is able to determine its own path +# the prefix is two levels up from the package specific share directory +$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName + +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/depth_image_proc/hook/cmake_prefix_path.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/depth_image_proc/hook/ld_library_path_lib.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/depth_image_proc/local_setup.ps1" + +Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.sh b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.sh new file mode 100644 index 000000000..17ca96b6e --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.sh @@ -0,0 +1,88 @@ +# generated from colcon_core/shell/template/package.sh.em + +# This script extends the environment for this package. + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prepend_unique_value_IFS=$IFS + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set in zsh + if [ "$(command -v colcon_zsh_convert_to_array)" ]; then + colcon_zsh_convert_to_array _values + fi + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS=$_colcon_prepend_unique_value_IFS + unset _colcon_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/DinoSage/rocket_league/src/image_pipeline/install/depth_image_proc" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_package_sh_COLCON_CURRENT_PREFIX + return 1 + fi + COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" +fi +unset _colcon_package_sh_COLCON_CURRENT_PREFIX + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh hooks +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/depth_image_proc/hook/cmake_prefix_path.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/depth_image_proc/hook/ld_library_path_lib.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/depth_image_proc/local_setup.sh" + +unset _colcon_package_sh_source_script +unset COLCON_CURRENT_PREFIX + +# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.xml b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.xml new file mode 100644 index 000000000..1ee00d2a9 --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.xml @@ -0,0 +1,50 @@ + + + + depth_image_proc + 4.0.0 + + + Contains components for processing depth images such as those + produced by OpenNI camera. Functions include creating disparity + images and point clouds, as well as registering (reprojecting) + a depth image into another camera frame. + + + + Vincent Rabaud + Joshua Whitley + Chris Ye + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/depth_image_proc/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Patrick Mihelich + + ament_cmake_auto + + class_loader + + cv_bridge + image_geometry + image_transport + libopencv-dev + message_filters + rclcpp + rclcpp_components + sensor_msgs + stereo_msgs + tf2 + tf2_eigen + tf2_ros + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.zsh b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.zsh new file mode 100644 index 000000000..df05aab8b --- /dev/null +++ b/src/image_pipeline/install/depth_image_proc/share/depth_image_proc/package.zsh @@ -0,0 +1,50 @@ +# generated from colcon_zsh/shell/template/package.zsh.em + +# This script extends the environment for this package. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" +else + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +colcon_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# source sh script of this package +_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/depth_image_proc/package.sh" +unset convert_zsh_to_array + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_zsh_COLCON_CURRENT_PREFIX" + +# source zsh hooks +_colcon_package_zsh_source_script "$COLCON_CURRENT_PREFIX/share/depth_image_proc/local_setup.zsh" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_zsh_source_script +unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_pipeline/share/ament_index/resource_index/package_run_dependencies/image_pipeline b/src/image_pipeline/install/image_pipeline/share/ament_index/resource_index/package_run_dependencies/image_pipeline new file mode 100644 index 000000000..fe594c8f6 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/ament_index/resource_index/package_run_dependencies/image_pipeline @@ -0,0 +1 @@ +camera_calibration;depth_image_proc;image_proc;image_publisher;image_rotate;image_view;stereo_image_proc;ament_lint_auto;ament_cmake_lint_cmake;ament_cmake_xmllint \ No newline at end of file diff --git a/src/image_pipeline/install/image_pipeline/share/ament_index/resource_index/packages/image_pipeline b/src/image_pipeline/install/image_pipeline/share/ament_index/resource_index/packages/image_pipeline new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/image_pipeline/share/ament_index/resource_index/parent_prefix_path/image_pipeline b/src/image_pipeline/install/image_pipeline/share/ament_index/resource_index/parent_prefix_path/image_pipeline new file mode 100644 index 000000000..2e94fcecd --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/ament_index/resource_index/parent_prefix_path/image_pipeline @@ -0,0 +1 @@ +/home/DinoSage/rocket_league/src/image_pipeline/install/stereo_image_proc:/home/DinoSage/rocket_league/src/image_pipeline/install/image_proc:/home/DinoSage/rocket_league/src/image_pipeline/install/tracetools_image_pipeline:/home/DinoSage/rocket_league/src/image_pipeline/install/image_rotate:/home/DinoSage/rocket_league/src/image_pipeline/install/depth_image_proc:/home/DinoSage/rocket_league/src/image_pipeline/install/camera_calibration:/opt/ros/iron \ No newline at end of file diff --git a/src/image_pipeline/install/image_pipeline/share/colcon-core/packages/image_pipeline b/src/image_pipeline/install/image_pipeline/share/colcon-core/packages/image_pipeline new file mode 100644 index 000000000..45e937ac3 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/colcon-core/packages/image_pipeline @@ -0,0 +1 @@ +camera_calibration:depth_image_proc:image_proc:image_publisher:image_rotate:image_view:stereo_image_proc \ No newline at end of file diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/cmake/image_pipelineConfig-version.cmake b/src/image_pipeline/install/image_pipeline/share/image_pipeline/cmake/image_pipelineConfig-version.cmake new file mode 100644 index 000000000..81dc15e08 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/cmake/image_pipelineConfig-version.cmake @@ -0,0 +1,14 @@ +# generated from ament/cmake/core/templates/nameConfig-version.cmake.in +set(PACKAGE_VERSION "4.0.0") + +set(PACKAGE_VERSION_EXACT False) +set(PACKAGE_VERSION_COMPATIBLE False) + +if("${PACKAGE_FIND_VERSION}" VERSION_EQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT True) + set(PACKAGE_VERSION_COMPATIBLE True) +endif() + +if("${PACKAGE_FIND_VERSION}" VERSION_LESS "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE True) +endif() diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/cmake/image_pipelineConfig.cmake b/src/image_pipeline/install/image_pipeline/share/image_pipeline/cmake/image_pipelineConfig.cmake new file mode 100644 index 000000000..1fcb8d158 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/cmake/image_pipelineConfig.cmake @@ -0,0 +1,42 @@ +# generated from ament/cmake/core/templates/nameConfig.cmake.in + +# prevent multiple inclusion +if(_image_pipeline_CONFIG_INCLUDED) + # ensure to keep the found flag the same + if(NOT DEFINED image_pipeline_FOUND) + # explicitly set it to FALSE, otherwise CMake will set it to TRUE + set(image_pipeline_FOUND FALSE) + elseif(NOT image_pipeline_FOUND) + # use separate condition to avoid uninitialized variable warning + set(image_pipeline_FOUND FALSE) + endif() + return() +endif() +set(_image_pipeline_CONFIG_INCLUDED TRUE) + +# output package information +if(NOT image_pipeline_FIND_QUIETLY) + message(STATUS "Found image_pipeline: 4.0.0 (${image_pipeline_DIR})") +endif() + +# warn when using a deprecated package +if(NOT "" STREQUAL "") + set(_msg "Package 'image_pipeline' is deprecated") + # append custom deprecation text if available + if(NOT "" STREQUAL "TRUE") + set(_msg "${_msg} ()") + endif() + # optionally quiet the deprecation message + if(NOT image_pipeline_DEPRECATED_QUIET) + message(DEPRECATION "${_msg}") + endif() +endif() + +# flag package as ament-based to distinguish it after being find_package()-ed +set(image_pipeline_FOUND_AMENT_PACKAGE TRUE) + +# include all config extra files +set(_extras "") +foreach(_extra ${_extras}) + include("${image_pipeline_DIR}/${_extra}") +endforeach() diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/ament_prefix_path.dsv b/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/ament_prefix_path.dsv new file mode 100644 index 000000000..79d4c95b5 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/ament_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;AMENT_PREFIX_PATH; diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/ament_prefix_path.sh b/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/ament_prefix_path.sh new file mode 100644 index 000000000..02e441b75 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/ament_prefix_path.sh @@ -0,0 +1,4 @@ +# copied from +# ament_cmake_core/cmake/environment_hooks/environment/ament_prefix_path.sh + +ament_prepend_unique_value AMENT_PREFIX_PATH "$AMENT_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/path.dsv b/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/path.dsv new file mode 100644 index 000000000..b94426af0 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate-if-exists;PATH;bin diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/path.sh b/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/path.sh new file mode 100644 index 000000000..e59b749a8 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/environment/path.sh @@ -0,0 +1,5 @@ +# copied from ament_cmake_core/cmake/environment_hooks/environment/path.sh + +if [ -d "$AMENT_CURRENT_PREFIX/bin" ]; then + ament_prepend_unique_value PATH "$AMENT_CURRENT_PREFIX/bin" +fi diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/hook/cmake_prefix_path.dsv b/src/image_pipeline/install/image_pipeline/share/image_pipeline/hook/cmake_prefix_path.dsv new file mode 100644 index 000000000..e119f32cb --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/hook/cmake_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;CMAKE_PREFIX_PATH; diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/hook/cmake_prefix_path.ps1 b/src/image_pipeline/install/image_pipeline/share/image_pipeline/hook/cmake_prefix_path.ps1 new file mode 100644 index 000000000..d03facc1a --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/hook/cmake_prefix_path.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value CMAKE_PREFIX_PATH "$env:COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/hook/cmake_prefix_path.sh b/src/image_pipeline/install/image_pipeline/share/image_pipeline/hook/cmake_prefix_path.sh new file mode 100644 index 000000000..a948e685b --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/hook/cmake_prefix_path.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value CMAKE_PREFIX_PATH "$COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.bash b/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.bash new file mode 100644 index 000000000..49782f246 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.bash @@ -0,0 +1,46 @@ +# generated from ament_package/template/package_level/local_setup.bash.in + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.dsv b/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.dsv new file mode 100644 index 000000000..76f29dcdb --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.dsv @@ -0,0 +1,2 @@ +source;share/image_pipeline/environment/ament_prefix_path.sh +source;share/image_pipeline/environment/path.sh diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.sh b/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.sh new file mode 100644 index 000000000..e4fd81490 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.sh @@ -0,0 +1,184 @@ +# generated from ament_package/template/package_level/local_setup.sh.in + +# since this file is sourced use either the provided AMENT_CURRENT_PREFIX +# or fall back to the destination set at configure time +: ${AMENT_CURRENT_PREFIX:="/home/DinoSage/rocket_league/src/image_pipeline/install/image_pipeline"} +if [ ! -d "$AMENT_CURRENT_PREFIX" ]; then + if [ -z "$COLCON_CURRENT_PREFIX" ]; then + echo "The compile time prefix path '$AMENT_CURRENT_PREFIX' doesn't " \ + "exist. Consider sourcing a different extension than '.sh'." 1>&2 + else + AMENT_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" + fi +fi + +# function to append values to environment variables +# using colons as separators and avoiding leading separators +ament_append_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # avoid leading separator + eval _values=\"\$$_listname\" + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + _ament_append_value_IFS=$IFS + unset IFS + eval export $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + IFS=$_ament_append_value_IFS + unset _ament_append_value_IFS + fi + unset _values + + unset _value + unset _listname +} + +# function to append non-duplicate values to environment variables +# using colons as separators and avoiding leading separators +ament_append_unique_value() { + # arguments + _listname=$1 + _value=$2 + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\$$_listname + _duplicate= + _ament_append_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ $_item = $_value ]; then + _duplicate=1 + fi + done + unset _item + + # append only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid leading separator + if [ -z "$_values" ]; then + eval $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + fi + fi + IFS=$_ament_append_unique_value_IFS + unset _ament_append_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# function to prepend non-duplicate values to environment variables +# using colons as separators and avoiding trailing separators +ament_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\"\$$_listname\" + _duplicate= + _ament_prepend_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ "$_item" = "$_value" ]; then + _duplicate=1 + fi + done + unset _item + + # prepend only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid trailing separator + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval export $_listname=\"$_value:\$$_listname\" + #eval echo "prepend list \$$_listname" + fi + fi + IFS=$_ament_prepend_unique_value_IFS + unset _ament_prepend_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# list all environment hooks of this package +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/image_pipeline/environment/ament_prefix_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/image_pipeline/environment/path.sh" + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array AMENT_ENVIRONMENT_HOOKS + fi + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + if [ -f "$_hook" ]; then + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + # trace output + if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_hook\"" + fi + . "$_hook" + fi + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +# reset AMENT_CURRENT_PREFIX after each package +# allowing to source multiple package-level setup files +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.zsh b/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.zsh new file mode 100644 index 000000000..fe161be53 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/local_setup.zsh @@ -0,0 +1,59 @@ +# generated from ament_package/template/package_level/local_setup.zsh.in + +AMENT_SHELL=zsh + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# function to convert array-like strings into arrays +# to wordaround SH_WORD_SPLIT not being set +ament_zsh_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +# the package-level local_setup file unsets AMENT_CURRENT_PREFIX +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.bash b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.bash new file mode 100644 index 000000000..360e1a8e7 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.bash @@ -0,0 +1,39 @@ +# generated from colcon_bash/shell/template/package.bash.em + +# This script extends the environment for this package. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" +else + _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh script of this package +_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/image_pipeline/package.sh" + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_bash_COLCON_CURRENT_PREFIX" + +# source bash hooks +_colcon_package_bash_source_script "$COLCON_CURRENT_PREFIX/share/image_pipeline/local_setup.bash" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_bash_source_script +unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.dsv b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.dsv new file mode 100644 index 000000000..0b7dcdf8e --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.dsv @@ -0,0 +1,8 @@ +source;share/image_pipeline/hook/cmake_prefix_path.ps1 +source;share/image_pipeline/hook/cmake_prefix_path.dsv +source;share/image_pipeline/hook/cmake_prefix_path.sh +source;share/image_pipeline/local_setup.bash +source;share/image_pipeline/local_setup.dsv +source;share/image_pipeline/local_setup.ps1 +source;share/image_pipeline/local_setup.sh +source;share/image_pipeline/local_setup.zsh diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.ps1 b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.ps1 new file mode 100644 index 000000000..b4b82658a --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.ps1 @@ -0,0 +1,116 @@ +# generated from colcon_powershell/shell/template/package.ps1.em + +# function to append a value to a variable +# which uses colons as separators +# duplicates as well as leading separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_append_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + $_duplicate="" + # start with no values + $_all_values="" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -eq $_value) { + $_duplicate="1" + } + if ($_all_values) { + $_all_values="${_all_values};$_" + } else { + $_all_values="$_" + } + } + } + } + # append only non-duplicates + if (!$_duplicate) { + # avoid leading separator + if ($_all_values) { + $_all_values="${_all_values};${_value}" + } else { + $_all_values="${_value}" + } + } + + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_prepend_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + # start with the new value + $_all_values="$_value" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -ne $_value) { + # keep non-duplicate values + $_all_values="${_all_values};$_" + } + } + } + } + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +function colcon_package_source_powershell_script { + param ( + $_colcon_package_source_powershell_script + ) + # source script with conditional trace output + if (Test-Path $_colcon_package_source_powershell_script) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_package_source_powershell_script'" + } + . "$_colcon_package_source_powershell_script" + } else { + Write-Error "not found: '$_colcon_package_source_powershell_script'" + } +} + + +# a powershell script is able to determine its own path +# the prefix is two levels up from the package specific share directory +$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName + +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_pipeline/hook/cmake_prefix_path.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_pipeline/local_setup.ps1" + +Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.sh b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.sh new file mode 100644 index 000000000..33829db88 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.sh @@ -0,0 +1,87 @@ +# generated from colcon_core/shell/template/package.sh.em + +# This script extends the environment for this package. + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prepend_unique_value_IFS=$IFS + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set in zsh + if [ "$(command -v colcon_zsh_convert_to_array)" ]; then + colcon_zsh_convert_to_array _values + fi + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS=$_colcon_prepend_unique_value_IFS + unset _colcon_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/DinoSage/rocket_league/src/image_pipeline/install/image_pipeline" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_package_sh_COLCON_CURRENT_PREFIX + return 1 + fi + COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" +fi +unset _colcon_package_sh_COLCON_CURRENT_PREFIX + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh hooks +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_pipeline/hook/cmake_prefix_path.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_pipeline/local_setup.sh" + +unset _colcon_package_sh_source_script +unset COLCON_CURRENT_PREFIX + +# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.xml b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.xml new file mode 100644 index 000000000..97b766179 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.xml @@ -0,0 +1,38 @@ + + + + image_pipeline + 4.0.0 + image_pipeline fills the gap between getting raw images from a camera driver and higher-level vision processing. + + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/image_pipeline/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Patrick Mihelich + James Bowman + + ament_cmake + + + camera_calibration + depth_image_proc + image_proc + image_publisher + image_rotate + image_view + stereo_image_proc + + ament_lint_auto + ament_cmake_lint_cmake + ament_cmake_xmllint + + + ament_cmake + + diff --git a/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.zsh b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.zsh new file mode 100644 index 000000000..693cf50d4 --- /dev/null +++ b/src/image_pipeline/install/image_pipeline/share/image_pipeline/package.zsh @@ -0,0 +1,50 @@ +# generated from colcon_zsh/shell/template/package.zsh.em + +# This script extends the environment for this package. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" +else + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +colcon_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# source sh script of this package +_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/image_pipeline/package.sh" +unset convert_zsh_to_array + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_zsh_COLCON_CURRENT_PREFIX" + +# source zsh hooks +_colcon_package_zsh_source_script "$COLCON_CURRENT_PREFIX/share/image_pipeline/local_setup.zsh" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_zsh_source_script +unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_proc/bin/image_proc b/src/image_pipeline/install/image_proc/bin/image_proc new file mode 100755 index 000000000..41a360d3f Binary files /dev/null and b/src/image_pipeline/install/image_proc/bin/image_proc differ diff --git a/src/image_pipeline/install/image_proc/include/image_proc/crop_decimate.hpp b/src/image_pipeline/install/image_proc/include/image_proc/crop_decimate.hpp new file mode 100644 index 000000000..95e24e874 --- /dev/null +++ b/src/image_pipeline/install/image_proc/include/image_proc/crop_decimate.hpp @@ -0,0 +1,81 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Steve Macenski, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__CROP_DECIMATE_HPP_ +#define IMAGE_PROC__CROP_DECIMATE_HPP_ + +#include + +#include "cv_bridge/cv_bridge.hpp" + +#include +#include +#include +#include + +namespace image_proc +{ + +enum class CropDecimateModes +{ + CropDecimate_NN = 0, + CropDecimate_Linear = 1, + CropDecimate_Cubic = 2, + CropDecimate_Area = 3, + CropDecimate_Lanczos4 = 4 +}; + +using cv_bridge::CvImage; +using cv_bridge::CvImageConstPtr; +using cv_bridge::toCvShare; + +class CropDecimateNode : public rclcpp::Node +{ +public: + explicit CropDecimateNode(const rclcpp::NodeOptions &); + +private: + image_transport::CameraSubscriber sub_; + image_transport::CameraPublisher pub_; + int queue_size_; + std::string target_frame_id_; + int decimation_x_, decimation_y_, offset_x_, offset_y_, width_, height_; + CropDecimateModes interpolation_; + + void imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr info_msg); +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__CROP_DECIMATE_HPP_ diff --git a/src/image_pipeline/install/image_proc/include/image_proc/crop_non_zero.hpp b/src/image_pipeline/install/image_proc/include/image_proc/crop_non_zero.hpp new file mode 100644 index 000000000..9b6b81bf2 --- /dev/null +++ b/src/image_pipeline/install/image_proc/include/image_proc/crop_non_zero.hpp @@ -0,0 +1,62 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Steve Macenski, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__CROP_NON_ZERO_HPP_ +#define IMAGE_PROC__CROP_NON_ZERO_HPP_ + +#include + +#include +#include +#include + +namespace image_proc +{ + +class CropNonZeroNode : public rclcpp::Node +{ +public: + explicit CropNonZeroNode(const rclcpp::NodeOptions &); + +private: + // Subscriptions + image_transport::Subscriber sub_raw_; + + // Publications + std::mutex connect_mutex_; + + image_transport::Publisher pub_; + + void imageCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg); +}; +} // namespace image_proc +#endif // IMAGE_PROC__CROP_NON_ZERO_HPP_ diff --git a/src/image_pipeline/install/image_proc/include/image_proc/debayer.hpp b/src/image_pipeline/install/image_proc/include/image_proc/debayer.hpp new file mode 100644 index 000000000..f4847fc2c --- /dev/null +++ b/src/image_pipeline/install/image_proc/include/image_proc/debayer.hpp @@ -0,0 +1,68 @@ +// Copyright 2008, 2019, Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__DEBAYER_HPP_ +#define IMAGE_PROC__DEBAYER_HPP_ + +#include +#include +#include + +namespace image_proc +{ + +class DebayerNode + : public rclcpp::Node +{ +public: + explicit DebayerNode(const rclcpp::NodeOptions &); + +private: + image_transport::Subscriber sub_raw_; + + int debayer_; + + int debayer_bilinear_ = 0; + int debayer_edgeaware_ = 1; + int debayer_edgeaware_weighted_ = 2; + int debayer_vng_ = 3; + + image_transport::Publisher pub_mono_; + image_transport::Publisher pub_color_; + + void connectCb(); + void imageCb(const sensor_msgs::msg::Image::ConstSharedPtr & raw_msg); +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__DEBAYER_HPP_ diff --git a/src/image_pipeline/install/image_proc/include/image_proc/edge_aware.hpp b/src/image_pipeline/install/image_proc/include/image_proc/edge_aware.hpp new file mode 100644 index 000000000..3f5bb2288 --- /dev/null +++ b/src/image_pipeline/install/image_proc/include/image_proc/edge_aware.hpp @@ -0,0 +1,48 @@ +// Copyright 2008, 2019, Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__EDGE_AWARE_HPP_ +#define IMAGE_PROC__EDGE_AWARE_HPP_ + +#include + +// Edge-aware debayering algorithms, intended for eventual inclusion in OpenCV. + +namespace image_proc +{ + +void debayerEdgeAware(const cv::Mat & bayer, cv::Mat & color); +void debayerEdgeAwareWeighted(const cv::Mat & bayer, cv::Mat & color); + +} // namespace image_proc + +#endif // IMAGE_PROC__EDGE_AWARE_HPP_ diff --git a/src/image_pipeline/install/image_proc/include/image_proc/processor.hpp b/src/image_pipeline/install/image_proc/include/image_proc/processor.hpp new file mode 100644 index 000000000..b3480f038 --- /dev/null +++ b/src/image_pipeline/install/image_proc/include/image_proc/processor.hpp @@ -0,0 +1,82 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__PROCESSOR_HPP_ +#define IMAGE_PROC__PROCESSOR_HPP_ + +#include + +#include "image_geometry/pinhole_camera_model.hpp" + +#include +#include + +namespace image_proc +{ + +struct ImageSet +{ + std::string color_encoding; + cv::Mat mono; + cv::Mat rect; + cv::Mat color; + cv::Mat rect_color; +}; + +class Processor +{ +public: + Processor() + : interpolation_(cv::INTER_LINEAR) + { + } + + int interpolation_; + + enum + { + MONO = 1 << 0, + RECT = 1 << 1, + COLOR = 1 << 2, + RECT_COLOR = 1 << 3, + ALL = MONO | RECT | COLOR | RECT_COLOR + }; + + bool process( + const sensor_msgs::msg::Image::ConstSharedPtr & raw_image, + const image_geometry::PinholeCameraModel & model, + ImageSet & output, int flags = ALL) const; +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__PROCESSOR_HPP_ diff --git a/src/image_pipeline/install/image_proc/include/image_proc/rectify.hpp b/src/image_pipeline/install/image_proc/include/image_proc/rectify.hpp new file mode 100644 index 000000000..4b80b25b2 --- /dev/null +++ b/src/image_pipeline/install/image_proc/include/image_proc/rectify.hpp @@ -0,0 +1,73 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__RECTIFY_HPP_ +#define IMAGE_PROC__RECTIFY_HPP_ + +#include + +#include "image_geometry/pinhole_camera_model.hpp" + +#include +#include +#include +#include + +namespace image_proc +{ + +class RectifyNode + : public rclcpp::Node +{ +public: + explicit RectifyNode(const rclcpp::NodeOptions &); + +private: + image_transport::CameraSubscriber sub_camera_; + + int queue_size_; + int interpolation; + std::mutex connect_mutex_; + image_transport::Publisher pub_rect_; + + // Processing state (note: only safe because we're using single-threaded NodeHandle!) + image_geometry::PinholeCameraModel model_; + + void subscribeToCamera(const rmw_qos_profile_t & qos_profile); + void imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr & image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & info_msg); +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__RECTIFY_HPP_ diff --git a/src/image_pipeline/install/image_proc/include/image_proc/resize.hpp b/src/image_pipeline/install/image_proc/include/image_proc/resize.hpp new file mode 100644 index 000000000..d51bca4b9 --- /dev/null +++ b/src/image_pipeline/install/image_proc/include/image_proc/resize.hpp @@ -0,0 +1,76 @@ +// Copyright 2008, 2019 Willow Garage, Inc., Andreas Klintberg, Joshua Whitley +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__RESIZE_HPP_ +#define IMAGE_PROC__RESIZE_HPP_ + +#include + +#include +#include +#include +#include + +namespace image_proc +{ + +class ResizeNode + : public rclcpp::Node +{ +public: + explicit ResizeNode(const rclcpp::NodeOptions &); + +protected: + image_transport::CameraPublisher pub_image_; + image_transport::CameraSubscriber sub_image_; + + int interpolation_; + bool use_scale_; + double scale_height_; + double scale_width_; + int height_; + int width_; + + cv_bridge::CvImage scaled_cv_; + + std::mutex connect_mutex_; + + void connectCb(); + + void imageCb( + sensor_msgs::msg::Image::ConstSharedPtr image_msg, + sensor_msgs::msg::CameraInfo::ConstSharedPtr info_msg); +}; + +} // namespace image_proc + +#endif // IMAGE_PROC__RESIZE_HPP_ diff --git a/src/image_pipeline/install/image_proc/include/image_proc/utils.hpp b/src/image_pipeline/install/image_proc/include/image_proc/utils.hpp new file mode 100644 index 000000000..6be4b88f7 --- /dev/null +++ b/src/image_pipeline/install/image_proc/include/image_proc/utils.hpp @@ -0,0 +1,69 @@ +// Copyright 2023 Willow Garage, Inc., Michal Wojcik +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of {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 OWNER 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. + +#ifndef IMAGE_PROC__UTILS_HPP_ +#define IMAGE_PROC__UTILS_HPP_ + +#include + +#include + +namespace image_proc +{ + +rmw_qos_profile_t getTopicQosProfile(rclcpp::Node * node, const std::string & topic) +{ + /** + * Given a topic name, get the QoS profile with which it is being published. + * Replaces history and depth settings with default sensor values since they cannot be retrieved. + * @param node pointer to the ROS node + * @param topic name of the topic + * @returns QoS profile of the publisher to the topic. If there are several publishers, it returns + * returns the profile of the first one on the list. If no publishers exist, it returns + * the sensor data profile. + */ + std::string topic_resolved = node->get_node_base_interface()->resolve_topic_or_service_name( + topic, false); + auto topics_info = node->get_publishers_info_by_topic(topic_resolved); + if (topics_info.size()) { + auto profile = topics_info[0].qos_profile().get_rmw_qos_profile(); + profile.history = rmw_qos_profile_sensor_data.history; + profile.depth = rmw_qos_profile_sensor_data.depth; + return profile; + } else { + return rmw_qos_profile_sensor_data; + } +} + +} // namespace image_proc + +#endif // IMAGE_PROC__UTILS_HPP_ diff --git a/src/image_pipeline/install/image_proc/share/ament_index/resource_index/package_run_dependencies/image_proc b/src/image_pipeline/install/image_proc/share/ament_index/resource_index/package_run_dependencies/image_proc new file mode 100644 index 000000000..ea6d43e13 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/ament_index/resource_index/package_run_dependencies/image_proc @@ -0,0 +1 @@ +cv_bridge;image_geometry;image_transport;libopencv-dev;rclcpp;rclcpp_components;rcutils;sensor_msgs;tracetools_image_pipeline;ament_lint_auto;ament_lint_common \ No newline at end of file diff --git a/src/image_pipeline/install/image_proc/share/ament_index/resource_index/packages/image_proc b/src/image_pipeline/install/image_proc/share/ament_index/resource_index/packages/image_proc new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/image_proc/share/ament_index/resource_index/parent_prefix_path/image_proc b/src/image_pipeline/install/image_proc/share/ament_index/resource_index/parent_prefix_path/image_proc new file mode 100644 index 000000000..a342499e7 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/ament_index/resource_index/parent_prefix_path/image_proc @@ -0,0 +1 @@ +/home/DinoSage/rocket_league/src/image_pipeline/install/tracetools_image_pipeline:/opt/ros/iron \ No newline at end of file diff --git a/src/image_pipeline/install/image_proc/share/ament_index/resource_index/rclcpp_components/image_proc b/src/image_pipeline/install/image_proc/share/ament_index/resource_index/rclcpp_components/image_proc new file mode 100644 index 000000000..86106c9b6 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/ament_index/resource_index/rclcpp_components/image_proc @@ -0,0 +1,5 @@ +image_proc::RectifyNode;lib/librectify.so +image_proc::DebayerNode;lib/libdebayer.so +image_proc::ResizeNode;lib/libresize.so +image_proc::CropDecimateNode;lib/libcrop_decimate.so +image_proc::CropNonZeroNode;lib/libcrop_non_zero.so diff --git a/src/image_pipeline/install/image_proc/share/colcon-core/packages/image_proc b/src/image_pipeline/install/image_proc/share/colcon-core/packages/image_proc new file mode 100644 index 000000000..4e8caa136 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/colcon-core/packages/image_proc @@ -0,0 +1 @@ +cv_bridge:image_geometry:image_transport:libopencv-dev:rclcpp:rclcpp_components:rcutils:sensor_msgs:tracetools_image_pipeline \ No newline at end of file diff --git a/src/image_pipeline/install/image_proc/share/image_proc/cmake/ament_cmake_export_dependencies-extras.cmake b/src/image_pipeline/install/image_proc/share/image_proc/cmake/ament_cmake_export_dependencies-extras.cmake new file mode 100644 index 000000000..e5a362adb --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/cmake/ament_cmake_export_dependencies-extras.cmake @@ -0,0 +1,92 @@ +# generated from ament_cmake_export_dependencies/cmake/ament_cmake_export_dependencies-extras.cmake.in + +set(_exported_dependencies "cv_bridge;image_geometry;image_transport;rclcpp;rclcpp_components;rcutils;sensor_msgs;tracetools_image_pipeline") + +find_package(ament_cmake_libraries QUIET REQUIRED) + +# find_package() all dependencies +# and append their DEFINITIONS INCLUDE_DIRS, LIBRARIES, and LINK_FLAGS +# variables to image_proc_DEFINITIONS, image_proc_INCLUDE_DIRS, +# image_proc_LIBRARIES, and image_proc_LINK_FLAGS. +# Additionally collect the direct dependency names in +# image_proc_DEPENDENCIES as well as the recursive dependency names +# in image_proc_RECURSIVE_DEPENDENCIES. +if(NOT _exported_dependencies STREQUAL "") + find_package(ament_cmake_core QUIET REQUIRED) + set(image_proc_DEPENDENCIES ${_exported_dependencies}) + set(image_proc_RECURSIVE_DEPENDENCIES ${_exported_dependencies}) + set(_libraries) + foreach(_dep ${_exported_dependencies}) + if(NOT ${_dep}_FOUND) + find_package("${_dep}" QUIET REQUIRED) + endif() + # if a package provides modern CMake interface targets use them + # exclusively assuming the classic CMake variables only exist for + # backward compatibility + set(use_modern_cmake FALSE) + if(NOT "${${_dep}_TARGETS}" STREQUAL "") + foreach(_target ${${_dep}_TARGETS}) + # only use actual targets + # in case a package uses this variable for other content + if(TARGET "${_target}") + get_target_property(_include_dirs ${_target} INTERFACE_INCLUDE_DIRECTORIES) + if(_include_dirs) + list_append_unique(image_proc_INCLUDE_DIRS "${_include_dirs}") + endif() + + get_target_property(_imported_configurations ${_target} IMPORTED_CONFIGURATIONS) + if(_imported_configurations) + string(TOUPPER "${_imported_configurations}" _imported_configurations) + if(DEBUG_CONFIGURATIONS) + string(TOUPPER "${DEBUG_CONFIGURATIONS}" _debug_configurations_uppercase) + else() + set(_debug_configurations_uppercase "DEBUG") + endif() + foreach(_imported_config ${_imported_configurations}) + get_target_property(_imported_implib ${_target} IMPORTED_IMPLIB_${_imported_config}) + if(_imported_implib) + set(_imported_implib_config "optimized") + if(${_imported_config} IN_LIST _debug_configurations_uppercase) + set(_imported_implib_config "debug") + endif() + list(APPEND _libraries ${_imported_implib_config} ${_imported_implib}) + else() + get_target_property(_imported_location ${_target} IMPORTED_LOCATION_${_imported_config}) + if(_imported_location) + list(APPEND _libraries "${_imported_location}") + endif() + endif() + endforeach() + endif() + + get_target_property(_link_libraries ${_target} INTERFACE_LINK_LIBRARIES) + if(_link_libraries) + list(APPEND _libraries "${_link_libraries}") + endif() + set(use_modern_cmake TRUE) + endif() + endforeach() + endif() + if(NOT use_modern_cmake) + if(${_dep}_DEFINITIONS) + list_append_unique(image_proc_DEFINITIONS "${${_dep}_DEFINITIONS}") + endif() + if(${_dep}_INCLUDE_DIRS) + list_append_unique(image_proc_INCLUDE_DIRS "${${_dep}_INCLUDE_DIRS}") + endif() + if(${_dep}_LIBRARIES) + list(APPEND _libraries "${${_dep}_LIBRARIES}") + endif() + if(${_dep}_LINK_FLAGS) + list_append_unique(image_proc_LINK_FLAGS "${${_dep}_LINK_FLAGS}") + endif() + if(${_dep}_RECURSIVE_DEPENDENCIES) + list_append_unique(image_proc_RECURSIVE_DEPENDENCIES "${${_dep}_RECURSIVE_DEPENDENCIES}") + endif() + endif() + if(_libraries) + ament_libraries_deduplicate(_libraries "${_libraries}") + list(APPEND image_proc_LIBRARIES "${_libraries}") + endif() + endforeach() +endif() diff --git a/src/image_pipeline/install/image_proc/share/image_proc/cmake/ament_cmake_export_include_directories-extras.cmake b/src/image_pipeline/install/image_proc/share/image_proc/cmake/ament_cmake_export_include_directories-extras.cmake new file mode 100644 index 000000000..978018a30 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/cmake/ament_cmake_export_include_directories-extras.cmake @@ -0,0 +1,16 @@ +# generated from ament_cmake_export_include_directories/cmake/ament_cmake_export_include_directories-extras.cmake.in + +set(_exported_include_dirs "${image_proc_DIR}/../../../include") + +# append include directories to image_proc_INCLUDE_DIRS +# warn about not existing paths +if(NOT _exported_include_dirs STREQUAL "") + find_package(ament_cmake_core QUIET REQUIRED) + foreach(_exported_include_dir ${_exported_include_dirs}) + if(NOT IS_DIRECTORY "${_exported_include_dir}") + message(WARNING "Package 'image_proc' exports the include directory '${_exported_include_dir}' which doesn't exist") + endif() + normalize_path(_exported_include_dir "${_exported_include_dir}") + list(APPEND image_proc_INCLUDE_DIRS "${_exported_include_dir}") + endforeach() +endif() diff --git a/src/image_pipeline/install/image_proc/share/image_proc/cmake/ament_cmake_export_libraries-extras.cmake b/src/image_pipeline/install/image_proc/share/image_proc/cmake/ament_cmake_export_libraries-extras.cmake new file mode 100644 index 000000000..a4a3162a2 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/cmake/ament_cmake_export_libraries-extras.cmake @@ -0,0 +1,141 @@ +# generated from ament_cmake_export_libraries/cmake/template/ament_cmake_export_libraries.cmake.in + +set(_exported_libraries "image_proc;rectify;debayer;resize;crop_decimate;crop_non_zero") +set(_exported_library_names "") + +# populate image_proc_LIBRARIES +if(NOT _exported_libraries STREQUAL "") + # loop over libraries, either target names or absolute paths + list(LENGTH _exported_libraries _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_libraries ${_i} _arg) + + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND image_proc_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'image_proc' passes the build configuration keyword '${_cfg}' as the last exported library") + endif() + list(GET _exported_libraries ${_i} _library) + else() + # the value is a library without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + if(NOT IS_ABSOLUTE "${_library}") + # search for library target relative to this CMake file + set(_lib "NOTFOUND") + find_library( + _lib NAMES "${_library}" + PATHS "${image_proc_DIR}/../../../lib" + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + + if(NOT _lib) + # warn about not existing library and ignore it + message(FATAL_ERROR "Package 'image_proc' exports the library '${_library}' which couldn't be found") + elseif(NOT IS_ABSOLUTE "${_lib}") + # the found library must be an absolute path + message(FATAL_ERROR "Package 'image_proc' found the library '${_library}' at '${_lib}' which is not an absolute path") + elseif(NOT EXISTS "${_lib}") + # the found library must exist + message(FATAL_ERROR "Package 'image_proc' found the library '${_lib}' which doesn't exist") + else() + list(APPEND image_proc_LIBRARIES ${_cfg} "${_lib}") + endif() + + else() + if(NOT EXISTS "${_library}") + # the found library must exist + message(WARNING "Package 'image_proc' exports the library '${_library}' which doesn't exist") + else() + list(APPEND image_proc_LIBRARIES ${_cfg} "${_library}") + endif() + endif() + endwhile() +endif() + +# find_library() library names with optional LIBRARY_DIRS +# and add the libraries to image_proc_LIBRARIES +if(NOT _exported_library_names STREQUAL "") + # loop over library names + # but remember related build configuration keyword if available + list(LENGTH _exported_library_names _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_library_names ${_i} _arg) + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND image_proc_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library name + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'image_proc' passes the build configuration keyword '${_cfg}' as the last exported target") + endif() + list(GET _exported_library_names ${_i} _library) + else() + # the value is a library target without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + # extract optional LIBRARY_DIRS from library name + string(REPLACE ":" ";" _library_dirs "${_library}") + list(GET _library_dirs 0 _library_name) + list(REMOVE_AT _library_dirs 0) + + set(_lib "NOTFOUND") + if(NOT _library_dirs) + # search for library in the common locations + find_library( + _lib + NAMES "${_library_name}" + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING "Package 'image_proc' exports library '${_library_name}' which couldn't be found") + endif() + else() + # search for library in the specified directories + find_library( + _lib + NAMES "${_library_name}" + PATHS ${_library_dirs} + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING + "Package 'image_proc' exports library '${_library_name}' with LIBRARY_DIRS '${_library_dirs}' which couldn't be found") + endif() + endif() + if(_lib) + list(APPEND image_proc_LIBRARIES ${_cfg} "${_lib}") + endif() + endwhile() +endif() + +# TODO(dirk-thomas) deduplicate image_proc_LIBRARIES +# while maintaining library order +# as well as build configuration keywords +# as well as linker flags diff --git a/src/image_pipeline/install/image_proc/share/image_proc/cmake/image_procConfig-version.cmake b/src/image_pipeline/install/image_proc/share/image_proc/cmake/image_procConfig-version.cmake new file mode 100644 index 000000000..81dc15e08 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/cmake/image_procConfig-version.cmake @@ -0,0 +1,14 @@ +# generated from ament/cmake/core/templates/nameConfig-version.cmake.in +set(PACKAGE_VERSION "4.0.0") + +set(PACKAGE_VERSION_EXACT False) +set(PACKAGE_VERSION_COMPATIBLE False) + +if("${PACKAGE_FIND_VERSION}" VERSION_EQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT True) + set(PACKAGE_VERSION_COMPATIBLE True) +endif() + +if("${PACKAGE_FIND_VERSION}" VERSION_LESS "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE True) +endif() diff --git a/src/image_pipeline/install/image_proc/share/image_proc/cmake/image_procConfig.cmake b/src/image_pipeline/install/image_proc/share/image_proc/cmake/image_procConfig.cmake new file mode 100644 index 000000000..a0c1611ea --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/cmake/image_procConfig.cmake @@ -0,0 +1,42 @@ +# generated from ament/cmake/core/templates/nameConfig.cmake.in + +# prevent multiple inclusion +if(_image_proc_CONFIG_INCLUDED) + # ensure to keep the found flag the same + if(NOT DEFINED image_proc_FOUND) + # explicitly set it to FALSE, otherwise CMake will set it to TRUE + set(image_proc_FOUND FALSE) + elseif(NOT image_proc_FOUND) + # use separate condition to avoid uninitialized variable warning + set(image_proc_FOUND FALSE) + endif() + return() +endif() +set(_image_proc_CONFIG_INCLUDED TRUE) + +# output package information +if(NOT image_proc_FIND_QUIETLY) + message(STATUS "Found image_proc: 4.0.0 (${image_proc_DIR})") +endif() + +# warn when using a deprecated package +if(NOT "" STREQUAL "") + set(_msg "Package 'image_proc' is deprecated") + # append custom deprecation text if available + if(NOT "" STREQUAL "TRUE") + set(_msg "${_msg} ()") + endif() + # optionally quiet the deprecation message + if(NOT image_proc_DEPRECATED_QUIET) + message(DEPRECATION "${_msg}") + endif() +endif() + +# flag package as ament-based to distinguish it after being find_package()-ed +set(image_proc_FOUND_AMENT_PACKAGE TRUE) + +# include all config extra files +set(_extras "ament_cmake_export_dependencies-extras.cmake;ament_cmake_export_include_directories-extras.cmake;ament_cmake_export_libraries-extras.cmake") +foreach(_extra ${_extras}) + include("${image_proc_DIR}/${_extra}") +endforeach() diff --git a/src/image_pipeline/install/image_proc/share/image_proc/environment/ament_prefix_path.dsv b/src/image_pipeline/install/image_proc/share/image_proc/environment/ament_prefix_path.dsv new file mode 100644 index 000000000..79d4c95b5 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/environment/ament_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;AMENT_PREFIX_PATH; diff --git a/src/image_pipeline/install/image_proc/share/image_proc/environment/ament_prefix_path.sh b/src/image_pipeline/install/image_proc/share/image_proc/environment/ament_prefix_path.sh new file mode 100644 index 000000000..02e441b75 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/environment/ament_prefix_path.sh @@ -0,0 +1,4 @@ +# copied from +# ament_cmake_core/cmake/environment_hooks/environment/ament_prefix_path.sh + +ament_prepend_unique_value AMENT_PREFIX_PATH "$AMENT_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/image_proc/share/image_proc/environment/library_path.dsv b/src/image_pipeline/install/image_proc/share/image_proc/environment/library_path.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/environment/library_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/image_proc/share/image_proc/environment/library_path.sh b/src/image_pipeline/install/image_proc/share/image_proc/environment/library_path.sh new file mode 100644 index 000000000..292e518f1 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/environment/library_path.sh @@ -0,0 +1,16 @@ +# copied from ament_package/template/environment_hook/library_path.sh + +# detect if running on Darwin platform +_UNAME=`uname -s` +_IS_DARWIN=0 +if [ "$_UNAME" = "Darwin" ]; then + _IS_DARWIN=1 +fi +unset _UNAME + +if [ $_IS_DARWIN -eq 0 ]; then + ament_prepend_unique_value LD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +else + ament_prepend_unique_value DYLD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +fi +unset _IS_DARWIN diff --git a/src/image_pipeline/install/image_proc/share/image_proc/environment/path.dsv b/src/image_pipeline/install/image_proc/share/image_proc/environment/path.dsv new file mode 100644 index 000000000..b94426af0 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/environment/path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate-if-exists;PATH;bin diff --git a/src/image_pipeline/install/image_proc/share/image_proc/environment/path.sh b/src/image_pipeline/install/image_proc/share/image_proc/environment/path.sh new file mode 100644 index 000000000..e59b749a8 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/environment/path.sh @@ -0,0 +1,5 @@ +# copied from ament_cmake_core/cmake/environment_hooks/environment/path.sh + +if [ -d "$AMENT_CURRENT_PREFIX/bin" ]; then + ament_prepend_unique_value PATH "$AMENT_CURRENT_PREFIX/bin" +fi diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/cmake_prefix_path.dsv b/src/image_pipeline/install/image_proc/share/image_proc/hook/cmake_prefix_path.dsv new file mode 100644 index 000000000..e119f32cb --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/cmake_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;CMAKE_PREFIX_PATH; diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/cmake_prefix_path.ps1 b/src/image_pipeline/install/image_proc/share/image_proc/hook/cmake_prefix_path.ps1 new file mode 100644 index 000000000..d03facc1a --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/cmake_prefix_path.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value CMAKE_PREFIX_PATH "$env:COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/cmake_prefix_path.sh b/src/image_pipeline/install/image_proc/share/image_proc/hook/cmake_prefix_path.sh new file mode 100644 index 000000000..a948e685b --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/cmake_prefix_path.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value CMAKE_PREFIX_PATH "$COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/ld_library_path_lib.dsv b/src/image_pipeline/install/image_proc/share/image_proc/hook/ld_library_path_lib.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/ld_library_path_lib.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/ld_library_path_lib.ps1 b/src/image_pipeline/install/image_proc/share/image_proc/hook/ld_library_path_lib.ps1 new file mode 100644 index 000000000..f6df601d0 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/ld_library_path_lib.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value LD_LIBRARY_PATH "$env:COLCON_CURRENT_PREFIX\lib" diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/ld_library_path_lib.sh b/src/image_pipeline/install/image_proc/share/image_proc/hook/ld_library_path_lib.sh new file mode 100644 index 000000000..ca3c1020b --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/ld_library_path_lib.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value LD_LIBRARY_PATH "$COLCON_CURRENT_PREFIX/lib" diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/path.dsv b/src/image_pipeline/install/image_proc/share/image_proc/hook/path.dsv new file mode 100644 index 000000000..95435e0c2 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;PATH;bin diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/path.ps1 b/src/image_pipeline/install/image_proc/share/image_proc/hook/path.ps1 new file mode 100644 index 000000000..0b980efa4 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/path.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value PATH "$env:COLCON_CURRENT_PREFIX\bin" diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/path.sh b/src/image_pipeline/install/image_proc/share/image_proc/hook/path.sh new file mode 100644 index 000000000..295266d6f --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/path.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value PATH "$COLCON_CURRENT_PREFIX/bin" diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/pythonscriptspath.dsv b/src/image_pipeline/install/image_proc/share/image_proc/hook/pythonscriptspath.dsv new file mode 100644 index 000000000..95435e0c2 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/pythonscriptspath.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;PATH;bin diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/pythonscriptspath.ps1 b/src/image_pipeline/install/image_proc/share/image_proc/hook/pythonscriptspath.ps1 new file mode 100644 index 000000000..0b980efa4 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/pythonscriptspath.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value PATH "$env:COLCON_CURRENT_PREFIX\bin" diff --git a/src/image_pipeline/install/image_proc/share/image_proc/hook/pythonscriptspath.sh b/src/image_pipeline/install/image_proc/share/image_proc/hook/pythonscriptspath.sh new file mode 100644 index 000000000..295266d6f --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/hook/pythonscriptspath.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value PATH "$COLCON_CURRENT_PREFIX/bin" diff --git a/src/image_pipeline/install/image_proc/share/image_proc/launch/image_proc.launch.py b/src/image_pipeline/install/image_proc/share/image_proc/launch/image_proc.launch.py new file mode 100644 index 000000000..91af35f7f --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/launch/image_proc.launch.py @@ -0,0 +1,104 @@ +# Copyright (c) 2020, Open Source Robotics Foundation, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * 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 OWNER 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. + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.conditions import LaunchConfigurationEquals +from launch.conditions import LaunchConfigurationNotEquals +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import ComposableNodeContainer +from launch_ros.actions import LoadComposableNodes +from launch_ros.descriptions import ComposableNode + + +def generate_launch_description(): + composable_nodes = [ + ComposableNode( + package='image_proc', + plugin='image_proc::DebayerNode', + name='debayer_node', + ), + ComposableNode( + package='image_proc', + plugin='image_proc::RectifyNode', + name='rectify_mono_node', + # Remap subscribers and publishers + remappings=[ + ('image', 'image_mono'), + ('camera_info', 'camera_info'), + ('image_rect', 'image_rect') + ], + ), + ComposableNode( + package='image_proc', + plugin='image_proc::RectifyNode', + name='rectify_color_node', + # Remap subscribers and publishers + remappings=[ + ('image', 'image_color'), + ('image_rect', 'image_rect_color') + ], + ) + ] + + arg_container = DeclareLaunchArgument( + name='container', default_value='', + description=( + 'Name of an existing node container to load launched nodes into. ' + 'If unset, a new container will be created.' + ) + ) + + # If an existing container is not provided, start a container and load nodes into it + image_processing_container = ComposableNodeContainer( + condition=LaunchConfigurationEquals('container', ''), + name='image_proc_container', + namespace='', + package='rclcpp_components', + executable='component_container', + composable_node_descriptions=composable_nodes, + output='screen' + ) + + # If an existing container name is provided, load composable nodes into it + # This will block until a container with the provided name is available and nodes are loaded + load_composable_nodes = LoadComposableNodes( + condition=LaunchConfigurationNotEquals('container', ''), + composable_node_descriptions=composable_nodes, + target_container=LaunchConfiguration('container'), + ) + + return LaunchDescription([ + arg_container, + image_processing_container, + load_composable_nodes, + ]) diff --git a/src/image_pipeline/install/image_proc/share/image_proc/local_setup.bash b/src/image_pipeline/install/image_proc/share/image_proc/local_setup.bash new file mode 100644 index 000000000..49782f246 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/local_setup.bash @@ -0,0 +1,46 @@ +# generated from ament_package/template/package_level/local_setup.bash.in + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_proc/share/image_proc/local_setup.dsv b/src/image_pipeline/install/image_proc/share/image_proc/local_setup.dsv new file mode 100644 index 000000000..62675d8b7 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/local_setup.dsv @@ -0,0 +1,3 @@ +source;share/image_proc/environment/ament_prefix_path.sh +source;share/image_proc/environment/library_path.sh +source;share/image_proc/environment/path.sh diff --git a/src/image_pipeline/install/image_proc/share/image_proc/local_setup.sh b/src/image_pipeline/install/image_proc/share/image_proc/local_setup.sh new file mode 100644 index 000000000..686e936d8 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/local_setup.sh @@ -0,0 +1,185 @@ +# generated from ament_package/template/package_level/local_setup.sh.in + +# since this file is sourced use either the provided AMENT_CURRENT_PREFIX +# or fall back to the destination set at configure time +: ${AMENT_CURRENT_PREFIX:="/home/DinoSage/rocket_league/src/image_pipeline/install/image_proc"} +if [ ! -d "$AMENT_CURRENT_PREFIX" ]; then + if [ -z "$COLCON_CURRENT_PREFIX" ]; then + echo "The compile time prefix path '$AMENT_CURRENT_PREFIX' doesn't " \ + "exist. Consider sourcing a different extension than '.sh'." 1>&2 + else + AMENT_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" + fi +fi + +# function to append values to environment variables +# using colons as separators and avoiding leading separators +ament_append_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # avoid leading separator + eval _values=\"\$$_listname\" + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + _ament_append_value_IFS=$IFS + unset IFS + eval export $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + IFS=$_ament_append_value_IFS + unset _ament_append_value_IFS + fi + unset _values + + unset _value + unset _listname +} + +# function to append non-duplicate values to environment variables +# using colons as separators and avoiding leading separators +ament_append_unique_value() { + # arguments + _listname=$1 + _value=$2 + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\$$_listname + _duplicate= + _ament_append_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ $_item = $_value ]; then + _duplicate=1 + fi + done + unset _item + + # append only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid leading separator + if [ -z "$_values" ]; then + eval $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + fi + fi + IFS=$_ament_append_unique_value_IFS + unset _ament_append_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# function to prepend non-duplicate values to environment variables +# using colons as separators and avoiding trailing separators +ament_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\"\$$_listname\" + _duplicate= + _ament_prepend_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ "$_item" = "$_value" ]; then + _duplicate=1 + fi + done + unset _item + + # prepend only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid trailing separator + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval export $_listname=\"$_value:\$$_listname\" + #eval echo "prepend list \$$_listname" + fi + fi + IFS=$_ament_prepend_unique_value_IFS + unset _ament_prepend_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# list all environment hooks of this package +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/image_proc/environment/ament_prefix_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/image_proc/environment/library_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/image_proc/environment/path.sh" + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array AMENT_ENVIRONMENT_HOOKS + fi + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + if [ -f "$_hook" ]; then + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + # trace output + if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_hook\"" + fi + . "$_hook" + fi + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +# reset AMENT_CURRENT_PREFIX after each package +# allowing to source multiple package-level setup files +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_proc/share/image_proc/local_setup.zsh b/src/image_pipeline/install/image_proc/share/image_proc/local_setup.zsh new file mode 100644 index 000000000..fe161be53 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/local_setup.zsh @@ -0,0 +1,59 @@ +# generated from ament_package/template/package_level/local_setup.zsh.in + +AMENT_SHELL=zsh + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# function to convert array-like strings into arrays +# to wordaround SH_WORD_SPLIT not being set +ament_zsh_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +# the package-level local_setup file unsets AMENT_CURRENT_PREFIX +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_proc/share/image_proc/package.bash b/src/image_pipeline/install/image_proc/share/image_proc/package.bash new file mode 100644 index 000000000..892a4fbb5 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/package.bash @@ -0,0 +1,39 @@ +# generated from colcon_bash/shell/template/package.bash.em + +# This script extends the environment for this package. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" +else + _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh script of this package +_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/image_proc/package.sh" + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_bash_COLCON_CURRENT_PREFIX" + +# source bash hooks +_colcon_package_bash_source_script "$COLCON_CURRENT_PREFIX/share/image_proc/local_setup.bash" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_bash_source_script +unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_proc/share/image_proc/package.dsv b/src/image_pipeline/install/image_proc/share/image_proc/package.dsv new file mode 100644 index 000000000..1ad181a61 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/package.dsv @@ -0,0 +1,17 @@ +source;share/image_proc/hook/cmake_prefix_path.ps1 +source;share/image_proc/hook/cmake_prefix_path.dsv +source;share/image_proc/hook/cmake_prefix_path.sh +source;share/image_proc/hook/ld_library_path_lib.ps1 +source;share/image_proc/hook/ld_library_path_lib.dsv +source;share/image_proc/hook/ld_library_path_lib.sh +source;share/image_proc/hook/path.ps1 +source;share/image_proc/hook/path.dsv +source;share/image_proc/hook/path.sh +source;share/image_proc/hook/pythonscriptspath.ps1 +source;share/image_proc/hook/pythonscriptspath.dsv +source;share/image_proc/hook/pythonscriptspath.sh +source;share/image_proc/local_setup.bash +source;share/image_proc/local_setup.dsv +source;share/image_proc/local_setup.ps1 +source;share/image_proc/local_setup.sh +source;share/image_proc/local_setup.zsh diff --git a/src/image_pipeline/install/image_proc/share/image_proc/package.ps1 b/src/image_pipeline/install/image_proc/share/image_proc/package.ps1 new file mode 100644 index 000000000..fa2f10896 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/package.ps1 @@ -0,0 +1,119 @@ +# generated from colcon_powershell/shell/template/package.ps1.em + +# function to append a value to a variable +# which uses colons as separators +# duplicates as well as leading separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_append_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + $_duplicate="" + # start with no values + $_all_values="" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -eq $_value) { + $_duplicate="1" + } + if ($_all_values) { + $_all_values="${_all_values};$_" + } else { + $_all_values="$_" + } + } + } + } + # append only non-duplicates + if (!$_duplicate) { + # avoid leading separator + if ($_all_values) { + $_all_values="${_all_values};${_value}" + } else { + $_all_values="${_value}" + } + } + + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_prepend_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + # start with the new value + $_all_values="$_value" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -ne $_value) { + # keep non-duplicate values + $_all_values="${_all_values};$_" + } + } + } + } + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +function colcon_package_source_powershell_script { + param ( + $_colcon_package_source_powershell_script + ) + # source script with conditional trace output + if (Test-Path $_colcon_package_source_powershell_script) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_package_source_powershell_script'" + } + . "$_colcon_package_source_powershell_script" + } else { + Write-Error "not found: '$_colcon_package_source_powershell_script'" + } +} + + +# a powershell script is able to determine its own path +# the prefix is two levels up from the package specific share directory +$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName + +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_proc/hook/cmake_prefix_path.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_proc/hook/ld_library_path_lib.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_proc/hook/path.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_proc/hook/pythonscriptspath.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_proc/local_setup.ps1" + +Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_proc/share/image_proc/package.sh b/src/image_pipeline/install/image_proc/share/image_proc/package.sh new file mode 100644 index 000000000..e77389e01 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/package.sh @@ -0,0 +1,90 @@ +# generated from colcon_core/shell/template/package.sh.em + +# This script extends the environment for this package. + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prepend_unique_value_IFS=$IFS + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set in zsh + if [ "$(command -v colcon_zsh_convert_to_array)" ]; then + colcon_zsh_convert_to_array _values + fi + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS=$_colcon_prepend_unique_value_IFS + unset _colcon_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/DinoSage/rocket_league/src/image_pipeline/install/image_proc" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_package_sh_COLCON_CURRENT_PREFIX + return 1 + fi + COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" +fi +unset _colcon_package_sh_COLCON_CURRENT_PREFIX + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh hooks +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_proc/hook/cmake_prefix_path.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_proc/hook/ld_library_path_lib.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_proc/hook/path.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_proc/hook/pythonscriptspath.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_proc/local_setup.sh" + +unset _colcon_package_sh_source_script +unset COLCON_CURRENT_PREFIX + +# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/src/image_pipeline/install/image_proc/share/image_proc/package.xml b/src/image_pipeline/install/image_proc/share/image_proc/package.xml new file mode 100644 index 000000000..e2afd43b2 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/package.xml @@ -0,0 +1,39 @@ + + + + image_proc + 4.0.0 + Single image rectification and color processing. + + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/image_proc/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Patrick Mihelich + Kurt Konolige + Jeremy Leibs + + ament_cmake_auto + + cv_bridge + image_geometry + image_transport + libopencv-dev + rclcpp + rclcpp_components + rcutils + sensor_msgs + tracetools_image_pipeline + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/image_pipeline/install/image_proc/share/image_proc/package.zsh b/src/image_pipeline/install/image_proc/share/image_proc/package.zsh new file mode 100644 index 000000000..5fc295777 --- /dev/null +++ b/src/image_pipeline/install/image_proc/share/image_proc/package.zsh @@ -0,0 +1,50 @@ +# generated from colcon_zsh/shell/template/package.zsh.em + +# This script extends the environment for this package. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" +else + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +colcon_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# source sh script of this package +_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/image_proc/package.sh" +unset convert_zsh_to_array + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_zsh_COLCON_CURRENT_PREFIX" + +# source zsh hooks +_colcon_package_zsh_source_script "$COLCON_CURRENT_PREFIX/share/image_proc/local_setup.zsh" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_zsh_source_script +unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_publisher/share/colcon-core/packages/image_publisher b/src/image_pipeline/install/image_publisher/share/colcon-core/packages/image_publisher new file mode 100644 index 000000000..8e0252821 --- /dev/null +++ b/src/image_pipeline/install/image_publisher/share/colcon-core/packages/image_publisher @@ -0,0 +1 @@ +camera_info_manager:cv_bridge:image_transport:rcl_interfaces:rclcpp:rclcpp_components \ No newline at end of file diff --git a/src/image_pipeline/install/image_publisher/share/image_publisher/package.bash b/src/image_pipeline/install/image_publisher/share/image_publisher/package.bash new file mode 100644 index 000000000..e1462dfd9 --- /dev/null +++ b/src/image_pipeline/install/image_publisher/share/image_publisher/package.bash @@ -0,0 +1,31 @@ +# generated from colcon_bash/shell/template/package.bash.em + +# This script extends the environment for this package. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" +else + _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh script of this package +_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/image_publisher/package.sh" + +unset _colcon_package_bash_source_script +unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_publisher/share/image_publisher/package.dsv b/src/image_pipeline/install/image_publisher/share/image_publisher/package.dsv new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/image_publisher/share/image_publisher/package.ps1 b/src/image_pipeline/install/image_publisher/share/image_publisher/package.ps1 new file mode 100644 index 000000000..4198e42ec --- /dev/null +++ b/src/image_pipeline/install/image_publisher/share/image_publisher/package.ps1 @@ -0,0 +1,108 @@ +# generated from colcon_powershell/shell/template/package.ps1.em + +# function to append a value to a variable +# which uses colons as separators +# duplicates as well as leading separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_append_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + $_duplicate="" + # start with no values + $_all_values="" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -eq $_value) { + $_duplicate="1" + } + if ($_all_values) { + $_all_values="${_all_values};$_" + } else { + $_all_values="$_" + } + } + } + } + # append only non-duplicates + if (!$_duplicate) { + # avoid leading separator + if ($_all_values) { + $_all_values="${_all_values};${_value}" + } else { + $_all_values="${_value}" + } + } + + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_prepend_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + # start with the new value + $_all_values="$_value" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -ne $_value) { + # keep non-duplicate values + $_all_values="${_all_values};$_" + } + } + } + } + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +function colcon_package_source_powershell_script { + param ( + $_colcon_package_source_powershell_script + ) + # source script with conditional trace output + if (Test-Path $_colcon_package_source_powershell_script) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_package_source_powershell_script'" + } + . "$_colcon_package_source_powershell_script" + } else { + Write-Error "not found: '$_colcon_package_source_powershell_script'" + } +} + + diff --git a/src/image_pipeline/install/image_publisher/share/image_publisher/package.sh b/src/image_pipeline/install/image_publisher/share/image_publisher/package.sh new file mode 100644 index 000000000..7d7278e5f --- /dev/null +++ b/src/image_pipeline/install/image_publisher/share/image_publisher/package.sh @@ -0,0 +1,52 @@ +# generated from colcon_core/shell/template/package.sh.em + +# This script extends the environment for this package. + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prepend_unique_value_IFS=$IFS + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set in zsh + if [ "$(command -v colcon_zsh_convert_to_array)" ]; then + colcon_zsh_convert_to_array _values + fi + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS=$_colcon_prepend_unique_value_IFS + unset _colcon_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/src/image_pipeline/install/image_publisher/share/image_publisher/package.zsh b/src/image_pipeline/install/image_publisher/share/image_publisher/package.zsh new file mode 100644 index 000000000..778d92d3b --- /dev/null +++ b/src/image_pipeline/install/image_publisher/share/image_publisher/package.zsh @@ -0,0 +1,42 @@ +# generated from colcon_zsh/shell/template/package.zsh.em + +# This script extends the environment for this package. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" +else + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +colcon_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# source sh script of this package +_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/image_publisher/package.sh" +unset convert_zsh_to_array + +unset _colcon_package_zsh_source_script +unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_rotate/include/image_rotate/image_rotate_node.hpp b/src/image_pipeline/install/image_rotate/include/image_rotate/image_rotate_node.hpp new file mode 100644 index 000000000..8482a1c72 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/include/image_rotate/image_rotate_node.hpp @@ -0,0 +1,112 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef IMAGE_ROTATE__IMAGE_ROTATE_NODE_HPP_ +#define IMAGE_ROTATE__IMAGE_ROTATE_NODE_HPP_ + +#include +#include + +#include "tf2_ros/buffer.h" +#include "tf2_ros/transform_listener.h" +#include "tf2_ros/transform_broadcaster.h" + +#include +#include +#include +#include +#include + +#include "image_rotate/visibility.h" + +namespace image_rotate +{ + +struct ImageRotateConfig +{ + std::string target_frame_id; + double target_x; + double target_y; + double target_z; + std::string source_frame_id; + double source_x; + double source_y; + double source_z; + std::string output_frame_id; + std::string input_frame_id; + bool use_camera_info; + double max_angular_rate; + double output_image_size; +}; + +class ImageRotateNode : public rclcpp::Node +{ +public: + IMAGE_ROTATE_PUBLIC ImageRotateNode(const rclcpp::NodeOptions & options); + +private: + const std::string frameWithDefault(const std::string & frame, const std::string & image_frame); + void imageCallbackWithInfo( + const sensor_msgs::msg::Image::ConstSharedPtr & msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & cam_info); + void imageCallback(const sensor_msgs::msg::Image::ConstSharedPtr & msg); + void do_work( + const sensor_msgs::msg::Image::ConstSharedPtr & msg, + const std::string input_frame_from_msg); + void subscribe(); + void unsubscribe(); + void connectCb(); + void disconnectCb(); + void onInit(); + + rclcpp::Node::OnSetParametersCallbackHandle::SharedPtr on_set_parameters_callback_handle_; + + std::shared_ptr tf_buffer_; + std::shared_ptr tf_sub_; + std::shared_ptr tf_pub_; + + image_rotate::ImageRotateConfig config_; + + image_transport::Publisher img_pub_; + image_transport::Subscriber img_sub_; + image_transport::CameraSubscriber cam_sub_; + + geometry_msgs::msg::Vector3Stamped target_vector_; + geometry_msgs::msg::Vector3Stamped source_vector_; + + int subscriber_count_; + double angle_; + tf2::TimePoint prev_stamp_; +}; +} // namespace image_rotate + +#endif // IMAGE_ROTATE__IMAGE_ROTATE_NODE_HPP_ diff --git a/src/image_pipeline/install/image_rotate/include/image_rotate/visibility.h b/src/image_pipeline/install/image_rotate/include/image_rotate/visibility.h new file mode 100644 index 000000000..643108bb1 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/include/image_rotate/visibility.h @@ -0,0 +1,75 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * Neither the name of the Willow Garage 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 OWNER 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. + +#ifndef IMAGE_ROTATE__VISIBILITY_H_ +#define IMAGE_ROTATE__VISIBILITY_H_ + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + + #ifdef __GNUC__ + #define IMAGE_ROTATE_EXPORT __attribute__ ((dllexport)) + #define IMAGE_ROTATE_IMPORT __attribute__ ((dllimport)) + #else + #define IMAGE_ROTATE_EXPORT __declspec(dllexport) + #define IMAGE_ROTATE_IMPORT __declspec(dllimport) + #endif + + #ifdef IMAGE_ROTATE_DLL + #define IMAGE_ROTATE_PUBLIC IMAGE_ROTATE_EXPORT + #else + #define IMAGE_ROTATE_PUBLIC IMAGE_ROTATE_IMPORT + #endif + + #define IMAGE_ROTATE_PUBLIC_TYPE IMAGE_ROTATE_PUBLIC + + #define IMAGE_ROTATE_LOCAL + +#else + + #define IMAGE_ROTATE_EXPORT __attribute__ ((visibility("default"))) + #define IMAGE_ROTATE_IMPORT + + #if __GNUC__ >= 4 + #define IMAGE_ROTATE_PUBLIC __attribute__ ((visibility("default"))) + #define IMAGE_ROTATE_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define IMAGE_ROTATE_PUBLIC + #define IMAGE_ROTATE_LOCAL + #endif + + #define IMAGE_ROTATE_PUBLIC_TYPE +#endif + +#endif // IMAGE_ROTATE__VISIBILITY_H_ diff --git a/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/package_run_dependencies/image_rotate b/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/package_run_dependencies/image_rotate new file mode 100644 index 000000000..e90a204df --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/package_run_dependencies/image_rotate @@ -0,0 +1 @@ +cv_bridge;geometry_msgs;image_transport;libopencv-dev;rcl_interfaces;rclcpp;rclcpp_components;sensor_msgs;tf2;tf2_geometry_msgs;tf2_ros;ament_lint_auto;ament_lint_common \ No newline at end of file diff --git a/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/packages/image_rotate b/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/packages/image_rotate new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/parent_prefix_path/image_rotate b/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/parent_prefix_path/image_rotate new file mode 100644 index 000000000..56c8678ea --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/parent_prefix_path/image_rotate @@ -0,0 +1 @@ +/opt/ros/iron \ No newline at end of file diff --git a/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/rclcpp_components/image_rotate b/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/rclcpp_components/image_rotate new file mode 100644 index 000000000..751bf153d --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/ament_index/resource_index/rclcpp_components/image_rotate @@ -0,0 +1 @@ +image_rotate::ImageRotateNode;lib/libimage_rotate.so diff --git a/src/image_pipeline/install/image_rotate/share/colcon-core/packages/image_rotate b/src/image_pipeline/install/image_rotate/share/colcon-core/packages/image_rotate new file mode 100644 index 000000000..3ebb3d9cf --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/colcon-core/packages/image_rotate @@ -0,0 +1 @@ +cv_bridge:geometry_msgs:image_transport:libopencv-dev:rcl_interfaces:rclcpp:rclcpp_components:sensor_msgs:tf2:tf2_geometry_msgs:tf2_ros \ No newline at end of file diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/ament_cmake_export_dependencies-extras.cmake b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/ament_cmake_export_dependencies-extras.cmake new file mode 100644 index 000000000..11c966c10 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/ament_cmake_export_dependencies-extras.cmake @@ -0,0 +1,92 @@ +# generated from ament_cmake_export_dependencies/cmake/ament_cmake_export_dependencies-extras.cmake.in + +set(_exported_dependencies "cv_bridge;geometry_msgs;image_transport;rcl_interfaces;rclcpp;rclcpp_components;sensor_msgs;tf2;tf2_geometry_msgs;tf2_ros") + +find_package(ament_cmake_libraries QUIET REQUIRED) + +# find_package() all dependencies +# and append their DEFINITIONS INCLUDE_DIRS, LIBRARIES, and LINK_FLAGS +# variables to image_rotate_DEFINITIONS, image_rotate_INCLUDE_DIRS, +# image_rotate_LIBRARIES, and image_rotate_LINK_FLAGS. +# Additionally collect the direct dependency names in +# image_rotate_DEPENDENCIES as well as the recursive dependency names +# in image_rotate_RECURSIVE_DEPENDENCIES. +if(NOT _exported_dependencies STREQUAL "") + find_package(ament_cmake_core QUIET REQUIRED) + set(image_rotate_DEPENDENCIES ${_exported_dependencies}) + set(image_rotate_RECURSIVE_DEPENDENCIES ${_exported_dependencies}) + set(_libraries) + foreach(_dep ${_exported_dependencies}) + if(NOT ${_dep}_FOUND) + find_package("${_dep}" QUIET REQUIRED) + endif() + # if a package provides modern CMake interface targets use them + # exclusively assuming the classic CMake variables only exist for + # backward compatibility + set(use_modern_cmake FALSE) + if(NOT "${${_dep}_TARGETS}" STREQUAL "") + foreach(_target ${${_dep}_TARGETS}) + # only use actual targets + # in case a package uses this variable for other content + if(TARGET "${_target}") + get_target_property(_include_dirs ${_target} INTERFACE_INCLUDE_DIRECTORIES) + if(_include_dirs) + list_append_unique(image_rotate_INCLUDE_DIRS "${_include_dirs}") + endif() + + get_target_property(_imported_configurations ${_target} IMPORTED_CONFIGURATIONS) + if(_imported_configurations) + string(TOUPPER "${_imported_configurations}" _imported_configurations) + if(DEBUG_CONFIGURATIONS) + string(TOUPPER "${DEBUG_CONFIGURATIONS}" _debug_configurations_uppercase) + else() + set(_debug_configurations_uppercase "DEBUG") + endif() + foreach(_imported_config ${_imported_configurations}) + get_target_property(_imported_implib ${_target} IMPORTED_IMPLIB_${_imported_config}) + if(_imported_implib) + set(_imported_implib_config "optimized") + if(${_imported_config} IN_LIST _debug_configurations_uppercase) + set(_imported_implib_config "debug") + endif() + list(APPEND _libraries ${_imported_implib_config} ${_imported_implib}) + else() + get_target_property(_imported_location ${_target} IMPORTED_LOCATION_${_imported_config}) + if(_imported_location) + list(APPEND _libraries "${_imported_location}") + endif() + endif() + endforeach() + endif() + + get_target_property(_link_libraries ${_target} INTERFACE_LINK_LIBRARIES) + if(_link_libraries) + list(APPEND _libraries "${_link_libraries}") + endif() + set(use_modern_cmake TRUE) + endif() + endforeach() + endif() + if(NOT use_modern_cmake) + if(${_dep}_DEFINITIONS) + list_append_unique(image_rotate_DEFINITIONS "${${_dep}_DEFINITIONS}") + endif() + if(${_dep}_INCLUDE_DIRS) + list_append_unique(image_rotate_INCLUDE_DIRS "${${_dep}_INCLUDE_DIRS}") + endif() + if(${_dep}_LIBRARIES) + list(APPEND _libraries "${${_dep}_LIBRARIES}") + endif() + if(${_dep}_LINK_FLAGS) + list_append_unique(image_rotate_LINK_FLAGS "${${_dep}_LINK_FLAGS}") + endif() + if(${_dep}_RECURSIVE_DEPENDENCIES) + list_append_unique(image_rotate_RECURSIVE_DEPENDENCIES "${${_dep}_RECURSIVE_DEPENDENCIES}") + endif() + endif() + if(_libraries) + ament_libraries_deduplicate(_libraries "${_libraries}") + list(APPEND image_rotate_LIBRARIES "${_libraries}") + endif() + endforeach() +endif() diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/ament_cmake_export_include_directories-extras.cmake b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/ament_cmake_export_include_directories-extras.cmake new file mode 100644 index 000000000..430c4b133 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/ament_cmake_export_include_directories-extras.cmake @@ -0,0 +1,16 @@ +# generated from ament_cmake_export_include_directories/cmake/ament_cmake_export_include_directories-extras.cmake.in + +set(_exported_include_dirs "${image_rotate_DIR}/../../../include") + +# append include directories to image_rotate_INCLUDE_DIRS +# warn about not existing paths +if(NOT _exported_include_dirs STREQUAL "") + find_package(ament_cmake_core QUIET REQUIRED) + foreach(_exported_include_dir ${_exported_include_dirs}) + if(NOT IS_DIRECTORY "${_exported_include_dir}") + message(WARNING "Package 'image_rotate' exports the include directory '${_exported_include_dir}' which doesn't exist") + endif() + normalize_path(_exported_include_dir "${_exported_include_dir}") + list(APPEND image_rotate_INCLUDE_DIRS "${_exported_include_dir}") + endforeach() +endif() diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/ament_cmake_export_libraries-extras.cmake b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/ament_cmake_export_libraries-extras.cmake new file mode 100644 index 000000000..64141cfca --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/ament_cmake_export_libraries-extras.cmake @@ -0,0 +1,141 @@ +# generated from ament_cmake_export_libraries/cmake/template/ament_cmake_export_libraries.cmake.in + +set(_exported_libraries "image_rotate") +set(_exported_library_names "") + +# populate image_rotate_LIBRARIES +if(NOT _exported_libraries STREQUAL "") + # loop over libraries, either target names or absolute paths + list(LENGTH _exported_libraries _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_libraries ${_i} _arg) + + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND image_rotate_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'image_rotate' passes the build configuration keyword '${_cfg}' as the last exported library") + endif() + list(GET _exported_libraries ${_i} _library) + else() + # the value is a library without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + if(NOT IS_ABSOLUTE "${_library}") + # search for library target relative to this CMake file + set(_lib "NOTFOUND") + find_library( + _lib NAMES "${_library}" + PATHS "${image_rotate_DIR}/../../../lib" + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + + if(NOT _lib) + # warn about not existing library and ignore it + message(FATAL_ERROR "Package 'image_rotate' exports the library '${_library}' which couldn't be found") + elseif(NOT IS_ABSOLUTE "${_lib}") + # the found library must be an absolute path + message(FATAL_ERROR "Package 'image_rotate' found the library '${_library}' at '${_lib}' which is not an absolute path") + elseif(NOT EXISTS "${_lib}") + # the found library must exist + message(FATAL_ERROR "Package 'image_rotate' found the library '${_lib}' which doesn't exist") + else() + list(APPEND image_rotate_LIBRARIES ${_cfg} "${_lib}") + endif() + + else() + if(NOT EXISTS "${_library}") + # the found library must exist + message(WARNING "Package 'image_rotate' exports the library '${_library}' which doesn't exist") + else() + list(APPEND image_rotate_LIBRARIES ${_cfg} "${_library}") + endif() + endif() + endwhile() +endif() + +# find_library() library names with optional LIBRARY_DIRS +# and add the libraries to image_rotate_LIBRARIES +if(NOT _exported_library_names STREQUAL "") + # loop over library names + # but remember related build configuration keyword if available + list(LENGTH _exported_library_names _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_library_names ${_i} _arg) + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND image_rotate_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library name + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'image_rotate' passes the build configuration keyword '${_cfg}' as the last exported target") + endif() + list(GET _exported_library_names ${_i} _library) + else() + # the value is a library target without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + # extract optional LIBRARY_DIRS from library name + string(REPLACE ":" ";" _library_dirs "${_library}") + list(GET _library_dirs 0 _library_name) + list(REMOVE_AT _library_dirs 0) + + set(_lib "NOTFOUND") + if(NOT _library_dirs) + # search for library in the common locations + find_library( + _lib + NAMES "${_library_name}" + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING "Package 'image_rotate' exports library '${_library_name}' which couldn't be found") + endif() + else() + # search for library in the specified directories + find_library( + _lib + NAMES "${_library_name}" + PATHS ${_library_dirs} + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING + "Package 'image_rotate' exports library '${_library_name}' with LIBRARY_DIRS '${_library_dirs}' which couldn't be found") + endif() + endif() + if(_lib) + list(APPEND image_rotate_LIBRARIES ${_cfg} "${_lib}") + endif() + endwhile() +endif() + +# TODO(dirk-thomas) deduplicate image_rotate_LIBRARIES +# while maintaining library order +# as well as build configuration keywords +# as well as linker flags diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/image_rotateConfig-version.cmake b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/image_rotateConfig-version.cmake new file mode 100644 index 000000000..81dc15e08 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/image_rotateConfig-version.cmake @@ -0,0 +1,14 @@ +# generated from ament/cmake/core/templates/nameConfig-version.cmake.in +set(PACKAGE_VERSION "4.0.0") + +set(PACKAGE_VERSION_EXACT False) +set(PACKAGE_VERSION_COMPATIBLE False) + +if("${PACKAGE_FIND_VERSION}" VERSION_EQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT True) + set(PACKAGE_VERSION_COMPATIBLE True) +endif() + +if("${PACKAGE_FIND_VERSION}" VERSION_LESS "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE True) +endif() diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/image_rotateConfig.cmake b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/image_rotateConfig.cmake new file mode 100644 index 000000000..af059b4ef --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/cmake/image_rotateConfig.cmake @@ -0,0 +1,42 @@ +# generated from ament/cmake/core/templates/nameConfig.cmake.in + +# prevent multiple inclusion +if(_image_rotate_CONFIG_INCLUDED) + # ensure to keep the found flag the same + if(NOT DEFINED image_rotate_FOUND) + # explicitly set it to FALSE, otherwise CMake will set it to TRUE + set(image_rotate_FOUND FALSE) + elseif(NOT image_rotate_FOUND) + # use separate condition to avoid uninitialized variable warning + set(image_rotate_FOUND FALSE) + endif() + return() +endif() +set(_image_rotate_CONFIG_INCLUDED TRUE) + +# output package information +if(NOT image_rotate_FIND_QUIETLY) + message(STATUS "Found image_rotate: 4.0.0 (${image_rotate_DIR})") +endif() + +# warn when using a deprecated package +if(NOT "" STREQUAL "") + set(_msg "Package 'image_rotate' is deprecated") + # append custom deprecation text if available + if(NOT "" STREQUAL "TRUE") + set(_msg "${_msg} ()") + endif() + # optionally quiet the deprecation message + if(NOT image_rotate_DEPRECATED_QUIET) + message(DEPRECATION "${_msg}") + endif() +endif() + +# flag package as ament-based to distinguish it after being find_package()-ed +set(image_rotate_FOUND_AMENT_PACKAGE TRUE) + +# include all config extra files +set(_extras "ament_cmake_export_dependencies-extras.cmake;ament_cmake_export_include_directories-extras.cmake;ament_cmake_export_libraries-extras.cmake") +foreach(_extra ${_extras}) + include("${image_rotate_DIR}/${_extra}") +endforeach() diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/environment/ament_prefix_path.dsv b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/ament_prefix_path.dsv new file mode 100644 index 000000000..79d4c95b5 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/ament_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;AMENT_PREFIX_PATH; diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/environment/ament_prefix_path.sh b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/ament_prefix_path.sh new file mode 100644 index 000000000..02e441b75 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/ament_prefix_path.sh @@ -0,0 +1,4 @@ +# copied from +# ament_cmake_core/cmake/environment_hooks/environment/ament_prefix_path.sh + +ament_prepend_unique_value AMENT_PREFIX_PATH "$AMENT_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/environment/library_path.dsv b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/library_path.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/library_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/environment/library_path.sh b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/library_path.sh new file mode 100644 index 000000000..292e518f1 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/library_path.sh @@ -0,0 +1,16 @@ +# copied from ament_package/template/environment_hook/library_path.sh + +# detect if running on Darwin platform +_UNAME=`uname -s` +_IS_DARWIN=0 +if [ "$_UNAME" = "Darwin" ]; then + _IS_DARWIN=1 +fi +unset _UNAME + +if [ $_IS_DARWIN -eq 0 ]; then + ament_prepend_unique_value LD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +else + ament_prepend_unique_value DYLD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +fi +unset _IS_DARWIN diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/environment/path.dsv b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/path.dsv new file mode 100644 index 000000000..b94426af0 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate-if-exists;PATH;bin diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/environment/path.sh b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/path.sh new file mode 100644 index 000000000..e59b749a8 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/environment/path.sh @@ -0,0 +1,5 @@ +# copied from ament_cmake_core/cmake/environment_hooks/environment/path.sh + +if [ -d "$AMENT_CURRENT_PREFIX/bin" ]; then + ament_prepend_unique_value PATH "$AMENT_CURRENT_PREFIX/bin" +fi diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/hook/cmake_prefix_path.dsv b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/cmake_prefix_path.dsv new file mode 100644 index 000000000..e119f32cb --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/cmake_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;CMAKE_PREFIX_PATH; diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/hook/cmake_prefix_path.ps1 b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/cmake_prefix_path.ps1 new file mode 100644 index 000000000..d03facc1a --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/cmake_prefix_path.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value CMAKE_PREFIX_PATH "$env:COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/hook/cmake_prefix_path.sh b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/cmake_prefix_path.sh new file mode 100644 index 000000000..a948e685b --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/cmake_prefix_path.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value CMAKE_PREFIX_PATH "$COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/hook/ld_library_path_lib.dsv b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/ld_library_path_lib.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/ld_library_path_lib.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/hook/ld_library_path_lib.ps1 b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/ld_library_path_lib.ps1 new file mode 100644 index 000000000..f6df601d0 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/ld_library_path_lib.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value LD_LIBRARY_PATH "$env:COLCON_CURRENT_PREFIX\lib" diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/hook/ld_library_path_lib.sh b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/ld_library_path_lib.sh new file mode 100644 index 000000000..ca3c1020b --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/hook/ld_library_path_lib.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value LD_LIBRARY_PATH "$COLCON_CURRENT_PREFIX/lib" diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/launch/image_rotate.launch.py b/src/image_pipeline/install/image_rotate/share/image_rotate/launch/image_rotate.launch.py new file mode 100644 index 000000000..7345a5ea8 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/launch/image_rotate.launch.py @@ -0,0 +1,44 @@ +# Copyright (c) 2008, Willow Garage, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Willow Garage 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 OWNER 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. + +from launch import LaunchDescription +import launch_ros.actions + + +def generate_launch_description(): + return LaunchDescription([ + launch_ros.actions.Node( + package='image_rotate', node_executable='image_rotate', output='screen', + remappings=[('image', '/camera/color/image_raw'), + ('camera_info', '/camera/color/camera_info'), + ('rotated/image', '/camera/color/image_raw_rotated')]), + ]) diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.bash b/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.bash new file mode 100644 index 000000000..49782f246 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.bash @@ -0,0 +1,46 @@ +# generated from ament_package/template/package_level/local_setup.bash.in + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.dsv b/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.dsv new file mode 100644 index 000000000..15c7d6b33 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.dsv @@ -0,0 +1,3 @@ +source;share/image_rotate/environment/ament_prefix_path.sh +source;share/image_rotate/environment/library_path.sh +source;share/image_rotate/environment/path.sh diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.sh b/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.sh new file mode 100644 index 000000000..02ec97ad7 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.sh @@ -0,0 +1,185 @@ +# generated from ament_package/template/package_level/local_setup.sh.in + +# since this file is sourced use either the provided AMENT_CURRENT_PREFIX +# or fall back to the destination set at configure time +: ${AMENT_CURRENT_PREFIX:="/home/DinoSage/rocket_league/src/image_pipeline/install/image_rotate"} +if [ ! -d "$AMENT_CURRENT_PREFIX" ]; then + if [ -z "$COLCON_CURRENT_PREFIX" ]; then + echo "The compile time prefix path '$AMENT_CURRENT_PREFIX' doesn't " \ + "exist. Consider sourcing a different extension than '.sh'." 1>&2 + else + AMENT_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" + fi +fi + +# function to append values to environment variables +# using colons as separators and avoiding leading separators +ament_append_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # avoid leading separator + eval _values=\"\$$_listname\" + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + _ament_append_value_IFS=$IFS + unset IFS + eval export $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + IFS=$_ament_append_value_IFS + unset _ament_append_value_IFS + fi + unset _values + + unset _value + unset _listname +} + +# function to append non-duplicate values to environment variables +# using colons as separators and avoiding leading separators +ament_append_unique_value() { + # arguments + _listname=$1 + _value=$2 + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\$$_listname + _duplicate= + _ament_append_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ $_item = $_value ]; then + _duplicate=1 + fi + done + unset _item + + # append only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid leading separator + if [ -z "$_values" ]; then + eval $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + fi + fi + IFS=$_ament_append_unique_value_IFS + unset _ament_append_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# function to prepend non-duplicate values to environment variables +# using colons as separators and avoiding trailing separators +ament_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\"\$$_listname\" + _duplicate= + _ament_prepend_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ "$_item" = "$_value" ]; then + _duplicate=1 + fi + done + unset _item + + # prepend only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid trailing separator + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval export $_listname=\"$_value:\$$_listname\" + #eval echo "prepend list \$$_listname" + fi + fi + IFS=$_ament_prepend_unique_value_IFS + unset _ament_prepend_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# list all environment hooks of this package +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/image_rotate/environment/ament_prefix_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/image_rotate/environment/library_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/image_rotate/environment/path.sh" + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array AMENT_ENVIRONMENT_HOOKS + fi + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + if [ -f "$_hook" ]; then + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + # trace output + if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_hook\"" + fi + . "$_hook" + fi + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +# reset AMENT_CURRENT_PREFIX after each package +# allowing to source multiple package-level setup files +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.zsh b/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.zsh new file mode 100644 index 000000000..fe161be53 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/local_setup.zsh @@ -0,0 +1,59 @@ +# generated from ament_package/template/package_level/local_setup.zsh.in + +AMENT_SHELL=zsh + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# function to convert array-like strings into arrays +# to wordaround SH_WORD_SPLIT not being set +ament_zsh_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +# the package-level local_setup file unsets AMENT_CURRENT_PREFIX +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/package.bash b/src/image_pipeline/install/image_rotate/share/image_rotate/package.bash new file mode 100644 index 000000000..d17b0a839 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/package.bash @@ -0,0 +1,39 @@ +# generated from colcon_bash/shell/template/package.bash.em + +# This script extends the environment for this package. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" +else + _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh script of this package +_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/image_rotate/package.sh" + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_bash_COLCON_CURRENT_PREFIX" + +# source bash hooks +_colcon_package_bash_source_script "$COLCON_CURRENT_PREFIX/share/image_rotate/local_setup.bash" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_bash_source_script +unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/package.dsv b/src/image_pipeline/install/image_rotate/share/image_rotate/package.dsv new file mode 100644 index 000000000..6ed7fc594 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/package.dsv @@ -0,0 +1,11 @@ +source;share/image_rotate/hook/cmake_prefix_path.ps1 +source;share/image_rotate/hook/cmake_prefix_path.dsv +source;share/image_rotate/hook/cmake_prefix_path.sh +source;share/image_rotate/hook/ld_library_path_lib.ps1 +source;share/image_rotate/hook/ld_library_path_lib.dsv +source;share/image_rotate/hook/ld_library_path_lib.sh +source;share/image_rotate/local_setup.bash +source;share/image_rotate/local_setup.dsv +source;share/image_rotate/local_setup.ps1 +source;share/image_rotate/local_setup.sh +source;share/image_rotate/local_setup.zsh diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/package.ps1 b/src/image_pipeline/install/image_rotate/share/image_rotate/package.ps1 new file mode 100644 index 000000000..c02d34070 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/package.ps1 @@ -0,0 +1,117 @@ +# generated from colcon_powershell/shell/template/package.ps1.em + +# function to append a value to a variable +# which uses colons as separators +# duplicates as well as leading separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_append_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + $_duplicate="" + # start with no values + $_all_values="" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -eq $_value) { + $_duplicate="1" + } + if ($_all_values) { + $_all_values="${_all_values};$_" + } else { + $_all_values="$_" + } + } + } + } + # append only non-duplicates + if (!$_duplicate) { + # avoid leading separator + if ($_all_values) { + $_all_values="${_all_values};${_value}" + } else { + $_all_values="${_value}" + } + } + + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_prepend_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + # start with the new value + $_all_values="$_value" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -ne $_value) { + # keep non-duplicate values + $_all_values="${_all_values};$_" + } + } + } + } + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +function colcon_package_source_powershell_script { + param ( + $_colcon_package_source_powershell_script + ) + # source script with conditional trace output + if (Test-Path $_colcon_package_source_powershell_script) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_package_source_powershell_script'" + } + . "$_colcon_package_source_powershell_script" + } else { + Write-Error "not found: '$_colcon_package_source_powershell_script'" + } +} + + +# a powershell script is able to determine its own path +# the prefix is two levels up from the package specific share directory +$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName + +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_rotate/hook/cmake_prefix_path.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_rotate/hook/ld_library_path_lib.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/image_rotate/local_setup.ps1" + +Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/package.sh b/src/image_pipeline/install/image_rotate/share/image_rotate/package.sh new file mode 100644 index 000000000..6ca5d8f9e --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/package.sh @@ -0,0 +1,88 @@ +# generated from colcon_core/shell/template/package.sh.em + +# This script extends the environment for this package. + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prepend_unique_value_IFS=$IFS + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set in zsh + if [ "$(command -v colcon_zsh_convert_to_array)" ]; then + colcon_zsh_convert_to_array _values + fi + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS=$_colcon_prepend_unique_value_IFS + unset _colcon_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/DinoSage/rocket_league/src/image_pipeline/install/image_rotate" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_package_sh_COLCON_CURRENT_PREFIX + return 1 + fi + COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" +fi +unset _colcon_package_sh_COLCON_CURRENT_PREFIX + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh hooks +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_rotate/hook/cmake_prefix_path.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_rotate/hook/ld_library_path_lib.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/image_rotate/local_setup.sh" + +unset _colcon_package_sh_source_script +unset COLCON_CURRENT_PREFIX + +# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/package.xml b/src/image_pipeline/install/image_rotate/share/image_rotate/package.xml new file mode 100644 index 000000000..192c440df --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/package.xml @@ -0,0 +1,60 @@ + + + + image_rotate + 4.0.0 + +

+ Contains a node that rotates an image stream in a way that minimizes + the angle between a vector in some arbitrary frame and a vector in the + camera frame. The frame of the outgoing image is published by the node. +

+

+ This node is intended to allow camera images to be visualized in an + orientation that is more intuitive than the hardware-constrained + orientation of the physical camera. This is particularly helpful, for + example, to show images from the PR2's forearm cameras with a + consistent up direction, despite the fact that the forearms need to + rotate in arbitrary ways during manipulation. +

+

+ It is not recommended to use the output from this node for further + computation, as it interpolates the source image, introduces black + borders, and does not output a camera_info. +

+
+ + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/image_rotate/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Blaise Gassend + + ament_cmake_auto + + class_loader + + cv_bridge + geometry_msgs + image_transport + libopencv-dev + rcl_interfaces + rclcpp + rclcpp_components + sensor_msgs + tf2 + tf2_geometry_msgs + tf2_ros + + ament_lint_auto + ament_lint_common + + + ament_cmake + +
diff --git a/src/image_pipeline/install/image_rotate/share/image_rotate/package.zsh b/src/image_pipeline/install/image_rotate/share/image_rotate/package.zsh new file mode 100644 index 000000000..fe7330eb7 --- /dev/null +++ b/src/image_pipeline/install/image_rotate/share/image_rotate/package.zsh @@ -0,0 +1,50 @@ +# generated from colcon_zsh/shell/template/package.zsh.em + +# This script extends the environment for this package. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" +else + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +colcon_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# source sh script of this package +_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/image_rotate/package.sh" +unset convert_zsh_to_array + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_zsh_COLCON_CURRENT_PREFIX" + +# source zsh hooks +_colcon_package_zsh_source_script "$COLCON_CURRENT_PREFIX/share/image_rotate/local_setup.zsh" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_zsh_source_script +unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/local_setup.bash b/src/image_pipeline/install/local_setup.bash new file mode 100644 index 000000000..efd5f8c9e --- /dev/null +++ b/src/image_pipeline/install/local_setup.bash @@ -0,0 +1,107 @@ +# generated from colcon_bash/shell/template/prefix.bash.em + +# This script extends the environment with all packages contained in this +# prefix path. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + _colcon_prefix_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" > /dev/null && pwd)" +else + _colcon_prefix_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prefix_bash_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prefix_bash_prepend_unique_value_IFS="$IFS" + IFS=":" + # start with the new value + _all_values="$_value" + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS="$_colcon_prefix_bash_prepend_unique_value_IFS" + unset _colcon_prefix_bash_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# add this prefix to the COLCON_PREFIX_PATH +_colcon_prefix_bash_prepend_unique_value COLCON_PREFIX_PATH "$_colcon_prefix_bash_COLCON_CURRENT_PREFIX" +unset _colcon_prefix_bash_prepend_unique_value + +# check environment variable for custom Python executable +if [ -n "$COLCON_PYTHON_EXECUTABLE" ]; then + if [ ! -f "$COLCON_PYTHON_EXECUTABLE" ]; then + echo "error: COLCON_PYTHON_EXECUTABLE '$COLCON_PYTHON_EXECUTABLE' doesn't exist" + return 1 + fi + _colcon_python_executable="$COLCON_PYTHON_EXECUTABLE" +else + # try the Python executable known at configure time + _colcon_python_executable="/usr/bin/python3" + # if it doesn't exist try a fall back + if [ ! -f "$_colcon_python_executable" ]; then + if ! /usr/bin/env python3 --version > /dev/null 2> /dev/null; then + echo "error: unable to find python3 executable" + return 1 + fi + _colcon_python_executable=`/usr/bin/env python3 -c "import sys; print(sys.executable)"` + fi +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# get all commands in topological order +_colcon_ordered_commands="$($_colcon_python_executable "$_colcon_prefix_bash_COLCON_CURRENT_PREFIX/_local_setup_util_sh.py" sh bash)" +unset _colcon_python_executable +if [ -n "$COLCON_TRACE" ]; then + echo "Execute generated script:" + echo "<<<" + echo "${_colcon_ordered_commands}" + echo ">>>" +fi +eval "${_colcon_ordered_commands}" +unset _colcon_ordered_commands + +unset _colcon_prefix_sh_source_script + +unset _colcon_prefix_bash_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/local_setup.ps1 b/src/image_pipeline/install/local_setup.ps1 new file mode 100644 index 000000000..6f68c8ded --- /dev/null +++ b/src/image_pipeline/install/local_setup.ps1 @@ -0,0 +1,55 @@ +# generated from colcon_powershell/shell/template/prefix.ps1.em + +# This script extends the environment with all packages contained in this +# prefix path. + +# check environment variable for custom Python executable +if ($env:COLCON_PYTHON_EXECUTABLE) { + if (!(Test-Path "$env:COLCON_PYTHON_EXECUTABLE" -PathType Leaf)) { + echo "error: COLCON_PYTHON_EXECUTABLE '$env:COLCON_PYTHON_EXECUTABLE' doesn't exist" + exit 1 + } + $_colcon_python_executable="$env:COLCON_PYTHON_EXECUTABLE" +} else { + # use the Python executable known at configure time + $_colcon_python_executable="/usr/bin/python3" + # if it doesn't exist try a fall back + if (!(Test-Path "$_colcon_python_executable" -PathType Leaf)) { + if (!(Get-Command "python3" -ErrorAction SilentlyContinue)) { + echo "error: unable to find python3 executable" + exit 1 + } + $_colcon_python_executable="python3" + } +} + +# function to source another script with conditional trace output +# first argument: the path of the script +function _colcon_prefix_powershell_source_script { + param ( + $_colcon_prefix_powershell_source_script_param + ) + # source script with conditional trace output + if (Test-Path $_colcon_prefix_powershell_source_script_param) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_prefix_powershell_source_script_param'" + } + . "$_colcon_prefix_powershell_source_script_param" + } else { + Write-Error "not found: '$_colcon_prefix_powershell_source_script_param'" + } +} + +# get all commands in topological order +$_colcon_ordered_commands = & "$_colcon_python_executable" "$(Split-Path $PSCommandPath -Parent)/_local_setup_util_ps1.py" ps1 + +# execute all commands in topological order +if ($env:COLCON_TRACE) { + echo "Execute generated script:" + echo "<<<" + $_colcon_ordered_commands.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries) | Write-Output + echo ">>>" +} +if ($_colcon_ordered_commands) { + $_colcon_ordered_commands.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries) | Invoke-Expression +} diff --git a/src/image_pipeline/install/local_setup.sh b/src/image_pipeline/install/local_setup.sh new file mode 100644 index 000000000..9a22b9146 --- /dev/null +++ b/src/image_pipeline/install/local_setup.sh @@ -0,0 +1,137 @@ +# generated from colcon_core/shell/template/prefix.sh.em + +# This script extends the environment with all packages contained in this +# prefix path. + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_prefix_sh_COLCON_CURRENT_PREFIX="/home/DinoSage/rocket_league/src/image_pipeline/install" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_prefix_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_prefix_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_prefix_sh_COLCON_CURRENT_PREFIX + return 1 + fi +else + _colcon_prefix_sh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prefix_sh_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prefix_sh_prepend_unique_value_IFS="$IFS" + IFS=":" + # start with the new value + _all_values="$_value" + _contained_value="" + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + _contained_value=1 + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + if [ -z "$_contained_value" ]; then + if [ -n "$COLCON_TRACE" ]; then + if [ "$_all_values" = "$_value" ]; then + echo "export $_listname=$_value" + else + echo "export $_listname=$_value:\$$_listname" + fi + fi + fi + unset _contained_value + # restore the field separator + IFS="$_colcon_prefix_sh_prepend_unique_value_IFS" + unset _colcon_prefix_sh_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# add this prefix to the COLCON_PREFIX_PATH +_colcon_prefix_sh_prepend_unique_value COLCON_PREFIX_PATH "$_colcon_prefix_sh_COLCON_CURRENT_PREFIX" +unset _colcon_prefix_sh_prepend_unique_value + +# check environment variable for custom Python executable +if [ -n "$COLCON_PYTHON_EXECUTABLE" ]; then + if [ ! -f "$COLCON_PYTHON_EXECUTABLE" ]; then + echo "error: COLCON_PYTHON_EXECUTABLE '$COLCON_PYTHON_EXECUTABLE' doesn't exist" + return 1 + fi + _colcon_python_executable="$COLCON_PYTHON_EXECUTABLE" +else + # try the Python executable known at configure time + _colcon_python_executable="/usr/bin/python3" + # if it doesn't exist try a fall back + if [ ! -f "$_colcon_python_executable" ]; then + if ! /usr/bin/env python3 --version > /dev/null 2> /dev/null; then + echo "error: unable to find python3 executable" + return 1 + fi + _colcon_python_executable=`/usr/bin/env python3 -c "import sys; print(sys.executable)"` + fi +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# get all commands in topological order +_colcon_ordered_commands="$($_colcon_python_executable "$_colcon_prefix_sh_COLCON_CURRENT_PREFIX/_local_setup_util_sh.py" sh)" +unset _colcon_python_executable +if [ -n "$COLCON_TRACE" ]; then + echo "_colcon_prefix_sh_source_script() { + if [ -f \"\$1\" ]; then + if [ -n \"\$COLCON_TRACE\" ]; then + echo \"# . \\\"\$1\\\"\" + fi + . \"\$1\" + else + echo \"not found: \\\"\$1\\\"\" 1>&2 + fi + }" + echo "# Execute generated script:" + echo "# <<<" + echo "${_colcon_ordered_commands}" + echo "# >>>" + echo "unset _colcon_prefix_sh_source_script" +fi +eval "${_colcon_ordered_commands}" +unset _colcon_ordered_commands + +unset _colcon_prefix_sh_source_script + +unset _colcon_prefix_sh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/local_setup.zsh b/src/image_pipeline/install/local_setup.zsh new file mode 100644 index 000000000..f7a8d904f --- /dev/null +++ b/src/image_pipeline/install/local_setup.zsh @@ -0,0 +1,120 @@ +# generated from colcon_zsh/shell/template/prefix.zsh.em + +# This script extends the environment with all packages contained in this +# prefix path. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + _colcon_prefix_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd)" +else + _colcon_prefix_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +_colcon_prefix_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prefix_zsh_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prefix_zsh_prepend_unique_value_IFS="$IFS" + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set + _colcon_prefix_zsh_convert_to_array _values + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS="$_colcon_prefix_zsh_prepend_unique_value_IFS" + unset _colcon_prefix_zsh_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# add this prefix to the COLCON_PREFIX_PATH +_colcon_prefix_zsh_prepend_unique_value COLCON_PREFIX_PATH "$_colcon_prefix_zsh_COLCON_CURRENT_PREFIX" +unset _colcon_prefix_zsh_prepend_unique_value +unset _colcon_prefix_zsh_convert_to_array + +# check environment variable for custom Python executable +if [ -n "$COLCON_PYTHON_EXECUTABLE" ]; then + if [ ! -f "$COLCON_PYTHON_EXECUTABLE" ]; then + echo "error: COLCON_PYTHON_EXECUTABLE '$COLCON_PYTHON_EXECUTABLE' doesn't exist" + return 1 + fi + _colcon_python_executable="$COLCON_PYTHON_EXECUTABLE" +else + # try the Python executable known at configure time + _colcon_python_executable="/usr/bin/python3" + # if it doesn't exist try a fall back + if [ ! -f "$_colcon_python_executable" ]; then + if ! /usr/bin/env python3 --version > /dev/null 2> /dev/null; then + echo "error: unable to find python3 executable" + return 1 + fi + _colcon_python_executable=`/usr/bin/env python3 -c "import sys; print(sys.executable)"` + fi +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# get all commands in topological order +_colcon_ordered_commands="$($_colcon_python_executable "$_colcon_prefix_zsh_COLCON_CURRENT_PREFIX/_local_setup_util_sh.py" sh zsh)" +unset _colcon_python_executable +if [ -n "$COLCON_TRACE" ]; then + echo "Execute generated script:" + echo "<<<" + echo "${_colcon_ordered_commands}" + echo ">>>" +fi +eval "${_colcon_ordered_commands}" +unset _colcon_ordered_commands + +unset _colcon_prefix_sh_source_script + +unset _colcon_prefix_zsh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/setup.bash b/src/image_pipeline/install/setup.bash new file mode 100644 index 000000000..782a5640d --- /dev/null +++ b/src/image_pipeline/install/setup.bash @@ -0,0 +1,31 @@ +# generated from colcon_bash/shell/template/prefix_chain.bash.em + +# This script extends the environment with the environment of other prefix +# paths which were sourced when this file was generated as well as all packages +# contained in this prefix path. + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_chain_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source chained prefixes +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script +COLCON_CURRENT_PREFIX="/opt/ros/iron" +_colcon_prefix_chain_bash_source_script "$COLCON_CURRENT_PREFIX/local_setup.bash" + +# source this prefix +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script +COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" > /dev/null && pwd)" +_colcon_prefix_chain_bash_source_script "$COLCON_CURRENT_PREFIX/local_setup.bash" + +unset COLCON_CURRENT_PREFIX +unset _colcon_prefix_chain_bash_source_script diff --git a/src/image_pipeline/install/setup.ps1 b/src/image_pipeline/install/setup.ps1 new file mode 100644 index 000000000..fe7014cb0 --- /dev/null +++ b/src/image_pipeline/install/setup.ps1 @@ -0,0 +1,29 @@ +# generated from colcon_powershell/shell/template/prefix_chain.ps1.em + +# This script extends the environment with the environment of other prefix +# paths which were sourced when this file was generated as well as all packages +# contained in this prefix path. + +# function to source another script with conditional trace output +# first argument: the path of the script +function _colcon_prefix_chain_powershell_source_script { + param ( + $_colcon_prefix_chain_powershell_source_script_param + ) + # source script with conditional trace output + if (Test-Path $_colcon_prefix_chain_powershell_source_script_param) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_prefix_chain_powershell_source_script_param'" + } + . "$_colcon_prefix_chain_powershell_source_script_param" + } else { + Write-Error "not found: '$_colcon_prefix_chain_powershell_source_script_param'" + } +} + +# source chained prefixes +_colcon_prefix_chain_powershell_source_script "/opt/ros/iron\local_setup.ps1" + +# source this prefix +$env:COLCON_CURRENT_PREFIX=(Split-Path $PSCommandPath -Parent) +_colcon_prefix_chain_powershell_source_script "$env:COLCON_CURRENT_PREFIX\local_setup.ps1" diff --git a/src/image_pipeline/install/setup.sh b/src/image_pipeline/install/setup.sh new file mode 100644 index 000000000..a16e02abe --- /dev/null +++ b/src/image_pipeline/install/setup.sh @@ -0,0 +1,45 @@ +# generated from colcon_core/shell/template/prefix_chain.sh.em + +# This script extends the environment with the environment of other prefix +# paths which were sourced when this file was generated as well as all packages +# contained in this prefix path. + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX=/home/DinoSage/rocket_league/src/image_pipeline/install +if [ ! -z "$COLCON_CURRENT_PREFIX" ]; then + _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +elif [ ! -d "$_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX + return 1 +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_chain_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source chained prefixes +# setting COLCON_CURRENT_PREFIX avoids relying on the build time prefix of the sourced script +COLCON_CURRENT_PREFIX="/opt/ros/iron" +_colcon_prefix_chain_sh_source_script "$COLCON_CURRENT_PREFIX/local_setup.sh" + + +# source this prefix +# setting COLCON_CURRENT_PREFIX avoids relying on the build time prefix of the sourced script +COLCON_CURRENT_PREFIX="$_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX" +_colcon_prefix_chain_sh_source_script "$COLCON_CURRENT_PREFIX/local_setup.sh" + +unset _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX +unset _colcon_prefix_chain_sh_source_script +unset COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/setup.zsh b/src/image_pipeline/install/setup.zsh new file mode 100644 index 000000000..77473f075 --- /dev/null +++ b/src/image_pipeline/install/setup.zsh @@ -0,0 +1,31 @@ +# generated from colcon_zsh/shell/template/prefix_chain.zsh.em + +# This script extends the environment with the environment of other prefix +# paths which were sourced when this file was generated as well as all packages +# contained in this prefix path. + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_chain_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source chained prefixes +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script +COLCON_CURRENT_PREFIX="/opt/ros/iron" +_colcon_prefix_chain_zsh_source_script "$COLCON_CURRENT_PREFIX/local_setup.zsh" + +# source this prefix +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script +COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd)" +_colcon_prefix_chain_zsh_source_script "$COLCON_CURRENT_PREFIX/local_setup.zsh" + +unset COLCON_CURRENT_PREFIX +unset _colcon_prefix_chain_zsh_source_script diff --git a/src/image_pipeline/install/stereo_image_proc/include/stereo_image_proc/stereo_processor.hpp b/src/image_pipeline/install/stereo_image_proc/include/stereo_image_proc/stereo_processor.hpp new file mode 100644 index 000000000..c909e7577 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/include/stereo_image_proc/stereo_processor.hpp @@ -0,0 +1,316 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * 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 OWNER 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. + +#ifndef STEREO_IMAGE_PROC__STEREO_PROCESSOR_HPP_ +#define STEREO_IMAGE_PROC__STEREO_PROCESSOR_HPP_ + +#include + +#include "image_geometry/stereo_camera_model.hpp" + +#include +#include +#include +#include + +namespace stereo_image_proc +{ + +struct StereoImageSet +{ + image_proc::ImageSet left; + image_proc::ImageSet right; + stereo_msgs::msg::DisparityImage disparity; + sensor_msgs::msg::PointCloud points; + sensor_msgs::msg::PointCloud2 points2; +}; + +class StereoProcessor +{ +public: + StereoProcessor() + { + block_matcher_ = cv::StereoBM::create(); + sg_block_matcher_ = cv::StereoSGBM::create(1, 1, 10); + } + + enum StereoType + { + BM, SGBM + }; + + enum + { + LEFT_MONO = 1 << 0, + LEFT_RECT = 1 << 1, + LEFT_COLOR = 1 << 2, + LEFT_RECT_COLOR = 1 << 3, + RIGHT_MONO = 1 << 4, + RIGHT_RECT = 1 << 5, + RIGHT_COLOR = 1 << 6, + RIGHT_RECT_COLOR = 1 << 7, + DISPARITY = 1 << 8, + POINT_CLOUD = 1 << 9, + POINT_CLOUD2 = 1 << 10, + LEFT_ALL = LEFT_MONO | LEFT_RECT | LEFT_COLOR | LEFT_RECT_COLOR, + RIGHT_ALL = RIGHT_MONO | RIGHT_RECT | RIGHT_COLOR | RIGHT_RECT_COLOR, + STEREO_ALL = DISPARITY | POINT_CLOUD | POINT_CLOUD2, + ALL = LEFT_ALL | RIGHT_ALL | STEREO_ALL + }; + + inline StereoType getStereoType() const + { + return current_stereo_algorithm_; + } + + inline void setStereoType(StereoType type) + { + current_stereo_algorithm_ = type; + } + + inline int getInterpolation() const + { + return mono_processor_.interpolation_; + } + + inline void setInterpolation(int interp) + { + mono_processor_.interpolation_ = interp; + } + + inline int getPreFilterCap() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getPreFilterCap(); + } + return sg_block_matcher_->getPreFilterCap(); + } + + inline void setPreFilterCap(int param) + { + block_matcher_->setPreFilterCap(param); + sg_block_matcher_->setPreFilterCap(param); + } + + inline int getCorrelationWindowSize() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getBlockSize(); + } + return sg_block_matcher_->getBlockSize(); + } + + inline void setCorrelationWindowSize(int param) + { + block_matcher_->setBlockSize(param); + sg_block_matcher_->setBlockSize(param); + } + + inline int getMinDisparity() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getMinDisparity(); + } + return sg_block_matcher_->getMinDisparity(); + } + + inline void setMinDisparity(int param) + { + block_matcher_->setMinDisparity(param); + sg_block_matcher_->setMinDisparity(param); + } + + inline int getDisparityRange() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getNumDisparities(); + } + return sg_block_matcher_->getNumDisparities(); + } + + inline void setDisparityRange(int param) + { + block_matcher_->setNumDisparities(param); + sg_block_matcher_->setNumDisparities(param); + } + + inline float getUniquenessRatio() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getUniquenessRatio(); + } + return sg_block_matcher_->getUniquenessRatio(); + } + + inline void setUniquenessRatio(float param) + { + block_matcher_->setUniquenessRatio(param); + sg_block_matcher_->setUniquenessRatio(param); + } + + inline int getSpeckleSize() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getSpeckleWindowSize(); + } + return sg_block_matcher_->getSpeckleWindowSize(); + } + + inline void setSpeckleSize(int param) + { + block_matcher_->setSpeckleWindowSize(param); + sg_block_matcher_->setSpeckleWindowSize(param); + } + + inline int getSpeckleRange() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getSpeckleRange(); + } + return sg_block_matcher_->getSpeckleRange(); + } + + inline void setSpeckleRange(int param) + { + block_matcher_->setSpeckleRange(param); + sg_block_matcher_->setSpeckleRange(param); + } + + // BM only + + inline int getPreFilterSize() const + { + return block_matcher_->getPreFilterSize(); + } + + inline void setPreFilterSize(int param) + { + block_matcher_->setPreFilterSize(param); + } + + inline int getTextureThreshold() const + { + return block_matcher_->getTextureThreshold(); + } + + inline void setTextureThreshold(int param) + { + block_matcher_->setTextureThreshold(param); + } + + // SGBM specific + + // getSgbmMode can return MODE_SGBM = 0, MODE_HH = 1. FullDP == 1 was MODE_HH so we're good + inline int getSgbmMode() const + { + return sg_block_matcher_->getMode(); + } + + inline void setSgbmMode(int param) + { + sg_block_matcher_->setMode(param); + } + + inline int getP1() const + { + return sg_block_matcher_->getP1(); + } + + inline void setP1(int param) + { + sg_block_matcher_->setP1(param); + } + + inline int getP2() const + { + return sg_block_matcher_->getP2(); + } + + inline void setP2(int param) + { + sg_block_matcher_->setP2(param); + } + + inline int getDisp12MaxDiff() const + { + return sg_block_matcher_->getDisp12MaxDiff(); + } + + inline void setDisp12MaxDiff(int param) + { + sg_block_matcher_->setDisp12MaxDiff(param); + } + + // Do all the work! + bool process( + const sensor_msgs::msg::Image::ConstSharedPtr & left_raw, + const sensor_msgs::msg::Image::ConstSharedPtr & right_raw, + const image_geometry::StereoCameraModel & model, + StereoImageSet & output, + int flags) const; + + void processDisparity( + const cv::Mat & left_rect, + const cv::Mat & right_rect, + const image_geometry::StereoCameraModel & model, + stereo_msgs::msg::DisparityImage & disparity) const; + + void processPoints( + const stereo_msgs::msg::DisparityImage & disparity, + const cv::Mat & color, + const std::string & encoding, + const image_geometry::StereoCameraModel & model, + sensor_msgs::msg::PointCloud & points) const; + + void processPoints2( + const stereo_msgs::msg::DisparityImage & disparity, + const cv::Mat & color, + const std::string & encoding, + const image_geometry::StereoCameraModel & model, + sensor_msgs::msg::PointCloud2 & points) const; + +private: + image_proc::Processor mono_processor_; + + /// Scratch buffer for 16-bit signed disparity image + mutable cv::Mat_ disparity16_; + /// Contains scratch buffers for block matching. + mutable cv::Ptr block_matcher_; + mutable cv::Ptr sg_block_matcher_; + StereoType current_stereo_algorithm_; + /// Scratch buffer for dense point cloud. + mutable cv::Mat_ dense_points_; +}; + +} // namespace stereo_image_proc + +#endif // STEREO_IMAGE_PROC__STEREO_PROCESSOR_HPP_ diff --git a/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/package_run_dependencies/stereo_image_proc b/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/package_run_dependencies/stereo_image_proc new file mode 100644 index 000000000..4913cff58 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/package_run_dependencies/stereo_image_proc @@ -0,0 +1 @@ +cv_bridge;image_geometry;image_proc;image_transport;message_filters;rclcpp;rclcpp_components;sensor_msgs;stereo_msgs;ament_cmake_pytest;ament_lint_auto;ament_lint_common;launch;launch_ros;launch_testing;launch_testing_ament_cmake;python3-opencv;python_cmake_module;rclpy;ros_testing \ No newline at end of file diff --git a/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/packages/stereo_image_proc b/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/packages/stereo_image_proc new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/parent_prefix_path/stereo_image_proc b/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/parent_prefix_path/stereo_image_proc new file mode 100644 index 000000000..63e46ad6b --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/parent_prefix_path/stereo_image_proc @@ -0,0 +1 @@ +/home/DinoSage/rocket_league/src/image_pipeline/install/image_proc:/home/DinoSage/rocket_league/src/image_pipeline/install/tracetools_image_pipeline:/opt/ros/iron \ No newline at end of file diff --git a/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/rclcpp_components/stereo_image_proc b/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/rclcpp_components/stereo_image_proc new file mode 100644 index 000000000..f40a911ba --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/ament_index/resource_index/rclcpp_components/stereo_image_proc @@ -0,0 +1,2 @@ +stereo_image_proc::DisparityNode;lib/libstereo_image_proc.so +stereo_image_proc::PointCloudNode;lib/libstereo_image_proc.so diff --git a/src/image_pipeline/install/stereo_image_proc/share/colcon-core/packages/stereo_image_proc b/src/image_pipeline/install/stereo_image_proc/share/colcon-core/packages/stereo_image_proc new file mode 100644 index 000000000..0c4d991fe --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/colcon-core/packages/stereo_image_proc @@ -0,0 +1 @@ +cv_bridge:image_geometry:image_proc:image_transport:message_filters:rclcpp:rclcpp_components:sensor_msgs:stereo_msgs \ No newline at end of file diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/ament_cmake_export_dependencies-extras.cmake b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/ament_cmake_export_dependencies-extras.cmake new file mode 100644 index 000000000..b6f73e36a --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/ament_cmake_export_dependencies-extras.cmake @@ -0,0 +1,92 @@ +# generated from ament_cmake_export_dependencies/cmake/ament_cmake_export_dependencies-extras.cmake.in + +set(_exported_dependencies "cv_bridge;image_geometry;image_proc;image_transport;message_filters;rclcpp;rclcpp_components;sensor_msgs;stereo_msgs") + +find_package(ament_cmake_libraries QUIET REQUIRED) + +# find_package() all dependencies +# and append their DEFINITIONS INCLUDE_DIRS, LIBRARIES, and LINK_FLAGS +# variables to stereo_image_proc_DEFINITIONS, stereo_image_proc_INCLUDE_DIRS, +# stereo_image_proc_LIBRARIES, and stereo_image_proc_LINK_FLAGS. +# Additionally collect the direct dependency names in +# stereo_image_proc_DEPENDENCIES as well as the recursive dependency names +# in stereo_image_proc_RECURSIVE_DEPENDENCIES. +if(NOT _exported_dependencies STREQUAL "") + find_package(ament_cmake_core QUIET REQUIRED) + set(stereo_image_proc_DEPENDENCIES ${_exported_dependencies}) + set(stereo_image_proc_RECURSIVE_DEPENDENCIES ${_exported_dependencies}) + set(_libraries) + foreach(_dep ${_exported_dependencies}) + if(NOT ${_dep}_FOUND) + find_package("${_dep}" QUIET REQUIRED) + endif() + # if a package provides modern CMake interface targets use them + # exclusively assuming the classic CMake variables only exist for + # backward compatibility + set(use_modern_cmake FALSE) + if(NOT "${${_dep}_TARGETS}" STREQUAL "") + foreach(_target ${${_dep}_TARGETS}) + # only use actual targets + # in case a package uses this variable for other content + if(TARGET "${_target}") + get_target_property(_include_dirs ${_target} INTERFACE_INCLUDE_DIRECTORIES) + if(_include_dirs) + list_append_unique(stereo_image_proc_INCLUDE_DIRS "${_include_dirs}") + endif() + + get_target_property(_imported_configurations ${_target} IMPORTED_CONFIGURATIONS) + if(_imported_configurations) + string(TOUPPER "${_imported_configurations}" _imported_configurations) + if(DEBUG_CONFIGURATIONS) + string(TOUPPER "${DEBUG_CONFIGURATIONS}" _debug_configurations_uppercase) + else() + set(_debug_configurations_uppercase "DEBUG") + endif() + foreach(_imported_config ${_imported_configurations}) + get_target_property(_imported_implib ${_target} IMPORTED_IMPLIB_${_imported_config}) + if(_imported_implib) + set(_imported_implib_config "optimized") + if(${_imported_config} IN_LIST _debug_configurations_uppercase) + set(_imported_implib_config "debug") + endif() + list(APPEND _libraries ${_imported_implib_config} ${_imported_implib}) + else() + get_target_property(_imported_location ${_target} IMPORTED_LOCATION_${_imported_config}) + if(_imported_location) + list(APPEND _libraries "${_imported_location}") + endif() + endif() + endforeach() + endif() + + get_target_property(_link_libraries ${_target} INTERFACE_LINK_LIBRARIES) + if(_link_libraries) + list(APPEND _libraries "${_link_libraries}") + endif() + set(use_modern_cmake TRUE) + endif() + endforeach() + endif() + if(NOT use_modern_cmake) + if(${_dep}_DEFINITIONS) + list_append_unique(stereo_image_proc_DEFINITIONS "${${_dep}_DEFINITIONS}") + endif() + if(${_dep}_INCLUDE_DIRS) + list_append_unique(stereo_image_proc_INCLUDE_DIRS "${${_dep}_INCLUDE_DIRS}") + endif() + if(${_dep}_LIBRARIES) + list(APPEND _libraries "${${_dep}_LIBRARIES}") + endif() + if(${_dep}_LINK_FLAGS) + list_append_unique(stereo_image_proc_LINK_FLAGS "${${_dep}_LINK_FLAGS}") + endif() + if(${_dep}_RECURSIVE_DEPENDENCIES) + list_append_unique(stereo_image_proc_RECURSIVE_DEPENDENCIES "${${_dep}_RECURSIVE_DEPENDENCIES}") + endif() + endif() + if(_libraries) + ament_libraries_deduplicate(_libraries "${_libraries}") + list(APPEND stereo_image_proc_LIBRARIES "${_libraries}") + endif() + endforeach() +endif() diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/ament_cmake_export_include_directories-extras.cmake b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/ament_cmake_export_include_directories-extras.cmake new file mode 100644 index 000000000..9e471abb3 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/ament_cmake_export_include_directories-extras.cmake @@ -0,0 +1,16 @@ +# generated from ament_cmake_export_include_directories/cmake/ament_cmake_export_include_directories-extras.cmake.in + +set(_exported_include_dirs "${stereo_image_proc_DIR}/../../../include") + +# append include directories to stereo_image_proc_INCLUDE_DIRS +# warn about not existing paths +if(NOT _exported_include_dirs STREQUAL "") + find_package(ament_cmake_core QUIET REQUIRED) + foreach(_exported_include_dir ${_exported_include_dirs}) + if(NOT IS_DIRECTORY "${_exported_include_dir}") + message(WARNING "Package 'stereo_image_proc' exports the include directory '${_exported_include_dir}' which doesn't exist") + endif() + normalize_path(_exported_include_dir "${_exported_include_dir}") + list(APPEND stereo_image_proc_INCLUDE_DIRS "${_exported_include_dir}") + endforeach() +endif() diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/ament_cmake_export_libraries-extras.cmake b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/ament_cmake_export_libraries-extras.cmake new file mode 100644 index 000000000..2388e376b --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/ament_cmake_export_libraries-extras.cmake @@ -0,0 +1,141 @@ +# generated from ament_cmake_export_libraries/cmake/template/ament_cmake_export_libraries.cmake.in + +set(_exported_libraries "stereo_image_proc") +set(_exported_library_names "") + +# populate stereo_image_proc_LIBRARIES +if(NOT _exported_libraries STREQUAL "") + # loop over libraries, either target names or absolute paths + list(LENGTH _exported_libraries _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_libraries ${_i} _arg) + + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND stereo_image_proc_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'stereo_image_proc' passes the build configuration keyword '${_cfg}' as the last exported library") + endif() + list(GET _exported_libraries ${_i} _library) + else() + # the value is a library without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + if(NOT IS_ABSOLUTE "${_library}") + # search for library target relative to this CMake file + set(_lib "NOTFOUND") + find_library( + _lib NAMES "${_library}" + PATHS "${stereo_image_proc_DIR}/../../../lib" + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + + if(NOT _lib) + # warn about not existing library and ignore it + message(FATAL_ERROR "Package 'stereo_image_proc' exports the library '${_library}' which couldn't be found") + elseif(NOT IS_ABSOLUTE "${_lib}") + # the found library must be an absolute path + message(FATAL_ERROR "Package 'stereo_image_proc' found the library '${_library}' at '${_lib}' which is not an absolute path") + elseif(NOT EXISTS "${_lib}") + # the found library must exist + message(FATAL_ERROR "Package 'stereo_image_proc' found the library '${_lib}' which doesn't exist") + else() + list(APPEND stereo_image_proc_LIBRARIES ${_cfg} "${_lib}") + endif() + + else() + if(NOT EXISTS "${_library}") + # the found library must exist + message(WARNING "Package 'stereo_image_proc' exports the library '${_library}' which doesn't exist") + else() + list(APPEND stereo_image_proc_LIBRARIES ${_cfg} "${_library}") + endif() + endif() + endwhile() +endif() + +# find_library() library names with optional LIBRARY_DIRS +# and add the libraries to stereo_image_proc_LIBRARIES +if(NOT _exported_library_names STREQUAL "") + # loop over library names + # but remember related build configuration keyword if available + list(LENGTH _exported_library_names _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_library_names ${_i} _arg) + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND stereo_image_proc_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library name + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'stereo_image_proc' passes the build configuration keyword '${_cfg}' as the last exported target") + endif() + list(GET _exported_library_names ${_i} _library) + else() + # the value is a library target without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + # extract optional LIBRARY_DIRS from library name + string(REPLACE ":" ";" _library_dirs "${_library}") + list(GET _library_dirs 0 _library_name) + list(REMOVE_AT _library_dirs 0) + + set(_lib "NOTFOUND") + if(NOT _library_dirs) + # search for library in the common locations + find_library( + _lib + NAMES "${_library_name}" + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING "Package 'stereo_image_proc' exports library '${_library_name}' which couldn't be found") + endif() + else() + # search for library in the specified directories + find_library( + _lib + NAMES "${_library_name}" + PATHS ${_library_dirs} + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING + "Package 'stereo_image_proc' exports library '${_library_name}' with LIBRARY_DIRS '${_library_dirs}' which couldn't be found") + endif() + endif() + if(_lib) + list(APPEND stereo_image_proc_LIBRARIES ${_cfg} "${_lib}") + endif() + endwhile() +endif() + +# TODO(dirk-thomas) deduplicate stereo_image_proc_LIBRARIES +# while maintaining library order +# as well as build configuration keywords +# as well as linker flags diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/stereo_image_procConfig-version.cmake b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/stereo_image_procConfig-version.cmake new file mode 100644 index 000000000..81dc15e08 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/stereo_image_procConfig-version.cmake @@ -0,0 +1,14 @@ +# generated from ament/cmake/core/templates/nameConfig-version.cmake.in +set(PACKAGE_VERSION "4.0.0") + +set(PACKAGE_VERSION_EXACT False) +set(PACKAGE_VERSION_COMPATIBLE False) + +if("${PACKAGE_FIND_VERSION}" VERSION_EQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT True) + set(PACKAGE_VERSION_COMPATIBLE True) +endif() + +if("${PACKAGE_FIND_VERSION}" VERSION_LESS "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE True) +endif() diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/stereo_image_procConfig.cmake b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/stereo_image_procConfig.cmake new file mode 100644 index 000000000..3bff0bbb8 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/cmake/stereo_image_procConfig.cmake @@ -0,0 +1,42 @@ +# generated from ament/cmake/core/templates/nameConfig.cmake.in + +# prevent multiple inclusion +if(_stereo_image_proc_CONFIG_INCLUDED) + # ensure to keep the found flag the same + if(NOT DEFINED stereo_image_proc_FOUND) + # explicitly set it to FALSE, otherwise CMake will set it to TRUE + set(stereo_image_proc_FOUND FALSE) + elseif(NOT stereo_image_proc_FOUND) + # use separate condition to avoid uninitialized variable warning + set(stereo_image_proc_FOUND FALSE) + endif() + return() +endif() +set(_stereo_image_proc_CONFIG_INCLUDED TRUE) + +# output package information +if(NOT stereo_image_proc_FIND_QUIETLY) + message(STATUS "Found stereo_image_proc: 4.0.0 (${stereo_image_proc_DIR})") +endif() + +# warn when using a deprecated package +if(NOT "" STREQUAL "") + set(_msg "Package 'stereo_image_proc' is deprecated") + # append custom deprecation text if available + if(NOT "" STREQUAL "TRUE") + set(_msg "${_msg} ()") + endif() + # optionally quiet the deprecation message + if(NOT stereo_image_proc_DEPRECATED_QUIET) + message(DEPRECATION "${_msg}") + endif() +endif() + +# flag package as ament-based to distinguish it after being find_package()-ed +set(stereo_image_proc_FOUND_AMENT_PACKAGE TRUE) + +# include all config extra files +set(_extras "ament_cmake_export_dependencies-extras.cmake;ament_cmake_export_include_directories-extras.cmake;ament_cmake_export_libraries-extras.cmake") +foreach(_extra ${_extras}) + include("${stereo_image_proc_DIR}/${_extra}") +endforeach() diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/ament_prefix_path.dsv b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/ament_prefix_path.dsv new file mode 100644 index 000000000..79d4c95b5 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/ament_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;AMENT_PREFIX_PATH; diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/ament_prefix_path.sh b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/ament_prefix_path.sh new file mode 100644 index 000000000..02e441b75 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/ament_prefix_path.sh @@ -0,0 +1,4 @@ +# copied from +# ament_cmake_core/cmake/environment_hooks/environment/ament_prefix_path.sh + +ament_prepend_unique_value AMENT_PREFIX_PATH "$AMENT_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/library_path.dsv b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/library_path.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/library_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/library_path.sh b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/library_path.sh new file mode 100644 index 000000000..292e518f1 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/library_path.sh @@ -0,0 +1,16 @@ +# copied from ament_package/template/environment_hook/library_path.sh + +# detect if running on Darwin platform +_UNAME=`uname -s` +_IS_DARWIN=0 +if [ "$_UNAME" = "Darwin" ]; then + _IS_DARWIN=1 +fi +unset _UNAME + +if [ $_IS_DARWIN -eq 0 ]; then + ament_prepend_unique_value LD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +else + ament_prepend_unique_value DYLD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +fi +unset _IS_DARWIN diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/path.dsv b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/path.dsv new file mode 100644 index 000000000..b94426af0 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate-if-exists;PATH;bin diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/path.sh b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/path.sh new file mode 100644 index 000000000..e59b749a8 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/environment/path.sh @@ -0,0 +1,5 @@ +# copied from ament_cmake_core/cmake/environment_hooks/environment/path.sh + +if [ -d "$AMENT_CURRENT_PREFIX/bin" ]; then + ament_prepend_unique_value PATH "$AMENT_CURRENT_PREFIX/bin" +fi diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/cmake_prefix_path.dsv b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/cmake_prefix_path.dsv new file mode 100644 index 000000000..e119f32cb --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/cmake_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;CMAKE_PREFIX_PATH; diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/cmake_prefix_path.ps1 b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/cmake_prefix_path.ps1 new file mode 100644 index 000000000..d03facc1a --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/cmake_prefix_path.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value CMAKE_PREFIX_PATH "$env:COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/cmake_prefix_path.sh b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/cmake_prefix_path.sh new file mode 100644 index 000000000..a948e685b --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/cmake_prefix_path.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value CMAKE_PREFIX_PATH "$COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/ld_library_path_lib.dsv b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/ld_library_path_lib.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/ld_library_path_lib.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/ld_library_path_lib.ps1 b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/ld_library_path_lib.ps1 new file mode 100644 index 000000000..f6df601d0 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/ld_library_path_lib.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value LD_LIBRARY_PATH "$env:COLCON_CURRENT_PREFIX\lib" diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/ld_library_path_lib.sh b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/ld_library_path_lib.sh new file mode 100644 index 000000000..ca3c1020b --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/hook/ld_library_path_lib.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value LD_LIBRARY_PATH "$COLCON_CURRENT_PREFIX/lib" diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/launch/stereo_image_proc.launch.py b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/launch/stereo_image_proc.launch.py new file mode 100644 index 000000000..4825b3568 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/launch/stereo_image_proc.launch.py @@ -0,0 +1,250 @@ +# Copyright (c) 2019, Open Source Robotics Foundation, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * 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 OWNER 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. + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.actions import GroupAction +from launch.actions import IncludeLaunchDescription +from launch.actions import SetLaunchConfiguration +from launch.conditions import IfCondition +from launch.conditions import LaunchConfigurationEquals +from launch.conditions import LaunchConfigurationNotEquals +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration +from launch.substitutions import PythonExpression +from launch_ros.actions import ComposableNodeContainer +from launch_ros.actions import LoadComposableNodes +from launch_ros.actions import PushRosNamespace +from launch_ros.descriptions import ComposableNode +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + composable_nodes = [ + ComposableNode( + package='stereo_image_proc', + plugin='stereo_image_proc::DisparityNode', + parameters=[{ + 'approximate_sync': LaunchConfiguration('approximate_sync'), + 'use_system_default_qos': LaunchConfiguration('use_system_default_qos'), + 'stereo_algorithm': LaunchConfiguration('stereo_algorithm'), + 'prefilter_size': LaunchConfiguration('prefilter_size'), + 'prefilter_cap': LaunchConfiguration('prefilter_cap'), + 'correlation_window_size': LaunchConfiguration('correlation_window_size'), + 'min_disparity': LaunchConfiguration('min_disparity'), + 'disparity_range': LaunchConfiguration('disparity_range'), + 'texture_threshold': LaunchConfiguration('texture_threshold'), + 'speckle_size': LaunchConfiguration('speckle_size'), + 'speckle_range': LaunchConfiguration('speckle_range'), + 'disp12_max_diff': LaunchConfiguration('disp12_max_diff'), + 'uniqueness_ratio': LaunchConfiguration('uniqueness_ratio'), + 'P1': LaunchConfiguration('P1'), + 'P2': LaunchConfiguration('P2'), + 'full_dp': LaunchConfiguration('full_dp'), + }], + remappings=[ + ('left/image_rect', [LaunchConfiguration('left_namespace'), '/image_rect']), + ('left/camera_info', [LaunchConfiguration('left_namespace'), '/camera_info']), + ('right/image_rect', [LaunchConfiguration('right_namespace'), '/image_rect']), + ('right/camera_info', [LaunchConfiguration('right_namespace'), '/camera_info']), + ] + ), + ComposableNode( + package='stereo_image_proc', + plugin='stereo_image_proc::PointCloudNode', + parameters=[{ + 'approximate_sync': LaunchConfiguration('approximate_sync'), + 'avoid_point_cloud_padding': LaunchConfiguration('avoid_point_cloud_padding'), + 'use_color': LaunchConfiguration('use_color'), + 'use_system_default_qos': LaunchConfiguration('use_system_default_qos'), + }], + remappings=[ + ('left/camera_info', [LaunchConfiguration('left_namespace'), '/camera_info']), + ('right/camera_info', [LaunchConfiguration('right_namespace'), '/camera_info']), + ( + 'left/image_rect_color', + [LaunchConfiguration('left_namespace'), '/image_rect_color'] + ), + ] + ), + ] + + return LaunchDescription([ + DeclareLaunchArgument( + name='approximate_sync', default_value='False', + description='Whether to use approximate synchronization of topics. Set to true if ' + 'the left and right cameras do not produce exactly synced timestamps.' + ), + DeclareLaunchArgument( + name='avoid_point_cloud_padding', default_value='False', + description='Avoid alignment padding in the generated point cloud.' + 'This reduces bandwidth requirements, as the point cloud size is halved.' + 'Using point clouds without alignment padding might degrade performance ' + 'for some algorithms.' + ), + DeclareLaunchArgument( + name='use_color', default_value='True', + description='Generate point cloud with rgb data.' + ), + DeclareLaunchArgument( + name='use_system_default_qos', default_value='False', + description='Use the RMW QoS settings for the image and camera info subscriptions.' + ), + DeclareLaunchArgument( + name='launch_image_proc', default_value='True', + description='Whether to launch debayer and rectify nodes from image_proc.' + ), + DeclareLaunchArgument( + name='left_namespace', default_value='left', + description='Namespace for the left camera' + ), + DeclareLaunchArgument( + name='right_namespace', default_value='right', + description='Namespace for the right camera' + ), + DeclareLaunchArgument( + name='container', default_value='', + description=( + 'Name of an existing node container to load launched nodes into. ' + 'If unset, a new container will be created.' + ) + ), + # Stereo algorithm parameters + DeclareLaunchArgument( + name='stereo_algorithm', default_value='0', + description='Stereo algorithm: Block Matching (0) or Semi-Global Block Matching (1)' + ), + DeclareLaunchArgument( + name='prefilter_size', default_value='9', + description='Normalization window size in pixels (must be odd)' + ), + DeclareLaunchArgument( + name='prefilter_cap', default_value='31', + description='Bound on normalized pixel values' + ), + DeclareLaunchArgument( + name='correlation_window_size', default_value='15', + description='SAD correlation window width in pixels (must be odd)' + ), + DeclareLaunchArgument( + name='min_disparity', default_value='0', + description='Disparity to begin search at in pixels' + ), + DeclareLaunchArgument( + name='disparity_range', default_value='64', + description='Number of disparities to search in pixels (must be a multiple of 16)' + ), + DeclareLaunchArgument( + name='texture_threshold', default_value='10', + description='Filter out if SAD window response does not exceed texture threshold' + ), + DeclareLaunchArgument( + name='speckle_size', default_value='100', + description='Reject regions smaller than this size in pixels' + ), + DeclareLaunchArgument( + name='speckle_range', default_value='4', + description='Maximum allowed difference between detected disparities' + ), + DeclareLaunchArgument( + name='disp12_max_diff', default_value='0', + description='Maximum allowed difference in the left-right disparity check in pixels ' + '(Semi-Global Block Matching only)' + ), + DeclareLaunchArgument( + name='uniqueness_ratio', default_value='15.0', + description='Filter out if best match does not sufficiently exceed the next-best match' + ), + DeclareLaunchArgument( + name='P1', default_value='200.0', + description='The first parameter ccontrolling the disparity smoothness ' + '(Semi-Global Block Matching only)' + ), + DeclareLaunchArgument( + name='P2', default_value='400.0', + description='The second parameter ccontrolling the disparity smoothness ' + '(Semi-Global Block Matching only)' + ), + DeclareLaunchArgument( + name='full_dp', default_value='False', + description='Run the full variant of the algorithm (Semi-Global Block Matching only)' + ), + ComposableNodeContainer( + condition=LaunchConfigurationEquals('container', ''), + package='rclcpp_components', + executable='component_container', + name='stereo_image_proc_container', + namespace='', + composable_node_descriptions=composable_nodes, + ), + LoadComposableNodes( + condition=LaunchConfigurationNotEquals('container', ''), + composable_node_descriptions=composable_nodes, + target_container=LaunchConfiguration('container'), + ), + # If a container name is not provided, + # set the name of the container launched above for image_proc nodes + SetLaunchConfiguration( + condition=LaunchConfigurationEquals('container', ''), + name='container', + value=PythonExpression([ + '"stereo_image_proc_container"', ' if ', + '"', LaunchConfiguration('ros_namespace', default=''), '"', + ' == "" else ', '"', + LaunchConfiguration('ros_namespace', default=''), '/stereo_image_proc_container"' + ]), + ), + GroupAction( + [ + PushRosNamespace(LaunchConfiguration('left_namespace')), + IncludeLaunchDescription( + PythonLaunchDescriptionSource([ + FindPackageShare('image_proc'), '/launch/image_proc.launch.py' + ]), + launch_arguments={'container': LaunchConfiguration('container')}.items() + ), + ], + condition=IfCondition(LaunchConfiguration('launch_image_proc')), + ), + GroupAction( + [ + PushRosNamespace(LaunchConfiguration('right_namespace')), + IncludeLaunchDescription( + PythonLaunchDescriptionSource([ + FindPackageShare('image_proc'), '/launch/image_proc.launch.py' + ]), + launch_arguments={'container': LaunchConfiguration('container')}.items() + ), + ], + condition=IfCondition(LaunchConfiguration('launch_image_proc')), + ) + ]) diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.bash b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.bash new file mode 100644 index 000000000..49782f246 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.bash @@ -0,0 +1,46 @@ +# generated from ament_package/template/package_level/local_setup.bash.in + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.dsv b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.dsv new file mode 100644 index 000000000..b751f8f24 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.dsv @@ -0,0 +1,3 @@ +source;share/stereo_image_proc/environment/ament_prefix_path.sh +source;share/stereo_image_proc/environment/library_path.sh +source;share/stereo_image_proc/environment/path.sh diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.sh b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.sh new file mode 100644 index 000000000..25f575cab --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.sh @@ -0,0 +1,185 @@ +# generated from ament_package/template/package_level/local_setup.sh.in + +# since this file is sourced use either the provided AMENT_CURRENT_PREFIX +# or fall back to the destination set at configure time +: ${AMENT_CURRENT_PREFIX:="/home/DinoSage/rocket_league/src/image_pipeline/install/stereo_image_proc"} +if [ ! -d "$AMENT_CURRENT_PREFIX" ]; then + if [ -z "$COLCON_CURRENT_PREFIX" ]; then + echo "The compile time prefix path '$AMENT_CURRENT_PREFIX' doesn't " \ + "exist. Consider sourcing a different extension than '.sh'." 1>&2 + else + AMENT_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" + fi +fi + +# function to append values to environment variables +# using colons as separators and avoiding leading separators +ament_append_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # avoid leading separator + eval _values=\"\$$_listname\" + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + _ament_append_value_IFS=$IFS + unset IFS + eval export $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + IFS=$_ament_append_value_IFS + unset _ament_append_value_IFS + fi + unset _values + + unset _value + unset _listname +} + +# function to append non-duplicate values to environment variables +# using colons as separators and avoiding leading separators +ament_append_unique_value() { + # arguments + _listname=$1 + _value=$2 + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\$$_listname + _duplicate= + _ament_append_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ $_item = $_value ]; then + _duplicate=1 + fi + done + unset _item + + # append only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid leading separator + if [ -z "$_values" ]; then + eval $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + fi + fi + IFS=$_ament_append_unique_value_IFS + unset _ament_append_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# function to prepend non-duplicate values to environment variables +# using colons as separators and avoiding trailing separators +ament_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\"\$$_listname\" + _duplicate= + _ament_prepend_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ "$_item" = "$_value" ]; then + _duplicate=1 + fi + done + unset _item + + # prepend only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid trailing separator + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval export $_listname=\"$_value:\$$_listname\" + #eval echo "prepend list \$$_listname" + fi + fi + IFS=$_ament_prepend_unique_value_IFS + unset _ament_prepend_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# list all environment hooks of this package +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/stereo_image_proc/environment/ament_prefix_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/stereo_image_proc/environment/library_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/stereo_image_proc/environment/path.sh" + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array AMENT_ENVIRONMENT_HOOKS + fi + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + if [ -f "$_hook" ]; then + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + # trace output + if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_hook\"" + fi + . "$_hook" + fi + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +# reset AMENT_CURRENT_PREFIX after each package +# allowing to source multiple package-level setup files +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.zsh b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.zsh new file mode 100644 index 000000000..fe161be53 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/local_setup.zsh @@ -0,0 +1,59 @@ +# generated from ament_package/template/package_level/local_setup.zsh.in + +AMENT_SHELL=zsh + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# function to convert array-like strings into arrays +# to wordaround SH_WORD_SPLIT not being set +ament_zsh_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +# the package-level local_setup file unsets AMENT_CURRENT_PREFIX +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.bash b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.bash new file mode 100644 index 000000000..3e54ca100 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.bash @@ -0,0 +1,39 @@ +# generated from colcon_bash/shell/template/package.bash.em + +# This script extends the environment for this package. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" +else + _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh script of this package +_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/stereo_image_proc/package.sh" + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_bash_COLCON_CURRENT_PREFIX" + +# source bash hooks +_colcon_package_bash_source_script "$COLCON_CURRENT_PREFIX/share/stereo_image_proc/local_setup.bash" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_bash_source_script +unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.dsv b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.dsv new file mode 100644 index 000000000..a00c32774 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.dsv @@ -0,0 +1,11 @@ +source;share/stereo_image_proc/hook/cmake_prefix_path.ps1 +source;share/stereo_image_proc/hook/cmake_prefix_path.dsv +source;share/stereo_image_proc/hook/cmake_prefix_path.sh +source;share/stereo_image_proc/hook/ld_library_path_lib.ps1 +source;share/stereo_image_proc/hook/ld_library_path_lib.dsv +source;share/stereo_image_proc/hook/ld_library_path_lib.sh +source;share/stereo_image_proc/local_setup.bash +source;share/stereo_image_proc/local_setup.dsv +source;share/stereo_image_proc/local_setup.ps1 +source;share/stereo_image_proc/local_setup.sh +source;share/stereo_image_proc/local_setup.zsh diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.ps1 b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.ps1 new file mode 100644 index 000000000..67a6566d9 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.ps1 @@ -0,0 +1,117 @@ +# generated from colcon_powershell/shell/template/package.ps1.em + +# function to append a value to a variable +# which uses colons as separators +# duplicates as well as leading separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_append_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + $_duplicate="" + # start with no values + $_all_values="" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -eq $_value) { + $_duplicate="1" + } + if ($_all_values) { + $_all_values="${_all_values};$_" + } else { + $_all_values="$_" + } + } + } + } + # append only non-duplicates + if (!$_duplicate) { + # avoid leading separator + if ($_all_values) { + $_all_values="${_all_values};${_value}" + } else { + $_all_values="${_value}" + } + } + + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_prepend_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + # start with the new value + $_all_values="$_value" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -ne $_value) { + # keep non-duplicate values + $_all_values="${_all_values};$_" + } + } + } + } + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +function colcon_package_source_powershell_script { + param ( + $_colcon_package_source_powershell_script + ) + # source script with conditional trace output + if (Test-Path $_colcon_package_source_powershell_script) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_package_source_powershell_script'" + } + . "$_colcon_package_source_powershell_script" + } else { + Write-Error "not found: '$_colcon_package_source_powershell_script'" + } +} + + +# a powershell script is able to determine its own path +# the prefix is two levels up from the package specific share directory +$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName + +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/stereo_image_proc/hook/cmake_prefix_path.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/stereo_image_proc/hook/ld_library_path_lib.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/stereo_image_proc/local_setup.ps1" + +Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.sh b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.sh new file mode 100644 index 000000000..df20b0898 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.sh @@ -0,0 +1,88 @@ +# generated from colcon_core/shell/template/package.sh.em + +# This script extends the environment for this package. + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prepend_unique_value_IFS=$IFS + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set in zsh + if [ "$(command -v colcon_zsh_convert_to_array)" ]; then + colcon_zsh_convert_to_array _values + fi + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS=$_colcon_prepend_unique_value_IFS + unset _colcon_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/DinoSage/rocket_league/src/image_pipeline/install/stereo_image_proc" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_package_sh_COLCON_CURRENT_PREFIX + return 1 + fi + COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" +fi +unset _colcon_package_sh_COLCON_CURRENT_PREFIX + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh hooks +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/stereo_image_proc/hook/cmake_prefix_path.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/stereo_image_proc/hook/ld_library_path_lib.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/stereo_image_proc/local_setup.sh" + +unset _colcon_package_sh_source_script +unset COLCON_CURRENT_PREFIX + +# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.xml b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.xml new file mode 100644 index 000000000..f42d8e45c --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.xml @@ -0,0 +1,48 @@ + + + + stereo_image_proc + 4.0.0 + Stereo and single image rectification and disparity processing. + + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/stereo_image_proc/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Patrick Mihelich + Kurt Konolige + Jeremy Leibs + + ament_cmake_auto + + cv_bridge + image_geometry + image_proc + image_transport + message_filters + rclcpp + rclcpp_components + sensor_msgs + stereo_msgs + + ament_cmake_pytest + ament_lint_auto + ament_lint_common + launch + launch_ros + launch_testing + launch_testing_ament_cmake + python3-opencv + python_cmake_module + rclpy + ros_testing + + + ament_cmake + + diff --git a/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.zsh b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.zsh new file mode 100644 index 000000000..2361630b7 --- /dev/null +++ b/src/image_pipeline/install/stereo_image_proc/share/stereo_image_proc/package.zsh @@ -0,0 +1,50 @@ +# generated from colcon_zsh/shell/template/package.zsh.em + +# This script extends the environment for this package. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" +else + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +colcon_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# source sh script of this package +_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/stereo_image_proc/package.sh" +unset convert_zsh_to_array + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_zsh_COLCON_CURRENT_PREFIX" + +# source zsh hooks +_colcon_package_zsh_source_script "$COLCON_CURRENT_PREFIX/share/stereo_image_proc/local_setup.zsh" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_zsh_source_script +unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/config.h b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/config.h new file mode 100644 index 000000000..7f4ffca11 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/config.h @@ -0,0 +1,22 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// Copyright 2019 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACETOOLS_IMAGE_PIPELINE__CONFIG_H_ +#define TRACETOOLS_IMAGE_PIPELINE__CONFIG_H_ + +/* #undef TRACETOOLS_DISABLED */ +#define TRACETOOLS_LTTNG_ENABLED + +#endif // TRACETOOLS_IMAGE_PIPELINE__CONFIG_H_ diff --git a/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/tp_call.h b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/tp_call.h new file mode 100644 index 000000000..70cdac38a --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/tp_call.h @@ -0,0 +1,105 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Provide fake header guard for cpplint +#undef TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ +#ifndef TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ +#define TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ + +#undef TRACEPOINT_PROVIDER +#define TRACEPOINT_PROVIDER ros2_image_pipeline + +#undef TRACEPOINT_INCLUDE +#define TRACEPOINT_INCLUDE "tracetools_image_pipeline/tp_call.h" + +#if !defined(_TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_) || defined(TRACEPOINT_HEADER_MULTI_READ) +#define _TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ + +#include + +#include +#include + + +// image_proc resize init callback +TRACEPOINT_EVENT( + TRACEPOINT_PROVIDER, // tracepoint provider name + image_proc_resize_init, // tracepoint name + TP_ARGS( + // input arguments, see https://lttng.org/docs/v2.12/#doc-tpp-def-input-args + const void *, resize_node_arg, + const void *, resize_image_msg_arg, + const void *, resize_info_msg_arg), + TP_FIELDS( + // output event fields, see https://lttng.org/man/3/lttng-ust/v2.12/#doc-ctf-macros + ctf_integer_hex(const void *, resize_node, resize_node_arg) + ctf_integer_hex(const void *, resize_image_msg, resize_image_msg_arg) + ctf_integer_hex(const void *, resize_info_msg, resize_info_msg_arg) + ctf_string(version, tracetools_image_pipeline_VERSION) + ) +) +// image_proc resize end of callback (after publication) +TRACEPOINT_EVENT( + TRACEPOINT_PROVIDER, + image_proc_resize_fini, + TP_ARGS( + const void *, resize_node_arg, + const void *, resize_image_msg_arg, + const void *, resize_info_msg_arg), + TP_FIELDS( + ctf_integer_hex(const void *, resize_node, resize_node_arg) + ctf_integer_hex(const void *, resize_image_msg, resize_image_msg_arg) + ctf_integer_hex(const void *, resize_info_msg, resize_info_msg_arg) + ctf_string(version, tracetools_image_pipeline_VERSION) + ) +) + +// image_proc rectify init callback +TRACEPOINT_EVENT( + TRACEPOINT_PROVIDER, // tracepoint provider name + image_proc_rectify_init, // tracepoint name + TP_ARGS( + // input arguments, see https://lttng.org/docs/v2.12/#doc-tpp-def-input-args + const void *, rectify_node_arg, + const void *, rectify_image_msg_arg, + const void *, rectify_info_msg_arg), + TP_FIELDS( + // output event fields, see https://lttng.org/man/3/lttng-ust/v2.12/#doc-ctf-macros + ctf_integer_hex(const void *, rectify_node, rectify_node_arg) + ctf_integer_hex(const void *, rectify_image_msg, rectify_image_msg_arg) + ctf_integer_hex(const void *, rectify_info_msg, rectify_info_msg_arg) + ctf_string(version, tracetools_image_pipeline_VERSION) + ) +) +// image_proc rectify end of callback (after publication) +TRACEPOINT_EVENT( + TRACEPOINT_PROVIDER, + image_proc_rectify_fini, + TP_ARGS( + const void *, rectify_node_arg, + const void *, rectify_image_msg_arg, + const void *, rectify_info_msg_arg), + TP_FIELDS( + ctf_integer_hex(const void *, rectify_node, rectify_node_arg) + ctf_integer_hex(const void *, rectify_image_msg, rectify_image_msg_arg) + ctf_integer_hex(const void *, rectify_info_msg, rectify_info_msg_arg) + ctf_string(version, tracetools_image_pipeline_VERSION) + ) +) + +#endif // _TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ + +#include + +#endif // TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ diff --git a/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/tracetools.h b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/tracetools.h new file mode 100644 index 000000000..9a5b2fb1a --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/tracetools.h @@ -0,0 +1,131 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** \mainpage tracetools_image_pipeline: tracing tools and instrumentation for + * for image_pipeline ROS 2 meta-package. + * + * `tracetools_image_pipeline` provides utilities to instrument ROS image_pipeline. + * It provides two main headers: + * + * - tracetools/tracetools.h + * - instrumentation functions + * - tracetools/utils.hpp + * - utility functions + */ + +#ifndef TRACETOOLS_IMAGE_PIPELINE__TRACETOOLS_H_ +#define TRACETOOLS_IMAGE_PIPELINE__TRACETOOLS_H_ + +#include +#include +#include +#include "tracetools_image_pipeline/config.h" +#include "tracetools_image_pipeline/visibility_control.hpp" + +#ifndef TRACETOOLS_DISABLED +/// Call a tracepoint. +/** + * This is the preferred method over calling the actual function directly. + */ +# define TRACEPOINT(event_name, ...) \ + (ros_trace_ ## event_name)(__VA_ARGS__) +# define DECLARE_TRACEPOINT(event_name, ...) \ + TRACETOOLS_PUBLIC void ros_trace_ ## event_name(__VA_ARGS__); +#else +# define TRACEPOINT(event_name, ...) ((void) (0)) +# define DECLARE_TRACEPOINT(event_name, ...) +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +/// Get tracing compilation status. +/** + * \return `true` if tracing is enabled, `false` otherwise + */ +TRACETOOLS_PUBLIC bool ros_trace_compile_status(); + +/// `image_proc_resize_init` +/** + * Tracepoint while initiating the callback of image_proc::ResizeNode component + * + * Notes the `tracetools_image_pipeline` version automatically. + * + * \param[in] resize_node rclcpp::node::Node subject to the callback + * \param[in] resize_image_msg image ROS message stored as sensor_msgs::msg::Image::ConstSharedPtr + * \param[in] resize_info_msg info ROS message as sensor_msgs::msg::CameraInfo::ConstSharedPtr + */ +DECLARE_TRACEPOINT( + image_proc_resize_init, + const void * resize_node, + const void * resize_image_msg, + const void * resize_info_msg) + +/// `image_proc_resize_fini` +/** + * Tracepoint while finishing the callback of image_proc::ResizeNode component + * + * Notes the `tracetools_image_pipeline` version automatically. + * + * \param[in] resize_node rclcpp::node::Node subject to the callback + * \param[in] resize_image_msg image ROS message stored as sensor_msgs::msg::Image::ConstSharedPtr + * \param[in] resize_info_msg info ROS message as sensor_msgs::msg::CameraInfo::ConstSharedPtr + */ +DECLARE_TRACEPOINT( + image_proc_resize_fini, + const void * resize_node, + const void * resize_image_msg, + const void * resize_info_msg) + +/// `image_proc_rectify_init` +/** + * Tracepoint while initiating the callback of image_proc::ResizeNode component + * + * Notes the `tracetools_image_pipeline` version automatically. + * + * \param[in] rectify_node rclcpp::node::Node subject to the callback + * \param[in] rectify_image_msg image ROS message stored as sensor_msgs::msg::Image::ConstSharedPtr + * \param[in] rectify_info_msg info ROS message as sensor_msgs::msg::CameraInfo::ConstSharedPtr + */ +DECLARE_TRACEPOINT( + image_proc_rectify_init, + const void * rectify_node, + const void * rectify_image_msg, + const void * rectify_info_msg) + +/// `image_proc_rectify_fini` +/** + * Tracepoint while finishing the callback of image_proc::ResizeNode component + * + * Notes the `tracetools_image_pipeline` version automatically. + * + * \param[in] rectify_node rclcpp::node::Node subject to the callback + * \param[in] rectify_image_msg image ROS message stored as sensor_msgs::msg::Image::ConstSharedPtr + * \param[in] rectify_info_msg info ROS message as sensor_msgs::msg::CameraInfo::ConstSharedPtr + */ +DECLARE_TRACEPOINT( + image_proc_rectify_fini, + const void * rectify_node, + const void * rectify_image_msg, + const void * rectify_info_msg) + + +#ifdef __cplusplus +} +#endif + +#endif // TRACETOOLS_IMAGE_PIPELINE__TRACETOOLS_H_ diff --git a/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/utils.hpp b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/utils.hpp new file mode 100644 index 000000000..6bc2f3d9f --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/utils.hpp @@ -0,0 +1,66 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// Copyright 2019 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACETOOLS_IMAGE_PIPELINE__UTILS_HPP_ +#define TRACETOOLS_IMAGE_PIPELINE__UTILS_HPP_ + +#include +#include + +#include "tracetools_image_pipeline/visibility_control.hpp" + +/// Default symbol, used when address resolution fails. +#define SYMBOL_UNKNOWN "UNKNOWN" + +TRACETOOLS_PUBLIC const char * _demangle_symbol(const char * mangled); + +TRACETOOLS_PUBLIC const char * _get_symbol_funcptr(void * funcptr); + +/// Get symbol from an std::function object. +/** + * If function address resolution or symbol demangling fails, + * this will return a string that starts with \ref SYMBOL_UNKNOWN. + * + * \param[in] f the std::function object + * \return the symbol, or a placeholder + */ +template +const char * get_symbol(std::function f) +{ + typedef T (fnType)(U...); + fnType ** fnPointer = f.template target(); + // If we get an actual address + if (fnPointer != nullptr) { + void * funcptr = reinterpret_cast(*fnPointer); + return _get_symbol_funcptr(funcptr); + } + // Otherwise we have to go through target_type() + return _demangle_symbol(f.target_type().name()); +} + +/// Get symbol from a function-related object. +/** + * Fallback meant for lambdas with captures. + * + * \param[in] l a generic object + * \return the symbol + */ +template +const char * get_symbol(L && l) +{ + return _demangle_symbol(typeid(l).name()); +} + +#endif // TRACETOOLS_IMAGE_PIPELINE__UTILS_HPP_ diff --git a/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/visibility_control.hpp b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/visibility_control.hpp new file mode 100644 index 000000000..b6ba91f6d --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/include/tracetools_image_pipeline/visibility_control.hpp @@ -0,0 +1,57 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// Copyright 2015 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* This header must be included by all TRACETOOLS headers which declare symbols + * which are defined in the TRACETOOLS library. When not building the TRACETOOLS + * library, i.e. when using the headers in other package's code, the contents + * of this header change the visibility of certain symbols which the TRACETOOLS + * library cannot have, but the consuming code must have inorder to link. + */ + +#ifndef TRACETOOLS_IMAGE_PIPELINE__VISIBILITY_CONTROL_HPP_ +#define TRACETOOLS_IMAGE_PIPELINE__VISIBILITY_CONTROL_HPP_ + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef __GNUC__ + #define TRACETOOLS_EXPORT __attribute__ ((dllexport)) + #define TRACETOOLS_IMPORT __attribute__ ((dllimport)) + #else + #define TRACETOOLS_EXPORT __declspec(dllexport) + #define TRACETOOLS_IMPORT __declspec(dllimport) + #endif + #ifdef TRACETOOLS_BUILDING_DLL + #define TRACETOOLS_PUBLIC TRACETOOLS_EXPORT + #else + #define TRACETOOLS_PUBLIC TRACETOOLS_IMPORT + #endif + #define TRACETOOLS_PUBLIC_TYPE TRACETOOLS_PUBLIC + #define TRACETOOLS_LOCAL +#else + #define TRACETOOLS_EXPORT __attribute__ ((visibility("default"))) + #define TRACETOOLS_IMPORT + #if __GNUC__ >= 4 + #define TRACETOOLS_PUBLIC __attribute__ ((visibility("default"))) + #define TRACETOOLS_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define TRACETOOLS_PUBLIC + #define TRACETOOLS_LOCAL + #endif + #define TRACETOOLS_PUBLIC_TYPE +#endif + +#endif // TRACETOOLS_IMAGE_PIPELINE__VISIBILITY_CONTROL_HPP_ diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/ament_index/resource_index/package_run_dependencies/tracetools_image_pipeline b/src/image_pipeline/install/tracetools_image_pipeline/share/ament_index/resource_index/package_run_dependencies/tracetools_image_pipeline new file mode 100644 index 000000000..25ce83abf --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/ament_index/resource_index/package_run_dependencies/tracetools_image_pipeline @@ -0,0 +1 @@ +ament_lint_auto;ament_lint_common \ No newline at end of file diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/ament_index/resource_index/packages/tracetools_image_pipeline b/src/image_pipeline/install/tracetools_image_pipeline/share/ament_index/resource_index/packages/tracetools_image_pipeline new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/ament_index/resource_index/parent_prefix_path/tracetools_image_pipeline b/src/image_pipeline/install/tracetools_image_pipeline/share/ament_index/resource_index/parent_prefix_path/tracetools_image_pipeline new file mode 100644 index 000000000..56c8678ea --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/ament_index/resource_index/parent_prefix_path/tracetools_image_pipeline @@ -0,0 +1 @@ +/opt/ros/iron \ No newline at end of file diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/colcon-core/packages/tracetools_image_pipeline b/src/image_pipeline/install/tracetools_image_pipeline/share/colcon-core/packages/tracetools_image_pipeline new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_include_directories-extras.cmake b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_include_directories-extras.cmake new file mode 100644 index 000000000..f8fae332b --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_include_directories-extras.cmake @@ -0,0 +1,16 @@ +# generated from ament_cmake_export_include_directories/cmake/ament_cmake_export_include_directories-extras.cmake.in + +set(_exported_include_dirs "${tracetools_image_pipeline_DIR}/../../../include") + +# append include directories to tracetools_image_pipeline_INCLUDE_DIRS +# warn about not existing paths +if(NOT _exported_include_dirs STREQUAL "") + find_package(ament_cmake_core QUIET REQUIRED) + foreach(_exported_include_dir ${_exported_include_dirs}) + if(NOT IS_DIRECTORY "${_exported_include_dir}") + message(WARNING "Package 'tracetools_image_pipeline' exports the include directory '${_exported_include_dir}' which doesn't exist") + endif() + normalize_path(_exported_include_dir "${_exported_include_dir}") + list(APPEND tracetools_image_pipeline_INCLUDE_DIRS "${_exported_include_dir}") + endforeach() +endif() diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_libraries-extras.cmake b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_libraries-extras.cmake new file mode 100644 index 000000000..34347e16d --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_libraries-extras.cmake @@ -0,0 +1,141 @@ +# generated from ament_cmake_export_libraries/cmake/template/ament_cmake_export_libraries.cmake.in + +set(_exported_libraries "tracetools_image_pipeline") +set(_exported_library_names "lttng-ust;lttng-ust-common;dl") + +# populate tracetools_image_pipeline_LIBRARIES +if(NOT _exported_libraries STREQUAL "") + # loop over libraries, either target names or absolute paths + list(LENGTH _exported_libraries _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_libraries ${_i} _arg) + + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND tracetools_image_pipeline_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'tracetools_image_pipeline' passes the build configuration keyword '${_cfg}' as the last exported library") + endif() + list(GET _exported_libraries ${_i} _library) + else() + # the value is a library without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + if(NOT IS_ABSOLUTE "${_library}") + # search for library target relative to this CMake file + set(_lib "NOTFOUND") + find_library( + _lib NAMES "${_library}" + PATHS "${tracetools_image_pipeline_DIR}/../../../lib" + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + + if(NOT _lib) + # warn about not existing library and ignore it + message(FATAL_ERROR "Package 'tracetools_image_pipeline' exports the library '${_library}' which couldn't be found") + elseif(NOT IS_ABSOLUTE "${_lib}") + # the found library must be an absolute path + message(FATAL_ERROR "Package 'tracetools_image_pipeline' found the library '${_library}' at '${_lib}' which is not an absolute path") + elseif(NOT EXISTS "${_lib}") + # the found library must exist + message(FATAL_ERROR "Package 'tracetools_image_pipeline' found the library '${_lib}' which doesn't exist") + else() + list(APPEND tracetools_image_pipeline_LIBRARIES ${_cfg} "${_lib}") + endif() + + else() + if(NOT EXISTS "${_library}") + # the found library must exist + message(WARNING "Package 'tracetools_image_pipeline' exports the library '${_library}' which doesn't exist") + else() + list(APPEND tracetools_image_pipeline_LIBRARIES ${_cfg} "${_library}") + endif() + endif() + endwhile() +endif() + +# find_library() library names with optional LIBRARY_DIRS +# and add the libraries to tracetools_image_pipeline_LIBRARIES +if(NOT _exported_library_names STREQUAL "") + # loop over library names + # but remember related build configuration keyword if available + list(LENGTH _exported_library_names _length) + set(_i 0) + while(_i LESS _length) + list(GET _exported_library_names ${_i} _arg) + # pass linker flags along + if("${_arg}" MATCHES "^-" AND NOT "${_arg}" MATCHES "^-[l|framework]") + list(APPEND tracetools_image_pipeline_LIBRARIES "${_arg}") + math(EXPR _i "${_i} + 1") + continue() + endif() + + if("${_arg}" MATCHES "^(debug|optimized|general)$") + # remember build configuration keyword + # and get following library name + set(_cfg "${_arg}") + math(EXPR _i "${_i} + 1") + if(_i EQUAL _length) + message(FATAL_ERROR "Package 'tracetools_image_pipeline' passes the build configuration keyword '${_cfg}' as the last exported target") + endif() + list(GET _exported_library_names ${_i} _library) + else() + # the value is a library target without a build configuration keyword + set(_cfg "") + set(_library "${_arg}") + endif() + math(EXPR _i "${_i} + 1") + + # extract optional LIBRARY_DIRS from library name + string(REPLACE ":" ";" _library_dirs "${_library}") + list(GET _library_dirs 0 _library_name) + list(REMOVE_AT _library_dirs 0) + + set(_lib "NOTFOUND") + if(NOT _library_dirs) + # search for library in the common locations + find_library( + _lib + NAMES "${_library_name}" + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING "Package 'tracetools_image_pipeline' exports library '${_library_name}' which couldn't be found") + endif() + else() + # search for library in the specified directories + find_library( + _lib + NAMES "${_library_name}" + PATHS ${_library_dirs} + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH + ) + if(NOT _lib) + # warn about not existing library and later ignore it + message(WARNING + "Package 'tracetools_image_pipeline' exports library '${_library_name}' with LIBRARY_DIRS '${_library_dirs}' which couldn't be found") + endif() + endif() + if(_lib) + list(APPEND tracetools_image_pipeline_LIBRARIES ${_cfg} "${_lib}") + endif() + endwhile() +endif() + +# TODO(dirk-thomas) deduplicate tracetools_image_pipeline_LIBRARIES +# while maintaining library order +# as well as build configuration keywords +# as well as linker flags diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_link_flags-extras.cmake b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_link_flags-extras.cmake new file mode 100644 index 000000000..540c42238 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_link_flags-extras.cmake @@ -0,0 +1,10 @@ +# generated from ament_cmake_export_link_flags/cmake/ament_cmake_export_link_flags-extras.cmake.in + +set(_exported_link_flags "-rdynamic") + +# append link_flags to tracetools_image_pipeline_LINK_FLAGS +if(NOT "${_exported_link_flags} " STREQUAL " ") + foreach(_link_flag ${_exported_link_flags}) + list(APPEND tracetools_image_pipeline_LINK_FLAGS "${_link_flag}") + endforeach() +endif() diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_targets-extras.cmake b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_targets-extras.cmake new file mode 100644 index 000000000..b2560f09e --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/ament_cmake_export_targets-extras.cmake @@ -0,0 +1,27 @@ +# generated from ament_cmake_export_targets/cmake/ament_cmake_export_targets-extras.cmake.in + +set(_exported_targets "tracetools_image_pipeline_export") + +# include all exported targets +if(NOT _exported_targets STREQUAL "") + foreach(_target ${_exported_targets}) + set(_export_file "${tracetools_image_pipeline_DIR}/${_target}Export.cmake") + include("${_export_file}") + + # extract the target names associated with the export + set(_regex "foreach\\((_cmake)?_expected_?[Tt]arget (IN ITEMS )?(.+)\\)") + file( + STRINGS "${_export_file}" _foreach_targets + REGEX "${_regex}") + list(LENGTH _foreach_targets _matches) + if(NOT _matches EQUAL 1) + message(FATAL_ERROR + "Failed to find exported target names in '${_export_file}'") + endif() + string(REGEX REPLACE "${_regex}" "\\3" _targets "${_foreach_targets}") + string(REPLACE " " ";" _targets "${_targets}") + list(LENGTH _targets _length) + + list(APPEND tracetools_image_pipeline_TARGETS ${_targets}) + endforeach() +endif() diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipelineConfig-version.cmake b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipelineConfig-version.cmake new file mode 100644 index 000000000..81dc15e08 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipelineConfig-version.cmake @@ -0,0 +1,14 @@ +# generated from ament/cmake/core/templates/nameConfig-version.cmake.in +set(PACKAGE_VERSION "4.0.0") + +set(PACKAGE_VERSION_EXACT False) +set(PACKAGE_VERSION_COMPATIBLE False) + +if("${PACKAGE_FIND_VERSION}" VERSION_EQUAL "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_EXACT True) + set(PACKAGE_VERSION_COMPATIBLE True) +endif() + +if("${PACKAGE_FIND_VERSION}" VERSION_LESS "${PACKAGE_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE True) +endif() diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipelineConfig.cmake b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipelineConfig.cmake new file mode 100644 index 000000000..1ed6b4e36 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipelineConfig.cmake @@ -0,0 +1,42 @@ +# generated from ament/cmake/core/templates/nameConfig.cmake.in + +# prevent multiple inclusion +if(_tracetools_image_pipeline_CONFIG_INCLUDED) + # ensure to keep the found flag the same + if(NOT DEFINED tracetools_image_pipeline_FOUND) + # explicitly set it to FALSE, otherwise CMake will set it to TRUE + set(tracetools_image_pipeline_FOUND FALSE) + elseif(NOT tracetools_image_pipeline_FOUND) + # use separate condition to avoid uninitialized variable warning + set(tracetools_image_pipeline_FOUND FALSE) + endif() + return() +endif() +set(_tracetools_image_pipeline_CONFIG_INCLUDED TRUE) + +# output package information +if(NOT tracetools_image_pipeline_FIND_QUIETLY) + message(STATUS "Found tracetools_image_pipeline: 4.0.0 (${tracetools_image_pipeline_DIR})") +endif() + +# warn when using a deprecated package +if(NOT "" STREQUAL "") + set(_msg "Package 'tracetools_image_pipeline' is deprecated") + # append custom deprecation text if available + if(NOT "" STREQUAL "TRUE") + set(_msg "${_msg} ()") + endif() + # optionally quiet the deprecation message + if(NOT tracetools_image_pipeline_DEPRECATED_QUIET) + message(DEPRECATION "${_msg}") + endif() +endif() + +# flag package as ament-based to distinguish it after being find_package()-ed +set(tracetools_image_pipeline_FOUND_AMENT_PACKAGE TRUE) + +# include all config extra files +set(_extras "ament_cmake_export_targets-extras.cmake;ament_cmake_export_include_directories-extras.cmake;ament_cmake_export_libraries-extras.cmake;ament_cmake_export_link_flags-extras.cmake") +foreach(_extra ${_extras}) + include("${tracetools_image_pipeline_DIR}/${_extra}") +endforeach() diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipeline_exportExport-noconfig.cmake b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipeline_exportExport-noconfig.cmake new file mode 100644 index 000000000..59f7aaa22 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipeline_exportExport-noconfig.cmake @@ -0,0 +1,19 @@ +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Import target "tracetools_image_pipeline::tracetools_image_pipeline" for configuration "" +set_property(TARGET tracetools_image_pipeline::tracetools_image_pipeline APPEND PROPERTY IMPORTED_CONFIGURATIONS NOCONFIG) +set_target_properties(tracetools_image_pipeline::tracetools_image_pipeline PROPERTIES + IMPORTED_LOCATION_NOCONFIG "${_IMPORT_PREFIX}/lib/libtracetools_image_pipeline.so" + IMPORTED_SONAME_NOCONFIG "libtracetools_image_pipeline.so" + ) + +list(APPEND _IMPORT_CHECK_TARGETS tracetools_image_pipeline::tracetools_image_pipeline ) +list(APPEND _IMPORT_CHECK_FILES_FOR_tracetools_image_pipeline::tracetools_image_pipeline "${_IMPORT_PREFIX}/lib/libtracetools_image_pipeline.so" ) + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipeline_exportExport.cmake b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipeline_exportExport.cmake new file mode 100644 index 000000000..6dcf77b3f --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/cmake/tracetools_image_pipeline_exportExport.cmake @@ -0,0 +1,99 @@ +# Generated by CMake + +if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.6) + message(FATAL_ERROR "CMake >= 2.6.0 required") +endif() +cmake_policy(PUSH) +cmake_policy(VERSION 2.6...3.20) +#---------------------------------------------------------------- +# Generated CMake target import file. +#---------------------------------------------------------------- + +# Commands may need to know the format version. +set(CMAKE_IMPORT_FILE_VERSION 1) + +# Protect against multiple inclusion, which would fail when already imported targets are added once more. +set(_targetsDefined) +set(_targetsNotDefined) +set(_expectedTargets) +foreach(_expectedTarget tracetools_image_pipeline::tracetools_image_pipeline) + list(APPEND _expectedTargets ${_expectedTarget}) + if(NOT TARGET ${_expectedTarget}) + list(APPEND _targetsNotDefined ${_expectedTarget}) + endif() + if(TARGET ${_expectedTarget}) + list(APPEND _targetsDefined ${_expectedTarget}) + endif() +endforeach() +if("${_targetsDefined}" STREQUAL "${_expectedTargets}") + unset(_targetsDefined) + unset(_targetsNotDefined) + unset(_expectedTargets) + set(CMAKE_IMPORT_FILE_VERSION) + cmake_policy(POP) + return() +endif() +if(NOT "${_targetsDefined}" STREQUAL "") + message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_targetsDefined}\nTargets not yet defined: ${_targetsNotDefined}\n") +endif() +unset(_targetsDefined) +unset(_targetsNotDefined) +unset(_expectedTargets) + + +# Compute the installation prefix relative to this file. +get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) +if(_IMPORT_PREFIX STREQUAL "/") + set(_IMPORT_PREFIX "") +endif() + +# Create imported target tracetools_image_pipeline::tracetools_image_pipeline +add_library(tracetools_image_pipeline::tracetools_image_pipeline SHARED IMPORTED) + +set_target_properties(tracetools_image_pipeline::tracetools_image_pipeline PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include;${_IMPORT_PREFIX}/include" + INTERFACE_LINK_LIBRARIES "lttng-ust;lttng-ust-common;dl;-rdynamic" +) + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "This file relies on consumers using CMake 2.8.12 or greater.") +endif() + +# Load information for each installed configuration. +get_filename_component(_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(GLOB CONFIG_FILES "${_DIR}/tracetools_image_pipeline_exportExport-*.cmake") +foreach(f ${CONFIG_FILES}) + include(${f}) +endforeach() + +# Cleanup temporary variables. +set(_IMPORT_PREFIX) + +# Loop over all imported files and verify that they actually exist +foreach(target ${_IMPORT_CHECK_TARGETS} ) + foreach(file ${_IMPORT_CHECK_FILES_FOR_${target}} ) + if(NOT EXISTS "${file}" ) + message(FATAL_ERROR "The imported target \"${target}\" references the file + \"${file}\" +but this file does not exist. Possible reasons include: +* The file was deleted, renamed, or moved to another location. +* An install or uninstall procedure did not complete successfully. +* The installation package was faulty and contained + \"${CMAKE_CURRENT_LIST_FILE}\" +but not all the files it references. +") + endif() + endforeach() + unset(_IMPORT_CHECK_FILES_FOR_${target}) +endforeach() +unset(_IMPORT_CHECK_TARGETS) + +# This file does not depend on other imported targets which have +# been exported from the same project but in a separate export set. + +# Commands beyond this point should not need to know the version. +set(CMAKE_IMPORT_FILE_VERSION) +cmake_policy(POP) diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/ament_prefix_path.dsv b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/ament_prefix_path.dsv new file mode 100644 index 000000000..79d4c95b5 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/ament_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;AMENT_PREFIX_PATH; diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/ament_prefix_path.sh b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/ament_prefix_path.sh new file mode 100644 index 000000000..02e441b75 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/ament_prefix_path.sh @@ -0,0 +1,4 @@ +# copied from +# ament_cmake_core/cmake/environment_hooks/environment/ament_prefix_path.sh + +ament_prepend_unique_value AMENT_PREFIX_PATH "$AMENT_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/library_path.dsv b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/library_path.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/library_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/library_path.sh b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/library_path.sh new file mode 100644 index 000000000..292e518f1 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/library_path.sh @@ -0,0 +1,16 @@ +# copied from ament_package/template/environment_hook/library_path.sh + +# detect if running on Darwin platform +_UNAME=`uname -s` +_IS_DARWIN=0 +if [ "$_UNAME" = "Darwin" ]; then + _IS_DARWIN=1 +fi +unset _UNAME + +if [ $_IS_DARWIN -eq 0 ]; then + ament_prepend_unique_value LD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +else + ament_prepend_unique_value DYLD_LIBRARY_PATH "$AMENT_CURRENT_PREFIX/lib" +fi +unset _IS_DARWIN diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/path.dsv b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/path.dsv new file mode 100644 index 000000000..b94426af0 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate-if-exists;PATH;bin diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/path.sh b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/path.sh new file mode 100644 index 000000000..e59b749a8 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/environment/path.sh @@ -0,0 +1,5 @@ +# copied from ament_cmake_core/cmake/environment_hooks/environment/path.sh + +if [ -d "$AMENT_CURRENT_PREFIX/bin" ]; then + ament_prepend_unique_value PATH "$AMENT_CURRENT_PREFIX/bin" +fi diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/cmake_prefix_path.dsv b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/cmake_prefix_path.dsv new file mode 100644 index 000000000..e119f32cb --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/cmake_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;CMAKE_PREFIX_PATH; diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/cmake_prefix_path.ps1 b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/cmake_prefix_path.ps1 new file mode 100644 index 000000000..d03facc1a --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/cmake_prefix_path.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value CMAKE_PREFIX_PATH "$env:COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/cmake_prefix_path.sh b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/cmake_prefix_path.sh new file mode 100644 index 000000000..a948e685b --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/cmake_prefix_path.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value CMAKE_PREFIX_PATH "$COLCON_CURRENT_PREFIX" diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/ld_library_path_lib.dsv b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/ld_library_path_lib.dsv new file mode 100644 index 000000000..89bec935b --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/ld_library_path_lib.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/ld_library_path_lib.ps1 b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/ld_library_path_lib.ps1 new file mode 100644 index 000000000..f6df601d0 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/ld_library_path_lib.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value LD_LIBRARY_PATH "$env:COLCON_CURRENT_PREFIX\lib" diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/ld_library_path_lib.sh b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/ld_library_path_lib.sh new file mode 100644 index 000000000..ca3c1020b --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/hook/ld_library_path_lib.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value LD_LIBRARY_PATH "$COLCON_CURRENT_PREFIX/lib" diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.bash b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.bash new file mode 100644 index 000000000..49782f246 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.bash @@ -0,0 +1,46 @@ +# generated from ament_package/template/package_level/local_setup.bash.in + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.dsv b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.dsv new file mode 100644 index 000000000..76f030778 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.dsv @@ -0,0 +1,3 @@ +source;share/tracetools_image_pipeline/environment/ament_prefix_path.sh +source;share/tracetools_image_pipeline/environment/library_path.sh +source;share/tracetools_image_pipeline/environment/path.sh diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.sh b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.sh new file mode 100644 index 000000000..b91d446f1 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.sh @@ -0,0 +1,185 @@ +# generated from ament_package/template/package_level/local_setup.sh.in + +# since this file is sourced use either the provided AMENT_CURRENT_PREFIX +# or fall back to the destination set at configure time +: ${AMENT_CURRENT_PREFIX:="/home/DinoSage/rocket_league/src/image_pipeline/install/tracetools_image_pipeline"} +if [ ! -d "$AMENT_CURRENT_PREFIX" ]; then + if [ -z "$COLCON_CURRENT_PREFIX" ]; then + echo "The compile time prefix path '$AMENT_CURRENT_PREFIX' doesn't " \ + "exist. Consider sourcing a different extension than '.sh'." 1>&2 + else + AMENT_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" + fi +fi + +# function to append values to environment variables +# using colons as separators and avoiding leading separators +ament_append_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # avoid leading separator + eval _values=\"\$$_listname\" + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + _ament_append_value_IFS=$IFS + unset IFS + eval export $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + IFS=$_ament_append_value_IFS + unset _ament_append_value_IFS + fi + unset _values + + unset _value + unset _listname +} + +# function to append non-duplicate values to environment variables +# using colons as separators and avoiding leading separators +ament_append_unique_value() { + # arguments + _listname=$1 + _value=$2 + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\$$_listname + _duplicate= + _ament_append_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ $_item = $_value ]; then + _duplicate=1 + fi + done + unset _item + + # append only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid leading separator + if [ -z "$_values" ]; then + eval $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval $_listname=\"\$$_listname:$_value\" + #eval echo "append list \$$_listname" + fi + fi + IFS=$_ament_append_unique_value_IFS + unset _ament_append_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# function to prepend non-duplicate values to environment variables +# using colons as separators and avoiding trailing separators +ament_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + #echo "listname $_listname" + #eval echo "list value \$$_listname" + #echo "value $_value" + + # check if the list contains the value + eval _values=\"\$$_listname\" + _duplicate= + _ament_prepend_unique_value_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array _values + fi + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + if [ "$_item" = "$_value" ]; then + _duplicate=1 + fi + done + unset _item + + # prepend only non-duplicates + if [ -z "$_duplicate" ]; then + # avoid trailing separator + if [ -z "$_values" ]; then + eval export $_listname=\"$_value\" + #eval echo "set list \$$_listname" + else + # field separator must not be a colon + unset IFS + eval export $_listname=\"$_value:\$$_listname\" + #eval echo "prepend list \$$_listname" + fi + fi + IFS=$_ament_prepend_unique_value_IFS + unset _ament_prepend_unique_value_IFS + unset _duplicate + unset _values + + unset _value + unset _listname +} + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# list all environment hooks of this package +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/tracetools_image_pipeline/environment/ament_prefix_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/tracetools_image_pipeline/environment/library_path.sh" +ament_append_value AMENT_ENVIRONMENT_HOOKS "$AMENT_CURRENT_PREFIX/share/tracetools_image_pipeline/environment/path.sh" + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + if [ "$AMENT_SHELL" = "zsh" ]; then + ament_zsh_to_array AMENT_ENVIRONMENT_HOOKS + fi + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + if [ -f "$_hook" ]; then + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + # trace output + if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_hook\"" + fi + . "$_hook" + fi + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +# reset AMENT_CURRENT_PREFIX after each package +# allowing to source multiple package-level setup files +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.zsh b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.zsh new file mode 100644 index 000000000..fe161be53 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/local_setup.zsh @@ -0,0 +1,59 @@ +# generated from ament_package/template/package_level/local_setup.zsh.in + +AMENT_SHELL=zsh + +# source local_setup.sh from same directory as this file +_this_path=$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd) +# provide AMENT_CURRENT_PREFIX to shell script +AMENT_CURRENT_PREFIX=$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd) +# store AMENT_CURRENT_PREFIX to restore it before each environment hook +_package_local_setup_AMENT_CURRENT_PREFIX=$AMENT_CURRENT_PREFIX + +# function to convert array-like strings into arrays +# to wordaround SH_WORD_SPLIT not being set +ament_zsh_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# trace output +if [ -n "$AMENT_TRACE_SETUP_FILES" ]; then + echo "# . \"$_this_path/local_setup.sh\"" +fi +# the package-level local_setup file unsets AMENT_CURRENT_PREFIX +. "$_this_path/local_setup.sh" +unset _this_path + +# unset AMENT_ENVIRONMENT_HOOKS +# if not appending to them for return +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + unset AMENT_ENVIRONMENT_HOOKS +fi + +# restore AMENT_CURRENT_PREFIX before evaluating the environment hooks +AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX +# list all environment hooks of this package + +# source all shell-specific environment hooks of this package +# if not returning them +if [ -z "$AMENT_RETURN_ENVIRONMENT_HOOKS" ]; then + _package_local_setup_IFS=$IFS + IFS=":" + for _hook in $AMENT_ENVIRONMENT_HOOKS; do + # restore AMENT_CURRENT_PREFIX for each environment hook + AMENT_CURRENT_PREFIX=$_package_local_setup_AMENT_CURRENT_PREFIX + # restore IFS before sourcing other files + IFS=$_package_local_setup_IFS + . "$_hook" + done + unset _hook + IFS=$_package_local_setup_IFS + unset _package_local_setup_IFS + unset AMENT_ENVIRONMENT_HOOKS +fi + +unset _package_local_setup_AMENT_CURRENT_PREFIX +unset AMENT_CURRENT_PREFIX diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.bash b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.bash new file mode 100644 index 000000000..0bd82ec48 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.bash @@ -0,0 +1,39 @@ +# generated from colcon_bash/shell/template/package.bash.em + +# This script extends the environment for this package. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" +else + _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh script of this package +_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/tracetools_image_pipeline/package.sh" + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_bash_COLCON_CURRENT_PREFIX" + +# source bash hooks +_colcon_package_bash_source_script "$COLCON_CURRENT_PREFIX/share/tracetools_image_pipeline/local_setup.bash" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_bash_source_script +unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.dsv b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.dsv new file mode 100644 index 000000000..981340cdc --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.dsv @@ -0,0 +1,11 @@ +source;share/tracetools_image_pipeline/hook/cmake_prefix_path.ps1 +source;share/tracetools_image_pipeline/hook/cmake_prefix_path.dsv +source;share/tracetools_image_pipeline/hook/cmake_prefix_path.sh +source;share/tracetools_image_pipeline/hook/ld_library_path_lib.ps1 +source;share/tracetools_image_pipeline/hook/ld_library_path_lib.dsv +source;share/tracetools_image_pipeline/hook/ld_library_path_lib.sh +source;share/tracetools_image_pipeline/local_setup.bash +source;share/tracetools_image_pipeline/local_setup.dsv +source;share/tracetools_image_pipeline/local_setup.ps1 +source;share/tracetools_image_pipeline/local_setup.sh +source;share/tracetools_image_pipeline/local_setup.zsh diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.ps1 b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.ps1 new file mode 100644 index 000000000..e3e111601 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.ps1 @@ -0,0 +1,117 @@ +# generated from colcon_powershell/shell/template/package.ps1.em + +# function to append a value to a variable +# which uses colons as separators +# duplicates as well as leading separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_append_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + $_duplicate="" + # start with no values + $_all_values="" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -eq $_value) { + $_duplicate="1" + } + if ($_all_values) { + $_all_values="${_all_values};$_" + } else { + $_all_values="$_" + } + } + } + } + # append only non-duplicates + if (!$_duplicate) { + # avoid leading separator + if ($_all_values) { + $_all_values="${_all_values};${_value}" + } else { + $_all_values="${_value}" + } + } + + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_prepend_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + # start with the new value + $_all_values="$_value" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -ne $_value) { + # keep non-duplicate values + $_all_values="${_all_values};$_" + } + } + } + } + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +function colcon_package_source_powershell_script { + param ( + $_colcon_package_source_powershell_script + ) + # source script with conditional trace output + if (Test-Path $_colcon_package_source_powershell_script) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_package_source_powershell_script'" + } + . "$_colcon_package_source_powershell_script" + } else { + Write-Error "not found: '$_colcon_package_source_powershell_script'" + } +} + + +# a powershell script is able to determine its own path +# the prefix is two levels up from the package specific share directory +$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName + +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/tracetools_image_pipeline/hook/cmake_prefix_path.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/tracetools_image_pipeline/hook/ld_library_path_lib.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/tracetools_image_pipeline/local_setup.ps1" + +Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.sh b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.sh new file mode 100644 index 000000000..86590d640 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.sh @@ -0,0 +1,88 @@ +# generated from colcon_core/shell/template/package.sh.em + +# This script extends the environment for this package. + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prepend_unique_value_IFS=$IFS + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set in zsh + if [ "$(command -v colcon_zsh_convert_to_array)" ]; then + colcon_zsh_convert_to_array _values + fi + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS=$_colcon_prepend_unique_value_IFS + unset _colcon_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/DinoSage/rocket_league/src/image_pipeline/install/tracetools_image_pipeline" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_package_sh_COLCON_CURRENT_PREFIX + return 1 + fi + COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" +fi +unset _colcon_package_sh_COLCON_CURRENT_PREFIX + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh hooks +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/tracetools_image_pipeline/hook/cmake_prefix_path.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/tracetools_image_pipeline/hook/ld_library_path_lib.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/tracetools_image_pipeline/local_setup.sh" + +unset _colcon_package_sh_source_script +unset COLCON_CURRENT_PREFIX + +# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.xml b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.xml new file mode 100644 index 000000000..a35c6a470 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.xml @@ -0,0 +1,23 @@ + + + + tracetools_image_pipeline + 4.0.0 + + LTTng tracing provider wrapper for image_pipeline ROS 2 meta-package. + + Víctor Mayoral-Vilches + Michael Ferguson + Apache 2.0 + Víctor Mayoral-Vilches + + ament_cmake_ros + pkg-config + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.zsh b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.zsh new file mode 100644 index 000000000..50bda5c25 --- /dev/null +++ b/src/image_pipeline/install/tracetools_image_pipeline/share/tracetools_image_pipeline/package.zsh @@ -0,0 +1,50 @@ +# generated from colcon_zsh/shell/template/package.zsh.em + +# This script extends the environment for this package. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" +else + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +colcon_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# source sh script of this package +_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/tracetools_image_pipeline/package.sh" +unset convert_zsh_to_array + +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts +COLCON_CURRENT_PREFIX="$_colcon_package_zsh_COLCON_CURRENT_PREFIX" + +# source zsh hooks +_colcon_package_zsh_source_script "$COLCON_CURRENT_PREFIX/share/tracetools_image_pipeline/local_setup.zsh" + +unset COLCON_CURRENT_PREFIX + +unset _colcon_package_zsh_source_script +unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/src/image_pipeline/log/COLCON_IGNORE b/src/image_pipeline/log/COLCON_IGNORE new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/log/latest b/src/image_pipeline/log/latest new file mode 120000 index 000000000..b57d247c7 --- /dev/null +++ b/src/image_pipeline/log/latest @@ -0,0 +1 @@ +latest_build \ No newline at end of file diff --git a/src/image_pipeline/log/latest_build b/src/image_pipeline/log/latest_build new file mode 120000 index 000000000..9d1a2b2ad --- /dev/null +++ b/src/image_pipeline/log/latest_build @@ -0,0 +1 @@ +build_2024-02-17_20-54-01 \ No newline at end of file diff --git a/src/image_pipeline/stereo_image_proc/CHANGELOG.rst b/src/image_pipeline/stereo_image_proc/CHANGELOG.rst new file mode 100644 index 000000000..b3f7494a6 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/CHANGELOG.rst @@ -0,0 +1,169 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package stereo_image_proc +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +4.0.0 (2022-12-24) +------------------ +* [backport iron] stereo_image_proc: cleanup cmake (`#904 `_) (`#908 `_) + This was supposed to be switched over when e-turtle rolled out. J-turtle + ain't that late... + backport https://github.com/ros-perception/image_pipeline/pull/904 + Co-authored-by: Michael Ferguson +* [backport iron] support rgba8 and bgra8 encodings by skipping alpha channel (`#869 `_) (`#896 `_) + backport `#869 `_ +* allow use as component or node (`#858 `_) + Backport `#852 `_ to Iron +* fix: change type for epsilon (`#822 `_) (`#849 `_) + Co-authored-by: Daisuke Nishimatsu <42202095+wep21@users.noreply.github.com> +* add myself as a maintainer (`#846 `_) +* Contributors: Alejandro Hernández Cordero, Michael Ferguson + +3.0.1 (2022-12-04) +------------------ +* Replace deprecated headers + Fixing compiler warnings. +* Add support for ApproximateEpsilonTime in stereo_image_proc and disparity_node +* Forward container namespace from stereo_image_proc -> image_proc (`#752 `_) +* Contributors: Brian, Ivan Santiago Paunovic, Jacob Perron + +3.0.0 (2022-04-29) +------------------ +* Fix the tests for stereo_image_proc. +* Cleanup stereo_image_proc +* Populate CameraInfo camera matrix in test fixture +* Use with_default_policies +* Improve formatting +* Use SubscriptionOptions +* Add subscriber qos overrides +* Remove QosPolicyKind::Invalid +* Allow QoS overrides for publishers +* Add missing test dependency +* Add color param to stereo_image_proc (`#661 `_) +* changes per comments +* fix for stereo_image_proc_tests +* Add maintainer (`#667 `_) +* Add disparity node parameters to launch file +* Fix disparity node parameter name +* Expose avoid_point_cloud_padding parameter in stereo_image_proc launch file (`#599 `_) +* Refactor image_proc and stereo_image_proc launch files (`#583 `_) +* Contributors: Audrow Nash, Chris Lalancette, Jacob Perron, Patrick Musau, Rebecca Butler + +2.2.1 (2020-08-27) +------------------ +* remove email blasts from steve macenski (`#596 `_) +* Refactor stereo_image_proc tests (`#588 `_) +* [Foxy] Use ament_auto Macros (`#573 `_) +* Contributors: Jacob Perron, Joshua Whitley, Steve Macenski + +2.2.0 (2020-07-27) +------------------ +* Replacing deprecated header includes with new HPP versions. (`#566 `_) +* Use newer 'add_on_set_parameters_callback' API (`#562 `_) + The old API was deprecated in Foxy and since removed in https://github.com/ros2/rclcpp/pull/1199. +* Contributors: Jacob Perron, Joshua Whitley + +* Initial ROS2 commit. +* Contributors: Michael Carroll + +1.12.23 (2018-05-10) +-------------------- +* Removed unused mutable scratch buffers (`#315 `_) + The uint32_t buffers conflicted with newer release of OpenCV3, as explained here https://github.com/ros-perception/image_pipeline/issues/310 +* Contributors: Miquel Massot + +1.12.22 (2017-12-08) +-------------------- + +1.12.21 (2017-11-05) +-------------------- +* Updated fix for traits change. (`#303 `_) +* Fix C++11 compilation + This fixes `#292 `_ and `#291 `_ +* Contributors: Mike Purvis, Vincent Rabaud + +1.12.20 (2017-04-30) +-------------------- +* fix doc jobs + This is a proper fix for `#233 `_ +* address gcc6 build error + With gcc6, compiling fails with `stdlib.h: No such file or directory`, + as including '-isystem /usr/include' breaks with gcc6, cf., + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70129. + This commit addresses this issue for this package in the same way + it was addressed in various other ROS packages. A list of related + commits and pull requests is at: + https://github.com/ros/rosdistro/issues/12783 + Signed-off-by: Lukas Bulwahn +* Contributors: Lukas Bulwahn, Vincent Rabaud + +1.12.19 (2016-07-24) +-------------------- + +1.12.18 (2016-07-12) +-------------------- + +1.12.17 (2016-07-11) +-------------------- + +1.12.16 (2016-03-19) +-------------------- +* clean OpenCV dependency in package.xml +* Contributors: Vincent Rabaud + +1.12.15 (2016-01-17) +-------------------- +* simplify OpenCV3 conversion +* Contributors: Vincent Rabaud + +1.12.14 (2015-07-22) +-------------------- +* add StereoSGBM and it can be chosen from dynamic_reconfigure +* Contributors: Ryohei Ueda + +1.12.13 (2015-04-06) +-------------------- +* get code to compile with OpenCV3 +* modify pointcloud data format of stereo_image_proc using point_cloud2_iterator +* Contributors: Hiroaki Yaguchi, Vincent Rabaud + +1.12.12 (2014-12-31) +-------------------- + +1.12.11 (2014-10-26) +-------------------- + +1.12.10 (2014-09-28) +-------------------- + +1.12.9 (2014-09-21) +------------------- +* get code to compile with OpenCV3 + fixes `#96 `_ +* Contributors: Vincent Rabaud + +1.12.8 (2014-08-19) +------------------- + +1.12.6 (2014-07-27) +------------------- + +1.12.4 (2014-04-28) +------------------- + +1.12.3 (2014-04-12) +------------------- + +1.12.2 (2014-04-08) +------------------- + +1.12.0 (2014-04-04) +------------------- +* remove PointCloud1 nodelets + +1.11.5 (2013-12-07 13:42:55 +0100) +---------------------------------- +- fix compilation on OSX (#50) + +1.11.4 (2013-11-23 13:10:55 +0100) +---------------------------------- +- convert images to MONO8 when computing disparity if needed (#49) diff --git a/src/image_pipeline/stereo_image_proc/CMakeLists.txt b/src/image_pipeline/stereo_image_proc/CMakeLists.txt new file mode 100644 index 000000000..2b0e2af41 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.5) +project(stereo_image_proc) + +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +find_package(ament_cmake_auto REQUIRED) +ament_auto_find_build_dependencies() + +find_package(OpenCV REQUIRED) + +# See note in image_proc/CMakeLists.txt +# add_definitions(-DOPENCV_TRAITS_ENABLE_DEPRECATED) + +ament_auto_add_library(${PROJECT_NAME} SHARED + src/${PROJECT_NAME}/stereo_processor.cpp + src/${PROJECT_NAME}/disparity_node.cpp + src/${PROJECT_NAME}/point_cloud_node.cpp +) +target_link_libraries(${PROJECT_NAME} + ${OpenCV_LIBRARIES} +) + +# Register individual components and also build standalone nodes for each +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "stereo_image_proc::DisparityNode" + EXECUTABLE disparity_node +) +rclcpp_components_register_node(${PROJECT_NAME} + PLUGIN "stereo_image_proc::PointCloudNode" + EXECUTABLE point_cloud_node +) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() + + set(_PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}") + if(WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug") + set(PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE_DEBUG}") + endif() + + ament_add_pytest_test("test_disparity_node" test/test_disparity_node.py + PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}" + ) + + ament_add_pytest_test("test_point_cloud_node" test/test_point_cloud_node.py + PYTHON_EXECUTABLE "${PYTHON_EXECUTABLE}" + ) + + set(PYTHON_EXECUTABLE "${_PYTHON_EXECUTABLE}") +endif() + +ament_auto_package(INSTALL_TO_SHARE launch) diff --git a/src/image_pipeline/stereo_image_proc/doc/mainpage.dox b/src/image_pipeline/stereo_image_proc/doc/mainpage.dox new file mode 100644 index 000000000..39b89a1e2 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/doc/mainpage.dox @@ -0,0 +1,14 @@ +/** + +@mainpage + +@htmlinclude manifest.html + +@b stereo_image_proc contains a node for performing rectification and +color processing on the raw images produced by a pair of stereo cameras. +It also produces 3d stereo outputs - the disparity image and point cloud. +See http://www.ros.org/wiki/stereo_image_proc for documentation. + +Currently this package has no public code API. + +*/ diff --git a/src/image_pipeline/stereo_image_proc/doc/stereo_frames.svg b/src/image_pipeline/stereo_image_proc/doc/stereo_frames.svg new file mode 100644 index 000000000..bac9f92cd --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/doc/stereo_frames.svg @@ -0,0 +1,1052 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + X + Y + Z + + + + + + + + + + + + + + + + + + + + + + + + LeftImager + RightImager + Point Cloud + + + diff --git a/src/image_pipeline/stereo_image_proc/include/stereo_image_proc/stereo_processor.hpp b/src/image_pipeline/stereo_image_proc/include/stereo_image_proc/stereo_processor.hpp new file mode 100644 index 000000000..c909e7577 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/include/stereo_image_proc/stereo_processor.hpp @@ -0,0 +1,316 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * 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 OWNER 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. + +#ifndef STEREO_IMAGE_PROC__STEREO_PROCESSOR_HPP_ +#define STEREO_IMAGE_PROC__STEREO_PROCESSOR_HPP_ + +#include + +#include "image_geometry/stereo_camera_model.hpp" + +#include +#include +#include +#include + +namespace stereo_image_proc +{ + +struct StereoImageSet +{ + image_proc::ImageSet left; + image_proc::ImageSet right; + stereo_msgs::msg::DisparityImage disparity; + sensor_msgs::msg::PointCloud points; + sensor_msgs::msg::PointCloud2 points2; +}; + +class StereoProcessor +{ +public: + StereoProcessor() + { + block_matcher_ = cv::StereoBM::create(); + sg_block_matcher_ = cv::StereoSGBM::create(1, 1, 10); + } + + enum StereoType + { + BM, SGBM + }; + + enum + { + LEFT_MONO = 1 << 0, + LEFT_RECT = 1 << 1, + LEFT_COLOR = 1 << 2, + LEFT_RECT_COLOR = 1 << 3, + RIGHT_MONO = 1 << 4, + RIGHT_RECT = 1 << 5, + RIGHT_COLOR = 1 << 6, + RIGHT_RECT_COLOR = 1 << 7, + DISPARITY = 1 << 8, + POINT_CLOUD = 1 << 9, + POINT_CLOUD2 = 1 << 10, + LEFT_ALL = LEFT_MONO | LEFT_RECT | LEFT_COLOR | LEFT_RECT_COLOR, + RIGHT_ALL = RIGHT_MONO | RIGHT_RECT | RIGHT_COLOR | RIGHT_RECT_COLOR, + STEREO_ALL = DISPARITY | POINT_CLOUD | POINT_CLOUD2, + ALL = LEFT_ALL | RIGHT_ALL | STEREO_ALL + }; + + inline StereoType getStereoType() const + { + return current_stereo_algorithm_; + } + + inline void setStereoType(StereoType type) + { + current_stereo_algorithm_ = type; + } + + inline int getInterpolation() const + { + return mono_processor_.interpolation_; + } + + inline void setInterpolation(int interp) + { + mono_processor_.interpolation_ = interp; + } + + inline int getPreFilterCap() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getPreFilterCap(); + } + return sg_block_matcher_->getPreFilterCap(); + } + + inline void setPreFilterCap(int param) + { + block_matcher_->setPreFilterCap(param); + sg_block_matcher_->setPreFilterCap(param); + } + + inline int getCorrelationWindowSize() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getBlockSize(); + } + return sg_block_matcher_->getBlockSize(); + } + + inline void setCorrelationWindowSize(int param) + { + block_matcher_->setBlockSize(param); + sg_block_matcher_->setBlockSize(param); + } + + inline int getMinDisparity() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getMinDisparity(); + } + return sg_block_matcher_->getMinDisparity(); + } + + inline void setMinDisparity(int param) + { + block_matcher_->setMinDisparity(param); + sg_block_matcher_->setMinDisparity(param); + } + + inline int getDisparityRange() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getNumDisparities(); + } + return sg_block_matcher_->getNumDisparities(); + } + + inline void setDisparityRange(int param) + { + block_matcher_->setNumDisparities(param); + sg_block_matcher_->setNumDisparities(param); + } + + inline float getUniquenessRatio() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getUniquenessRatio(); + } + return sg_block_matcher_->getUniquenessRatio(); + } + + inline void setUniquenessRatio(float param) + { + block_matcher_->setUniquenessRatio(param); + sg_block_matcher_->setUniquenessRatio(param); + } + + inline int getSpeckleSize() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getSpeckleWindowSize(); + } + return sg_block_matcher_->getSpeckleWindowSize(); + } + + inline void setSpeckleSize(int param) + { + block_matcher_->setSpeckleWindowSize(param); + sg_block_matcher_->setSpeckleWindowSize(param); + } + + inline int getSpeckleRange() const + { + if (current_stereo_algorithm_ == BM) { + return block_matcher_->getSpeckleRange(); + } + return sg_block_matcher_->getSpeckleRange(); + } + + inline void setSpeckleRange(int param) + { + block_matcher_->setSpeckleRange(param); + sg_block_matcher_->setSpeckleRange(param); + } + + // BM only + + inline int getPreFilterSize() const + { + return block_matcher_->getPreFilterSize(); + } + + inline void setPreFilterSize(int param) + { + block_matcher_->setPreFilterSize(param); + } + + inline int getTextureThreshold() const + { + return block_matcher_->getTextureThreshold(); + } + + inline void setTextureThreshold(int param) + { + block_matcher_->setTextureThreshold(param); + } + + // SGBM specific + + // getSgbmMode can return MODE_SGBM = 0, MODE_HH = 1. FullDP == 1 was MODE_HH so we're good + inline int getSgbmMode() const + { + return sg_block_matcher_->getMode(); + } + + inline void setSgbmMode(int param) + { + sg_block_matcher_->setMode(param); + } + + inline int getP1() const + { + return sg_block_matcher_->getP1(); + } + + inline void setP1(int param) + { + sg_block_matcher_->setP1(param); + } + + inline int getP2() const + { + return sg_block_matcher_->getP2(); + } + + inline void setP2(int param) + { + sg_block_matcher_->setP2(param); + } + + inline int getDisp12MaxDiff() const + { + return sg_block_matcher_->getDisp12MaxDiff(); + } + + inline void setDisp12MaxDiff(int param) + { + sg_block_matcher_->setDisp12MaxDiff(param); + } + + // Do all the work! + bool process( + const sensor_msgs::msg::Image::ConstSharedPtr & left_raw, + const sensor_msgs::msg::Image::ConstSharedPtr & right_raw, + const image_geometry::StereoCameraModel & model, + StereoImageSet & output, + int flags) const; + + void processDisparity( + const cv::Mat & left_rect, + const cv::Mat & right_rect, + const image_geometry::StereoCameraModel & model, + stereo_msgs::msg::DisparityImage & disparity) const; + + void processPoints( + const stereo_msgs::msg::DisparityImage & disparity, + const cv::Mat & color, + const std::string & encoding, + const image_geometry::StereoCameraModel & model, + sensor_msgs::msg::PointCloud & points) const; + + void processPoints2( + const stereo_msgs::msg::DisparityImage & disparity, + const cv::Mat & color, + const std::string & encoding, + const image_geometry::StereoCameraModel & model, + sensor_msgs::msg::PointCloud2 & points) const; + +private: + image_proc::Processor mono_processor_; + + /// Scratch buffer for 16-bit signed disparity image + mutable cv::Mat_ disparity16_; + /// Contains scratch buffers for block matching. + mutable cv::Ptr block_matcher_; + mutable cv::Ptr sg_block_matcher_; + StereoType current_stereo_algorithm_; + /// Scratch buffer for dense point cloud. + mutable cv::Mat_ dense_points_; +}; + +} // namespace stereo_image_proc + +#endif // STEREO_IMAGE_PROC__STEREO_PROCESSOR_HPP_ diff --git a/src/image_pipeline/stereo_image_proc/launch/stereo_image_proc.launch.py b/src/image_pipeline/stereo_image_proc/launch/stereo_image_proc.launch.py new file mode 100644 index 000000000..4825b3568 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/launch/stereo_image_proc.launch.py @@ -0,0 +1,250 @@ +# Copyright (c) 2019, Open Source Robotics Foundation, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * 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 OWNER 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. + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument +from launch.actions import GroupAction +from launch.actions import IncludeLaunchDescription +from launch.actions import SetLaunchConfiguration +from launch.conditions import IfCondition +from launch.conditions import LaunchConfigurationEquals +from launch.conditions import LaunchConfigurationNotEquals +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration +from launch.substitutions import PythonExpression +from launch_ros.actions import ComposableNodeContainer +from launch_ros.actions import LoadComposableNodes +from launch_ros.actions import PushRosNamespace +from launch_ros.descriptions import ComposableNode +from launch_ros.substitutions import FindPackageShare + + +def generate_launch_description(): + composable_nodes = [ + ComposableNode( + package='stereo_image_proc', + plugin='stereo_image_proc::DisparityNode', + parameters=[{ + 'approximate_sync': LaunchConfiguration('approximate_sync'), + 'use_system_default_qos': LaunchConfiguration('use_system_default_qos'), + 'stereo_algorithm': LaunchConfiguration('stereo_algorithm'), + 'prefilter_size': LaunchConfiguration('prefilter_size'), + 'prefilter_cap': LaunchConfiguration('prefilter_cap'), + 'correlation_window_size': LaunchConfiguration('correlation_window_size'), + 'min_disparity': LaunchConfiguration('min_disparity'), + 'disparity_range': LaunchConfiguration('disparity_range'), + 'texture_threshold': LaunchConfiguration('texture_threshold'), + 'speckle_size': LaunchConfiguration('speckle_size'), + 'speckle_range': LaunchConfiguration('speckle_range'), + 'disp12_max_diff': LaunchConfiguration('disp12_max_diff'), + 'uniqueness_ratio': LaunchConfiguration('uniqueness_ratio'), + 'P1': LaunchConfiguration('P1'), + 'P2': LaunchConfiguration('P2'), + 'full_dp': LaunchConfiguration('full_dp'), + }], + remappings=[ + ('left/image_rect', [LaunchConfiguration('left_namespace'), '/image_rect']), + ('left/camera_info', [LaunchConfiguration('left_namespace'), '/camera_info']), + ('right/image_rect', [LaunchConfiguration('right_namespace'), '/image_rect']), + ('right/camera_info', [LaunchConfiguration('right_namespace'), '/camera_info']), + ] + ), + ComposableNode( + package='stereo_image_proc', + plugin='stereo_image_proc::PointCloudNode', + parameters=[{ + 'approximate_sync': LaunchConfiguration('approximate_sync'), + 'avoid_point_cloud_padding': LaunchConfiguration('avoid_point_cloud_padding'), + 'use_color': LaunchConfiguration('use_color'), + 'use_system_default_qos': LaunchConfiguration('use_system_default_qos'), + }], + remappings=[ + ('left/camera_info', [LaunchConfiguration('left_namespace'), '/camera_info']), + ('right/camera_info', [LaunchConfiguration('right_namespace'), '/camera_info']), + ( + 'left/image_rect_color', + [LaunchConfiguration('left_namespace'), '/image_rect_color'] + ), + ] + ), + ] + + return LaunchDescription([ + DeclareLaunchArgument( + name='approximate_sync', default_value='False', + description='Whether to use approximate synchronization of topics. Set to true if ' + 'the left and right cameras do not produce exactly synced timestamps.' + ), + DeclareLaunchArgument( + name='avoid_point_cloud_padding', default_value='False', + description='Avoid alignment padding in the generated point cloud.' + 'This reduces bandwidth requirements, as the point cloud size is halved.' + 'Using point clouds without alignment padding might degrade performance ' + 'for some algorithms.' + ), + DeclareLaunchArgument( + name='use_color', default_value='True', + description='Generate point cloud with rgb data.' + ), + DeclareLaunchArgument( + name='use_system_default_qos', default_value='False', + description='Use the RMW QoS settings for the image and camera info subscriptions.' + ), + DeclareLaunchArgument( + name='launch_image_proc', default_value='True', + description='Whether to launch debayer and rectify nodes from image_proc.' + ), + DeclareLaunchArgument( + name='left_namespace', default_value='left', + description='Namespace for the left camera' + ), + DeclareLaunchArgument( + name='right_namespace', default_value='right', + description='Namespace for the right camera' + ), + DeclareLaunchArgument( + name='container', default_value='', + description=( + 'Name of an existing node container to load launched nodes into. ' + 'If unset, a new container will be created.' + ) + ), + # Stereo algorithm parameters + DeclareLaunchArgument( + name='stereo_algorithm', default_value='0', + description='Stereo algorithm: Block Matching (0) or Semi-Global Block Matching (1)' + ), + DeclareLaunchArgument( + name='prefilter_size', default_value='9', + description='Normalization window size in pixels (must be odd)' + ), + DeclareLaunchArgument( + name='prefilter_cap', default_value='31', + description='Bound on normalized pixel values' + ), + DeclareLaunchArgument( + name='correlation_window_size', default_value='15', + description='SAD correlation window width in pixels (must be odd)' + ), + DeclareLaunchArgument( + name='min_disparity', default_value='0', + description='Disparity to begin search at in pixels' + ), + DeclareLaunchArgument( + name='disparity_range', default_value='64', + description='Number of disparities to search in pixels (must be a multiple of 16)' + ), + DeclareLaunchArgument( + name='texture_threshold', default_value='10', + description='Filter out if SAD window response does not exceed texture threshold' + ), + DeclareLaunchArgument( + name='speckle_size', default_value='100', + description='Reject regions smaller than this size in pixels' + ), + DeclareLaunchArgument( + name='speckle_range', default_value='4', + description='Maximum allowed difference between detected disparities' + ), + DeclareLaunchArgument( + name='disp12_max_diff', default_value='0', + description='Maximum allowed difference in the left-right disparity check in pixels ' + '(Semi-Global Block Matching only)' + ), + DeclareLaunchArgument( + name='uniqueness_ratio', default_value='15.0', + description='Filter out if best match does not sufficiently exceed the next-best match' + ), + DeclareLaunchArgument( + name='P1', default_value='200.0', + description='The first parameter ccontrolling the disparity smoothness ' + '(Semi-Global Block Matching only)' + ), + DeclareLaunchArgument( + name='P2', default_value='400.0', + description='The second parameter ccontrolling the disparity smoothness ' + '(Semi-Global Block Matching only)' + ), + DeclareLaunchArgument( + name='full_dp', default_value='False', + description='Run the full variant of the algorithm (Semi-Global Block Matching only)' + ), + ComposableNodeContainer( + condition=LaunchConfigurationEquals('container', ''), + package='rclcpp_components', + executable='component_container', + name='stereo_image_proc_container', + namespace='', + composable_node_descriptions=composable_nodes, + ), + LoadComposableNodes( + condition=LaunchConfigurationNotEquals('container', ''), + composable_node_descriptions=composable_nodes, + target_container=LaunchConfiguration('container'), + ), + # If a container name is not provided, + # set the name of the container launched above for image_proc nodes + SetLaunchConfiguration( + condition=LaunchConfigurationEquals('container', ''), + name='container', + value=PythonExpression([ + '"stereo_image_proc_container"', ' if ', + '"', LaunchConfiguration('ros_namespace', default=''), '"', + ' == "" else ', '"', + LaunchConfiguration('ros_namespace', default=''), '/stereo_image_proc_container"' + ]), + ), + GroupAction( + [ + PushRosNamespace(LaunchConfiguration('left_namespace')), + IncludeLaunchDescription( + PythonLaunchDescriptionSource([ + FindPackageShare('image_proc'), '/launch/image_proc.launch.py' + ]), + launch_arguments={'container': LaunchConfiguration('container')}.items() + ), + ], + condition=IfCondition(LaunchConfiguration('launch_image_proc')), + ), + GroupAction( + [ + PushRosNamespace(LaunchConfiguration('right_namespace')), + IncludeLaunchDescription( + PythonLaunchDescriptionSource([ + FindPackageShare('image_proc'), '/launch/image_proc.launch.py' + ]), + launch_arguments={'container': LaunchConfiguration('container')}.items() + ), + ], + condition=IfCondition(LaunchConfiguration('launch_image_proc')), + ) + ]) diff --git a/src/image_pipeline/stereo_image_proc/package.xml b/src/image_pipeline/stereo_image_proc/package.xml new file mode 100644 index 000000000..f42d8e45c --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/package.xml @@ -0,0 +1,48 @@ + + + + stereo_image_proc + 4.0.0 + Stereo and single image rectification and disparity processing. + + Vincent Rabaud + Joshua Whitley + Jacob Perron + Michael Ferguson + + BSD + https://index.ros.org/p/stereo_image_proc/github-ros-perception-image_pipeline/ + https://github.com/ros-perception/image_pipeline/issues + https://github.com/ros-perception/image_pipeline + Patrick Mihelich + Kurt Konolige + Jeremy Leibs + + ament_cmake_auto + + cv_bridge + image_geometry + image_proc + image_transport + message_filters + rclcpp + rclcpp_components + sensor_msgs + stereo_msgs + + ament_cmake_pytest + ament_lint_auto + ament_lint_common + launch + launch_ros + launch_testing + launch_testing_ament_cmake + python3-opencv + python_cmake_module + rclpy + ros_testing + + + ament_cmake + + diff --git a/src/image_pipeline/stereo_image_proc/src/stereo_image_proc/disparity_node.cpp b/src/image_pipeline/stereo_image_proc/src/stereo_image_proc/disparity_node.cpp new file mode 100644 index 000000000..d092fcc05 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/src/stereo_image_proc/disparity_node.cpp @@ -0,0 +1,423 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * 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 OWNER 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 +#include +#include +#include + +#include "cv_bridge/cv_bridge.hpp" +#include "image_geometry/stereo_camera_model.hpp" +#include "message_filters/subscriber.h" +#include "message_filters/synchronizer.h" +#include "message_filters/sync_policies/approximate_time.h" +#include "message_filters/sync_policies/approximate_epsilon_time.h" +#include "message_filters/sync_policies/exact_time.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace stereo_image_proc +{ + +class DisparityNode : public rclcpp::Node +{ +public: + explicit DisparityNode(const rclcpp::NodeOptions & options); + +private: + enum StereoAlgorithm + { + BLOCK_MATCHING = 0, + SEMI_GLOBAL_BLOCK_MATCHING + }; + + // Subscriptions + image_transport::SubscriberFilter sub_l_image_, sub_r_image_; + message_filters::Subscriber sub_l_info_, sub_r_info_; + using ExactPolicy = message_filters::sync_policies::ExactTime< + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo, + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo>; + using ApproximatePolicy = message_filters::sync_policies::ApproximateTime< + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo, + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo>; + using ApproximateEpsilonPolicy = message_filters::sync_policies::ApproximateEpsilonTime< + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo, + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo>; + using ExactSync = message_filters::Synchronizer; + using ApproximateSync = message_filters::Synchronizer; + using ApproximateEpsilonSync = message_filters::Synchronizer; + std::shared_ptr exact_sync_; + std::shared_ptr approximate_sync_; + std::shared_ptr approximate_epsilon_sync_; + // Publications + std::shared_ptr> pub_disparity_; + + // Handle to parameters callback + rclcpp::Node::OnSetParametersCallbackHandle::SharedPtr on_set_parameters_callback_handle_; + + // Processing state (note: only safe because we're single-threaded!) + image_geometry::StereoCameraModel model_; + // contains scratch buffers for block matching + stereo_image_proc::StereoProcessor block_matcher_; + + void connectCb(); + + void imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr & l_image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & l_info_msg, + const sensor_msgs::msg::Image::ConstSharedPtr & r_image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & r_info_msg); + + rcl_interfaces::msg::SetParametersResult parameterSetCb( + const std::vector & parameters); +}; + +// Some helper functions for adding a parameter to a collection +static void add_param_to_map( + std::map> & parameters, + const std::string & name, + const std::string & description, + const int default_value, + const int from_value, + const int to_value, + const int step) +{ + rcl_interfaces::msg::IntegerRange integer_range; + integer_range.from_value = from_value; + integer_range.to_value = to_value; + integer_range.step = step; + rcl_interfaces::msg::ParameterDescriptor descriptor; + descriptor.description = description; + descriptor.integer_range = {integer_range}; + parameters[name] = std::make_pair(default_value, descriptor); +} + +static void add_param_to_map( + std::map> & parameters, + const std::string & name, + const std::string & description, + const double default_value, + const double from_value, + const double to_value, + const double step) +{ + rcl_interfaces::msg::FloatingPointRange floating_point_range; + floating_point_range.from_value = from_value; + floating_point_range.to_value = to_value; + floating_point_range.step = step; + rcl_interfaces::msg::ParameterDescriptor descriptor; + descriptor.description = description; + descriptor.floating_point_range = {floating_point_range}; + parameters[name] = std::make_pair(default_value, descriptor); +} + +DisparityNode::DisparityNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("disparity_node", options) +{ + using namespace std::placeholders; + + // Declare/read parameters + int queue_size = this->declare_parameter("queue_size", 5); + bool approx = this->declare_parameter("approximate_sync", false); + double approx_sync_epsilon = this->declare_parameter("approximate_sync_tolerance_seconds", 0.0); + this->declare_parameter("use_system_default_qos", false); + + // Synchronize callbacks + if (approx) { + if (0.0 == approx_sync_epsilon) { + approximate_sync_.reset( + new ApproximateSync( + ApproximatePolicy(queue_size), + sub_l_image_, sub_l_info_, + sub_r_image_, sub_r_info_)); + approximate_sync_->registerCallback( + std::bind(&DisparityNode::imageCb, this, _1, _2, _3, _4)); + } else { + approximate_epsilon_sync_.reset( + new ApproximateEpsilonSync( + ApproximateEpsilonPolicy( + queue_size, rclcpp::Duration::from_seconds(approx_sync_epsilon)), + sub_l_image_, sub_l_info_, + sub_r_image_, sub_r_info_)); + approximate_epsilon_sync_->registerCallback( + std::bind(&DisparityNode::imageCb, this, _1, _2, _3, _4)); + } + } else { + exact_sync_.reset( + new ExactSync( + ExactPolicy(queue_size), + sub_l_image_, sub_l_info_, + sub_r_image_, sub_r_info_)); + exact_sync_->registerCallback( + std::bind(&DisparityNode::imageCb, this, _1, _2, _3, _4)); + } + + // Register a callback for when parameters are set + on_set_parameters_callback_handle_ = this->add_on_set_parameters_callback( + std::bind(&DisparityNode::parameterSetCb, this, _1)); + + // Describe int parameters + std::map> int_params; + add_param_to_map( + int_params, + "stereo_algorithm", + "Stereo algorithm: Block Matching (0) or Semi-Global Block Matching (1)", + 0, 0, 1, 1); // default, from, to, step + add_param_to_map( + int_params, + "prefilter_size", + "Normalization window size in pixels (must be odd)", + 9, 5, 255, 2); + add_param_to_map( + int_params, + "prefilter_cap", + "Bound on normalized pixel values", + 31, 1, 63, 1); + add_param_to_map( + int_params, + "correlation_window_size", + "SAD correlation window width in pixels (must be odd)", + 15, 5, 255, 2); + add_param_to_map( + int_params, + "min_disparity", + "Disparity to begin search at in pixels", + 0, -2048, 2048, 1); + add_param_to_map( + int_params, + "disparity_range", + "Number of disparities to search in pixels (must be a multiple of 16)", + 64, 32, 4096, 16); + add_param_to_map( + int_params, + "texture_threshold", + "Filter out if SAD window response does not exceed texture threshold", + 10, 0, 10000, 1); + add_param_to_map( + int_params, + "speckle_size", + "Reject regions smaller than this size in pixels", + 100, 0, 1000, 1); + add_param_to_map( + int_params, + "speckle_range", + "Maximum allowed difference between detected disparities", + 4, 0, 31, 1); + add_param_to_map( + int_params, + "disp12_max_diff", + "Maximum allowed difference in the left-right disparity check in pixels" + " (Semi-Global Block Matching only)", + 0, 0, 128, 1); + + // Describe double parameters + std::map> double_params; + add_param_to_map( + double_params, + "uniqueness_ratio", + "Filter out if best match does not sufficiently exceed the next-best match", + 15.0, 0.0, 100.0, 0.0); + add_param_to_map( + double_params, + "P1", + "The first parameter ccontrolling the disparity smoothness (Semi-Global Block Matching only)", + 200.0, 0.0, 4000.0, 0.0); + add_param_to_map( + double_params, + "P2", + "The second parameter ccontrolling the disparity smoothness (Semi-Global Block Matching only)", + 400.0, 0.0, 4000.0, 0.0); + + // Describe bool parameters + std::map> bool_params; + rcl_interfaces::msg::ParameterDescriptor full_dp_descriptor; + full_dp_descriptor.description = + "Run the full variant of the algorithm (Semi-Global Block Matching only)"; + bool_params["full_dp"] = std::make_pair(false, full_dp_descriptor); + + // Declaring parameters triggers the previously registered callback + this->declare_parameters("", int_params); + this->declare_parameters("", double_params); + this->declare_parameters("", bool_params); + + // Update the publisher options to allow reconfigurable qos settings. + rclcpp::PublisherOptions pub_opts; + pub_opts.qos_overriding_options = rclcpp::QosOverridingOptions::with_default_policies(); + pub_disparity_ = create_publisher("disparity", 1, pub_opts); + + // TODO(jacobperron): Replace this with a graph event. + // Only subscribe if there's a subscription listening to our publisher. + connectCb(); +} + +// Handles (un)subscribing when clients (un)subscribe +void DisparityNode::connectCb() +{ + // TODO(jacobperron): Add unsubscribe logic when we use graph events + image_transport::TransportHints hints(this, "raw"); + const bool use_system_default_qos = this->get_parameter("use_system_default_qos").as_bool(); + rclcpp::QoS image_sub_qos = rclcpp::SensorDataQoS(); + if (use_system_default_qos) { + image_sub_qos = rclcpp::SystemDefaultsQoS(); + } + const auto image_sub_rmw_qos = image_sub_qos.get_rmw_qos_profile(); + auto sub_opts = rclcpp::SubscriptionOptions(); + sub_opts.qos_overriding_options = rclcpp::QosOverridingOptions::with_default_policies(); + sub_l_image_.subscribe( + this, "left/image_rect", hints.getTransport(), image_sub_rmw_qos, sub_opts); + sub_l_info_.subscribe(this, "left/camera_info", image_sub_rmw_qos, sub_opts); + sub_r_image_.subscribe( + this, "right/image_rect", hints.getTransport(), image_sub_rmw_qos, sub_opts); + sub_r_info_.subscribe(this, "right/camera_info", image_sub_rmw_qos, sub_opts); +} + +void DisparityNode::imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr & l_image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & l_info_msg, + const sensor_msgs::msg::Image::ConstSharedPtr & r_image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & r_info_msg) +{ + // If there are no subscriptions for the disparity image, do nothing + if (pub_disparity_->get_subscription_count() == 0u) { + return; + } + + // Update the camera model + model_.fromCameraInfo(l_info_msg, r_info_msg); + + // Allocate new disparity image message + auto disp_msg = std::make_shared(); + disp_msg->header = l_info_msg->header; + disp_msg->image.header = l_info_msg->header; + + // Compute window of (potentially) valid disparities + int border = block_matcher_.getCorrelationWindowSize() / 2; + int left = block_matcher_.getDisparityRange() + block_matcher_.getMinDisparity() + border - 1; + int wtf; + if (block_matcher_.getMinDisparity() >= 0) { + wtf = border + block_matcher_.getMinDisparity(); + } else { + wtf = std::max(border, -block_matcher_.getMinDisparity()); + } + // TODO(jacobperron): the message width has not been set yet! What should we do here? + int right = disp_msg->image.width - 1 - wtf; + int top = border; + int bottom = disp_msg->image.height - 1 - border; + disp_msg->valid_window.x_offset = left; + disp_msg->valid_window.y_offset = top; + disp_msg->valid_window.width = right - left; + disp_msg->valid_window.height = bottom - top; + + // Create cv::Mat views onto all buffers + const cv::Mat_ l_image = + cv_bridge::toCvShare(l_image_msg, sensor_msgs::image_encodings::MONO8)->image; + const cv::Mat_ r_image = + cv_bridge::toCvShare(r_image_msg, sensor_msgs::image_encodings::MONO8)->image; + + // Perform block matching to find the disparities + block_matcher_.processDisparity(l_image, r_image, model_, *disp_msg); + + pub_disparity_->publish(*disp_msg); +} + +rcl_interfaces::msg::SetParametersResult DisparityNode::parameterSetCb( + const std::vector & parameters) +{ + rcl_interfaces::msg::SetParametersResult result; + result.successful = true; + for (const auto & param : parameters) { + const std::string param_name = param.get_name(); + if ("stereo_algorithm" == param_name) { + const int stereo_algorithm_value = param.as_int(); + if (BLOCK_MATCHING == stereo_algorithm_value) { + block_matcher_.setStereoType(StereoProcessor::BM); + } else if (SEMI_GLOBAL_BLOCK_MATCHING == stereo_algorithm_value) { + block_matcher_.setStereoType(StereoProcessor::SGBM); + } else { + result.successful = false; + std::ostringstream oss; + oss << "Unknown stereo algorithm type '" << stereo_algorithm_value << "'"; + result.reason = oss.str(); + } + } else if ("prefilter_size" == param_name) { + block_matcher_.setPreFilterSize(param.as_int()); + } else if ("prefilter_cap" == param_name) { + block_matcher_.setPreFilterCap(param.as_int()); + } else if ("correlation_window_size" == param_name) { + block_matcher_.setCorrelationWindowSize(param.as_int()); + } else if ("min_disparity" == param_name) { + block_matcher_.setMinDisparity(param.as_int()); + } else if ("disparity_range" == param_name) { + block_matcher_.setDisparityRange(param.as_int()); + } else if ("uniqueness_ratio" == param_name) { + block_matcher_.setUniquenessRatio(param.as_double()); + } else if ("texture_threshold" == param_name) { + block_matcher_.setTextureThreshold(param.as_int()); + } else if ("speckle_size" == param_name) { + block_matcher_.setSpeckleSize(param.as_int()); + } else if ("speckle_range" == param_name) { + block_matcher_.setSpeckleRange(param.as_int()); + } else if ("full_dp" == param_name) { + block_matcher_.setSgbmMode(param.as_bool()); + } else if ("P1" == param_name) { + block_matcher_.setP1(param.as_double()); + } else if ("P2" == param_name) { + block_matcher_.setP2(param.as_double()); + } else if ("disp12_max_diff" == param_name) { + block_matcher_.setDisp12MaxDiff(param.as_int()); + } + } + return result; +} + +} // namespace stereo_image_proc + +// Register component +RCLCPP_COMPONENTS_REGISTER_NODE(stereo_image_proc::DisparityNode) diff --git a/src/image_pipeline/stereo_image_proc/src/stereo_image_proc/point_cloud_node.cpp b/src/image_pipeline/stereo_image_proc/src/stereo_image_proc/point_cloud_node.cpp new file mode 100644 index 000000000..ddbf752a0 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/src/stereo_image_proc/point_cloud_node.cpp @@ -0,0 +1,340 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * 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 OWNER 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 "image_geometry/stereo_camera_model.hpp" +#include "message_filters/subscriber.h" +#include "message_filters/synchronizer.h" +#include "message_filters/sync_policies/approximate_time.h" +#include "message_filters/sync_policies/approximate_epsilon_time.h" +#include "message_filters/sync_policies/exact_time.h" +#include "rcutils/logging_macros.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace stereo_image_proc +{ + +class PointCloudNode : public rclcpp::Node +{ +public: + explicit PointCloudNode(const rclcpp::NodeOptions & options); + +private: + // Subscriptions + image_transport::SubscriberFilter sub_l_image_; + message_filters::Subscriber sub_l_info_, sub_r_info_; + message_filters::Subscriber sub_disparity_; + using ExactPolicy = message_filters::sync_policies::ExactTime< + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo, + sensor_msgs::msg::CameraInfo, + stereo_msgs::msg::DisparityImage>; + using ApproximatePolicy = message_filters::sync_policies::ApproximateTime< + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo, + sensor_msgs::msg::CameraInfo, + stereo_msgs::msg::DisparityImage>; + using ApproximateEpsilonPolicy = message_filters::sync_policies::ApproximateEpsilonTime< + sensor_msgs::msg::Image, + sensor_msgs::msg::CameraInfo, + sensor_msgs::msg::CameraInfo, + stereo_msgs::msg::DisparityImage>; + using ExactSync = message_filters::Synchronizer; + using ApproximateSync = message_filters::Synchronizer; + using ApproximateEpsilonSync = message_filters::Synchronizer; + std::shared_ptr exact_sync_; + std::shared_ptr approximate_sync_; + std::shared_ptr approximate_epsilon_sync_; + + // Publications + std::shared_ptr> pub_points2_; + + // Processing state (note: only safe because we're single-threaded!) + image_geometry::StereoCameraModel model_; + cv::Mat_ points_mat_; // scratch buffer + + void connectCb(); + + void imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr & l_image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & l_info_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & r_info_msg, + const stereo_msgs::msg::DisparityImage::ConstSharedPtr & disp_msg); +}; + +PointCloudNode::PointCloudNode(const rclcpp::NodeOptions & options) +: rclcpp::Node("point_cloud_node", options) +{ + using namespace std::placeholders; + + // Declare/read parameters + int queue_size = this->declare_parameter("queue_size", 5); + bool approx = this->declare_parameter("approximate_sync", false); + double approx_sync_epsilon = this->declare_parameter("approximate_sync_tolerance_seconds", 0.0); + this->declare_parameter("use_system_default_qos", false); + rcl_interfaces::msg::ParameterDescriptor descriptor; + // TODO(ivanpauno): Confirm if using point cloud padding in `sensor_msgs::msg::PointCloud2` + // can improve performance in some cases or not. + descriptor.description = + "This parameter avoids using alignment padding in the generated point cloud." + "This reduces bandwidth requirements, as the point cloud size is halved." + "Using point clouds without alignment padding might degrade performance for some algorithms."; + this->declare_parameter("avoid_point_cloud_padding", false, descriptor); + this->declare_parameter("use_color", true); + + // Synchronize callbacks + if (approx) { + if (0.0 == approx_sync_epsilon) { + approximate_sync_.reset( + new ApproximateSync( + ApproximatePolicy(queue_size), + sub_l_image_, sub_l_info_, + sub_r_info_, sub_disparity_)); + approximate_sync_->registerCallback( + std::bind(&PointCloudNode::imageCb, this, _1, _2, _3, _4)); + } else { + approximate_epsilon_sync_.reset( + new ApproximateEpsilonSync( + ApproximateEpsilonPolicy( + queue_size, rclcpp::Duration::from_seconds(approx_sync_epsilon)), + sub_l_image_, sub_l_info_, + sub_r_info_, sub_disparity_)); + approximate_epsilon_sync_->registerCallback( + std::bind(&PointCloudNode::imageCb, this, _1, _2, _3, _4)); + } + } else { + exact_sync_.reset( + new ExactSync( + ExactPolicy(queue_size), + sub_l_image_, sub_l_info_, + sub_r_info_, sub_disparity_)); + exact_sync_->registerCallback( + std::bind(&PointCloudNode::imageCb, this, _1, _2, _3, _4)); + } + + // Update the publisher options to allow reconfigurable qos settings. + rclcpp::PublisherOptions pub_opts; + pub_opts.qos_overriding_options = rclcpp::QosOverridingOptions::with_default_policies(); + pub_points2_ = create_publisher("points2", 1, pub_opts); + + // TODO(jacobperron): Replace this with a graph event. + // Only subscribe if there's a subscription listening to our publisher. + connectCb(); +} + +// Handles (un)subscribing when clients (un)subscribe +void PointCloudNode::connectCb() +{ + // TODO(jacobperron): Add unsubscribe logic when we use graph events + image_transport::TransportHints hints(this, "raw"); + const bool use_system_default_qos = this->get_parameter("use_system_default_qos").as_bool(); + rclcpp::QoS image_sub_qos = rclcpp::SensorDataQoS(); + if (use_system_default_qos) { + image_sub_qos = rclcpp::SystemDefaultsQoS(); + } + const auto image_sub_rmw_qos = image_sub_qos.get_rmw_qos_profile(); + auto sub_opts = rclcpp::SubscriptionOptions(); + sub_opts.qos_overriding_options = rclcpp::QosOverridingOptions::with_default_policies(); + sub_l_image_.subscribe( + this, "left/image_rect_color", hints.getTransport(), image_sub_rmw_qos, sub_opts); + sub_l_info_.subscribe(this, "left/camera_info", image_sub_rmw_qos, sub_opts); + sub_r_info_.subscribe(this, "right/camera_info", image_sub_rmw_qos, sub_opts); + sub_disparity_.subscribe(this, "disparity", image_sub_rmw_qos, sub_opts); +} + +inline bool isValidPoint(const cv::Vec3f & pt) +{ + // Check both for disparities explicitly marked as invalid (where OpenCV maps pt.z to MISSING_Z) + // and zero disparities (point mapped to infinity). + return pt[2] != image_geometry::StereoCameraModel::MISSING_Z && !std::isinf(pt[2]); +} + +void PointCloudNode::imageCb( + const sensor_msgs::msg::Image::ConstSharedPtr & l_image_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & l_info_msg, + const sensor_msgs::msg::CameraInfo::ConstSharedPtr & r_info_msg, + const stereo_msgs::msg::DisparityImage::ConstSharedPtr & disp_msg) +{ + // If there are no subscriptions for the point cloud, do nothing + if (pub_points2_->get_subscription_count() == 0u) { + return; + } + + // Update the camera model + model_.fromCameraInfo(l_info_msg, r_info_msg); + + // Calculate point cloud + const sensor_msgs::msg::Image & dimage = disp_msg->image; + // The cv::Mat_ constructor doesn't accept a const data data pointer + // so we remove the constness before reinterpreting into float. + // This is "safe" since our cv::Mat is const. + float * data = reinterpret_cast(const_cast(&dimage.data[0])); + + const cv::Mat_ dmat(dimage.height, dimage.width, data, dimage.step); + model_.projectDisparityImageTo3d(dmat, points_mat_, true); + cv::Mat_ mat = points_mat_; + + // Fill in new PointCloud2 message (2D image-like layout) + auto points_msg = std::make_shared(); + points_msg->header = disp_msg->header; + points_msg->height = mat.rows; + points_msg->width = mat.cols; + points_msg->is_bigendian = false; + points_msg->is_dense = false; // there may be invalid points + + sensor_msgs::PointCloud2Modifier pcd_modifier(*points_msg); + + if (!this->get_parameter("avoid_point_cloud_padding").as_bool()) { + if (this->get_parameter("use_color").as_bool()) { + // Data will be packed as (DC=don't care, each item is a float): + // x, y, z, DC, rgb, DC, DC, DC + // Resulting step size: 32 bytes + pcd_modifier.setPointCloud2FieldsByString(2, "xyz", "rgb"); + } else { + // Data will be packed as: + // x, y, z, DC + // Resulting step size: 16 bytes + pcd_modifier.setPointCloud2FieldsByString(1, "xyz"); + } + } else { + if (this->get_parameter("use_color").as_bool()) { + // Data will be packed as: + // x, y, z, rgb + // Resulting step size: 16 bytes + pcd_modifier.setPointCloud2Fields( + 4, + "x", 1, sensor_msgs::msg::PointField::FLOAT32, + "y", 1, sensor_msgs::msg::PointField::FLOAT32, + "z", 1, sensor_msgs::msg::PointField::FLOAT32, + "rgb", 1, sensor_msgs::msg::PointField::FLOAT32); + } else { + // Data will be packed as: + // x, y, z + // Resulting step size: 12 bytes + pcd_modifier.setPointCloud2Fields( + 3, + "x", 1, sensor_msgs::msg::PointField::FLOAT32, + "y", 1, sensor_msgs::msg::PointField::FLOAT32, + "z", 1, sensor_msgs::msg::PointField::FLOAT32); + } + } + + sensor_msgs::PointCloud2Iterator iter_x(*points_msg, "x"); + sensor_msgs::PointCloud2Iterator iter_y(*points_msg, "y"); + sensor_msgs::PointCloud2Iterator iter_z(*points_msg, "z"); + + float bad_point = std::numeric_limits::quiet_NaN(); + for (int v = 0; v < mat.rows; ++v) { + for (int u = 0; u < mat.cols; ++u, ++iter_x, ++iter_y, ++iter_z) { + if (isValidPoint(mat(v, u))) { + // x,y,z + *iter_x = mat(v, u)[0]; + *iter_y = mat(v, u)[1]; + *iter_z = mat(v, u)[2]; + } else { + *iter_x = *iter_y = *iter_z = bad_point; + } + } + } + + if (this->get_parameter("use_color").as_bool()) { + sensor_msgs::PointCloud2Iterator iter_r(*points_msg, "r"); + sensor_msgs::PointCloud2Iterator iter_g(*points_msg, "g"); + sensor_msgs::PointCloud2Iterator iter_b(*points_msg, "b"); + + // Fill in color + namespace enc = sensor_msgs::image_encodings; + const std::string & encoding = l_image_msg->encoding; + if (encoding == enc::MONO8) { + const cv::Mat_ color( + l_image_msg->height, l_image_msg->width, + const_cast(&l_image_msg->data[0]), + l_image_msg->step); + for (int v = 0; v < mat.rows; ++v) { + for (int u = 0; u < mat.cols; ++u, ++iter_r, ++iter_g, ++iter_b) { + uint8_t g = color(v, u); + *iter_r = *iter_g = *iter_b = g; + } + } + } else if (encoding == enc::RGB8) { + const cv::Mat_ color( + l_image_msg->height, l_image_msg->width, + (cv::Vec3b *)(&l_image_msg->data[0]), + l_image_msg->step); + for (int v = 0; v < mat.rows; ++v) { + for (int u = 0; u < mat.cols; ++u, ++iter_r, ++iter_g, ++iter_b) { + const cv::Vec3b & rgb = color(v, u); + *iter_r = rgb[0]; + *iter_g = rgb[1]; + *iter_b = rgb[2]; + } + } + } else if (encoding == enc::BGR8) { + const cv::Mat_ color( + l_image_msg->height, l_image_msg->width, + (cv::Vec3b *)(&l_image_msg->data[0]), + l_image_msg->step); + for (int v = 0; v < mat.rows; ++v) { + for (int u = 0; u < mat.cols; ++u, ++iter_r, ++iter_g, ++iter_b) { + const cv::Vec3b & bgr = color(v, u); + *iter_r = bgr[2]; + *iter_g = bgr[1]; + *iter_b = bgr[0]; + } + } + } else { + // Throttle duration in milliseconds + RCUTILS_LOG_WARN_THROTTLE( + RCUTILS_STEADY_TIME, 30000, + "Could not fill color channel of the point cloud, " + "unsupported encoding '%s'", encoding.c_str()); + } + } + + pub_points2_->publish(*points_msg); +} + +} // namespace stereo_image_proc + +// Register node +RCLCPP_COMPONENTS_REGISTER_NODE(stereo_image_proc::PointCloudNode) diff --git a/src/image_pipeline/stereo_image_proc/src/stereo_image_proc/stereo_processor.cpp b/src/image_pipeline/stereo_image_proc/src/stereo_image_proc/stereo_processor.cpp new file mode 100644 index 000000000..1c02d96a7 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/src/stereo_image_proc/stereo_processor.cpp @@ -0,0 +1,383 @@ +// Copyright (c) 2008, Willow Garage, Inc. +// All rights reserved. +// +// Software License Agreement (BSD License 2.0) +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * 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. +// * 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 OWNER 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 "rcutils/logging_macros.h" + +#include "stereo_image_proc/stereo_processor.hpp" + +#include + +// TODO(jacobperron): Remove this after it's implemented upstream +// https://github.com/ros2/rcutils/pull/112 +#ifndef RCUTILS_ASSERT +#define RCUTILS_ASSERT assert +#endif +// Uncomment below instead +// #include + +namespace stereo_image_proc +{ + +bool StereoProcessor::process( + const sensor_msgs::msg::Image::ConstSharedPtr & left_raw, + const sensor_msgs::msg::Image::ConstSharedPtr & right_raw, + const image_geometry::StereoCameraModel & model, + StereoImageSet & output, + int flags) const +{ + // Do monocular processing on left and right images + int left_flags = flags & LEFT_ALL; + int right_flags = flags & RIGHT_ALL; + if (flags & STEREO_ALL) { + // Need the rectified images for stereo processing + left_flags |= LEFT_RECT; + right_flags |= RIGHT_RECT; + } + if (flags & (POINT_CLOUD | POINT_CLOUD2)) { + flags |= DISPARITY; + // Need the color channels for the point cloud + left_flags |= LEFT_RECT_COLOR; + } + if (!mono_processor_.process(left_raw, model.left(), output.left, left_flags)) { + return false; + } + if (!mono_processor_.process(right_raw, model.right(), output.right, right_flags >> 4)) { + return false; + } + + // Do block matching to produce the disparity image + if (flags & DISPARITY) { + processDisparity(output.left.rect, output.right.rect, model, output.disparity); + } + + // Project disparity image to 3d point cloud + if (flags & POINT_CLOUD) { + processPoints( + output.disparity, output.left.rect_color, output.left.color_encoding, model, output.points); + } + + // Project disparity image to 3d point cloud + if (flags & POINT_CLOUD2) { + processPoints2( + output.disparity, output.left.rect_color, output.left.color_encoding, model, output.points2); + } + + return true; +} + +void StereoProcessor::processDisparity( + const cv::Mat & left_rect, + const cv::Mat & right_rect, + const image_geometry::StereoCameraModel & model, + stereo_msgs::msg::DisparityImage & disparity) const +{ + // Fixed-point disparity is 16 times the true value: d = d_fp / 16.0 = x_l - x_r. + static const int DPP = 16; // disparities per pixel + static const double inv_dpp = 1.0 / DPP; + + // Block matcher produces 16-bit signed (fixed point) disparity image + if (current_stereo_algorithm_ == BM) { + block_matcher_->compute(left_rect, right_rect, disparity16_); + } else { + sg_block_matcher_->compute(left_rect, right_rect, disparity16_); + } + + // Fill in DisparityImage image data, converting to 32-bit float + sensor_msgs::msg::Image & dimage = disparity.image; + dimage.height = disparity16_.rows; + dimage.width = disparity16_.cols; + dimage.encoding = sensor_msgs::image_encodings::TYPE_32FC1; + dimage.step = dimage.width * sizeof(float); + dimage.data.resize(dimage.step * dimage.height); + cv::Mat_ dmat( + dimage.height, dimage.width, reinterpret_cast(&dimage.data[0]), dimage.step); + // We convert from fixed-point to float disparity and also adjust for any x-offset between + // the principal points: d = d_fp*inv_dpp - (cx_l - cx_r) + disparity16_.convertTo(dmat, dmat.type(), inv_dpp, -(model.left().cx() - model.right().cx())); + RCUTILS_ASSERT(dmat.data == &dimage.data[0]); + // TODO(unknown): is_bigendian? + + // Stereo parameters + disparity.f = model.right().fx(); + disparity.t = model.baseline(); + + // TODO(unknown): Window of (potentially) valid disparities + + // Disparity search range + disparity.min_disparity = getMinDisparity(); + disparity.max_disparity = getMinDisparity() + getDisparityRange() - 1; + disparity.delta_d = inv_dpp; +} + +inline bool isValidPoint(const cv::Vec3f & pt) +{ + // Check both for disparities explicitly marked as invalid (where OpenCV maps pt.z to MISSING_Z) + // and zero disparities (point mapped to infinity). + return pt[2] != image_geometry::StereoCameraModel::MISSING_Z && !std::isinf(pt[2]); +} + +void StereoProcessor::processPoints( + const stereo_msgs::msg::DisparityImage & disparity, + const cv::Mat & color, + const std::string & encoding, + const image_geometry::StereoCameraModel & model, + sensor_msgs::msg::PointCloud & points) const +{ + // Calculate dense point cloud + const sensor_msgs::msg::Image & dimage = disparity.image; + // The cv::Mat_ constructor doesn't accept a const data data pointer + // so we remove the constness before reinterpreting into float. + // This is "safe" since our cv::Mat is const. + float * data = reinterpret_cast(const_cast(&dimage.data[0])); + const cv::Mat_ dmat(dimage.height, dimage.width, data, dimage.step); + model.projectDisparityImageTo3d(dmat, dense_points_, true); + + // Fill in sparse point cloud message + points.points.resize(0); + points.channels.resize(3); + points.channels[0].name = "rgb"; + points.channels[0].values.resize(0); + points.channels[1].name = "u"; + points.channels[1].values.resize(0); + points.channels[2].name = "v"; + points.channels[2].values.resize(0); + + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v) { + if (isValidPoint(dense_points_(u, v))) { + // x,y,z + geometry_msgs::msg::Point32 pt; + pt.x = dense_points_(u, v)[0]; + pt.y = dense_points_(u, v)[1]; + pt.z = dense_points_(u, v)[2]; + points.points.push_back(pt); + // u,v + points.channels[1].values.push_back(u); + points.channels[2].values.push_back(v); + } + } + } + + // Fill in color + namespace enc = sensor_msgs::image_encodings; + points.channels[0].values.reserve(points.points.size()); + if (encoding == enc::MONO8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v) { + if (isValidPoint(dense_points_(u, v))) { + uint8_t g = color.at(u, v); + int32_t rgb = (g << 16) | (g << 8) | g; + points.channels[0].values.push_back(*reinterpret_cast(&rgb)); + } + } + } + } else if (encoding == enc::RGB8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v) { + if (isValidPoint(dense_points_(u, v))) { + const cv::Vec3b & rgb = color.at(u, v); + int32_t rgb_packed = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + points.channels[0].values.push_back(*reinterpret_cast(&rgb_packed)); + } + } + } + } else if (encoding == enc::RGBA8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v) { + if (isValidPoint(dense_points_(u, v))) { + const cv::Vec4b & rgb = color.at(u, v); + int32_t rgb_packed = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + points.channels[0].values.push_back(*reinterpret_cast(&rgb_packed)); + } + } + } + } else if (encoding == enc::BGRA8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v) { + if (isValidPoint(dense_points_(u, v))) { + const cv::Vec4b & bgr = color.at(u, v); + int32_t rgb_packed = (bgr[2] << 16) | (bgr[1] << 8) | bgr[0]; + points.channels[0].values.push_back(*reinterpret_cast(&rgb_packed)); + } + } + } + } else if (encoding == enc::BGR8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v) { + if (isValidPoint(dense_points_(u, v))) { + const cv::Vec3b & bgr = color.at(u, v); + int32_t rgb_packed = (bgr[2] << 16) | (bgr[1] << 8) | bgr[0]; + points.channels[0].values.push_back(*reinterpret_cast(&rgb_packed)); + } + } + } + } else { + RCUTILS_LOG_WARN( + "Could not fill color channel of the point cloud, unrecognized encoding '%s'", + encoding.c_str()); + } +} + +void StereoProcessor::processPoints2( + const stereo_msgs::msg::DisparityImage & disparity, + const cv::Mat & color, + const std::string & encoding, + const image_geometry::StereoCameraModel & model, + sensor_msgs::msg::PointCloud2 & points) const +{ + // Calculate dense point cloud + const sensor_msgs::msg::Image & dimage = disparity.image; + // The cv::Mat_ constructor doesn't accept a const data data pointer + // so we remove the constness before reinterpreting into float. + // This is "safe" since our cv::Mat is const. + float * data = reinterpret_cast(const_cast(&dimage.data[0])); + const cv::Mat_ dmat(dimage.height, dimage.width, data, dimage.step); + model.projectDisparityImageTo3d(dmat, dense_points_, true); + + // Fill in sparse point cloud message + points.height = dense_points_.rows; + points.width = dense_points_.cols; + points.fields.resize(4); + points.fields[0].name = "x"; + points.fields[0].offset = 0; + points.fields[0].count = 1; + points.fields[0].datatype = sensor_msgs::msg::PointField::FLOAT32; + points.fields[1].name = "y"; + points.fields[1].offset = 4; + points.fields[1].count = 1; + points.fields[1].datatype = sensor_msgs::msg::PointField::FLOAT32; + points.fields[2].name = "z"; + points.fields[2].offset = 8; + points.fields[2].count = 1; + points.fields[2].datatype = sensor_msgs::msg::PointField::FLOAT32; + points.fields[3].name = "rgb"; + points.fields[3].offset = 12; + points.fields[3].count = 1; + points.fields[3].datatype = sensor_msgs::msg::PointField::FLOAT32; + // points.is_bigendian = false; ??? + points.point_step = 16; + points.row_step = points.point_step * points.width; + points.data.resize(points.row_step * points.height); + points.is_dense = false; // there may be invalid points + + float bad_point = std::numeric_limits::quiet_NaN(); + int i = 0; + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v, ++i) { + if (isValidPoint(dense_points_(u, v))) { + // x,y,z,rgba + memcpy(&points.data[i * points.point_step + 0], &dense_points_(u, v)[0], sizeof(float)); + memcpy(&points.data[i * points.point_step + 4], &dense_points_(u, v)[1], sizeof(float)); + memcpy(&points.data[i * points.point_step + 8], &dense_points_(u, v)[2], sizeof(float)); + } else { + memcpy(&points.data[i * points.point_step + 0], &bad_point, sizeof(float)); + memcpy(&points.data[i * points.point_step + 4], &bad_point, sizeof(float)); + memcpy(&points.data[i * points.point_step + 8], &bad_point, sizeof(float)); + } + } + } + + // Fill in color + namespace enc = sensor_msgs::image_encodings; + i = 0; + if (encoding == enc::MONO8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v, ++i) { + if (isValidPoint(dense_points_(u, v))) { + uint8_t g = color.at(u, v); + int32_t rgb = (g << 16) | (g << 8) | g; + memcpy(&points.data[i * points.point_step + 12], &rgb, sizeof(int32_t)); + } else { + memcpy(&points.data[i * points.point_step + 12], &bad_point, sizeof(float)); + } + } + } + } else if (encoding == enc::RGB8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v, ++i) { + if (isValidPoint(dense_points_(u, v))) { + const cv::Vec3b & rgb = color.at(u, v); + int32_t rgb_packed = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + memcpy(&points.data[i * points.point_step + 12], &rgb_packed, sizeof(int32_t)); + } else { + memcpy(&points.data[i * points.point_step + 12], &bad_point, sizeof(float)); + } + } + } + } else if (encoding == enc::RGBA8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v, ++i) { + if (isValidPoint(dense_points_(u, v))) { + const cv::Vec4b & rgb = color.at(u, v); + int32_t rgb_packed = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + memcpy(&points.data[i * points.point_step + 12], &rgb_packed, sizeof(int32_t)); + } else { + memcpy(&points.data[i * points.point_step + 12], &bad_point, sizeof(float)); + } + } + } + } else if (encoding == enc::BGR8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v, ++i) { + if (isValidPoint(dense_points_(u, v))) { + const cv::Vec3b & bgr = color.at(u, v); + int32_t rgb_packed = (bgr[2] << 16) | (bgr[1] << 8) | bgr[0]; + memcpy(&points.data[i * points.point_step + 12], &rgb_packed, sizeof(int32_t)); + } else { + memcpy(&points.data[i * points.point_step + 12], &bad_point, sizeof(float)); + } + } + } + } else if (encoding == enc::BGRA8) { + for (int32_t u = 0; u < dense_points_.rows; ++u) { + for (int32_t v = 0; v < dense_points_.cols; ++v, ++i) { + if (isValidPoint(dense_points_(u, v))) { + const cv::Vec4b & bgr = color.at(u, v); + int32_t rgb_packed = (bgr[2] << 16) | (bgr[1] << 8) | bgr[0]; + memcpy(&points.data[i * points.point_step + 12], &rgb_packed, sizeof(int32_t)); + } else { + memcpy(&points.data[i * points.point_step + 12], &bad_point, sizeof(float)); + } + } + } + } else { + RCUTILS_LOG_WARN( + "Could not fill color channel of the point cloud, unrecognized encoding '%s'", + encoding.c_str()); + } +} + +} // namespace stereo_image_proc diff --git a/src/image_pipeline/stereo_image_proc/test/data/README.md b/src/image_pipeline/stereo_image_proc/test/data/README.md new file mode 100644 index 000000000..83dc00ffa --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/test/data/README.md @@ -0,0 +1,2 @@ +The images in this directory were copied from the OpenCV project [from this location](https://github.com/opencv/opencv_extra/tree/dc0c6c1ee2cd142f936e7f957bbc595df2ce17e8/testdata/gpu/stereobm). +Images are licensed under the open-source BSD license: https://opencv.org/license/ diff --git a/src/image_pipeline/stereo_image_proc/test/data/aloe-L.png b/src/image_pipeline/stereo_image_proc/test/data/aloe-L.png new file mode 100644 index 000000000..47587668e Binary files /dev/null and b/src/image_pipeline/stereo_image_proc/test/data/aloe-L.png differ diff --git a/src/image_pipeline/stereo_image_proc/test/data/aloe-R.png b/src/image_pipeline/stereo_image_proc/test/data/aloe-R.png new file mode 100644 index 000000000..5d11c57a9 Binary files /dev/null and b/src/image_pipeline/stereo_image_proc/test/data/aloe-R.png differ diff --git a/src/image_pipeline/stereo_image_proc/test/data/aloe-disp.png b/src/image_pipeline/stereo_image_proc/test/data/aloe-disp.png new file mode 100644 index 000000000..dd4a499be Binary files /dev/null and b/src/image_pipeline/stereo_image_proc/test/data/aloe-disp.png differ diff --git a/src/image_pipeline/stereo_image_proc/test/fixtures/disparity_image_publisher.py b/src/image_pipeline/stereo_image_proc/test/fixtures/disparity_image_publisher.py new file mode 100644 index 000000000..1bc97de27 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/test/fixtures/disparity_image_publisher.py @@ -0,0 +1,124 @@ +# Copyright (c) 2019, Open Source Robotics Foundation, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * 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 OWNER 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. + +import array +import sys + +import cv2 + +import numpy + +import rclpy +from rclpy.node import Node +from sensor_msgs.msg import CameraInfo +from sensor_msgs.msg import Image +from stereo_msgs.msg import DisparityImage + + +class DisparityImagePublisher(Node): + """ + Disparity image publisher test fixture. + + Publishes test data on topics expected by stereo_image_proc nodes: + + - left image + - left camera info + - right camera info + - disparity image + """ + + def __init__( + self, + left_image: numpy.ndarray, + disparity_image: numpy.ndarray, + *, + timer_period: float = 0.1 + ): + """ + Construct a stereo image publisher. + + :param: left_image The image to publish on the left topic. + :param: right_image The image to publish on the right topic. + :param: timer_period The period in seconds at which messages are published. + """ + super().__init__('image_publisher') + self.left_image_and_info = self._create_image_and_info_messages(left_image) + self.disparity_image = DisparityImage() + self.disparity_image.image.height = disparity_image.shape[0] + self.disparity_image.image.width = disparity_image.shape[1] + self.disparity_image.image.step = self.disparity_image.image.width * 4 + self.disparity_image.image.data = array.array('B', disparity_image.tobytes()) + + self.left_image_pub = self.create_publisher(Image, 'left/image_rect_color', 1) + self.left_camera_info_pub = self.create_publisher(CameraInfo, 'left/camera_info', 1) + self.right_camera_info_pub = self.create_publisher(CameraInfo, 'right/camera_info', 1) + self.disparity_image_pub = self.create_publisher(DisparityImage, 'disparity', 1) + self.timer = self.create_timer(timer_period, self.timer_callback) + + def _create_image_and_info_messages(self, image): + image_msg = Image() + image_msg.height = image.shape[0] + image_msg.width = image.shape[1] + image_msg.encoding = 'bgr8' + image_msg.step = image_msg.width * 3 + image_msg.data = array.array('B', image.tobytes()) + + camera_info_msg = CameraInfo() + camera_info_msg.height = image.shape[0] + camera_info_msg.width = image.shape[1] + + return (image_msg, camera_info_msg) + + def timer_callback(self): + now = self.get_clock().now().to_msg() + self.left_image_and_info[0].header.stamp = now + self.left_image_and_info[1].header.stamp = now + self.disparity_image.header.stamp = now + + self.left_image_pub.publish(self.left_image_and_info[0]) + self.left_camera_info_pub.publish(self.left_image_and_info[1]) + self.right_camera_info_pub.publish(self.left_image_and_info[1]) + self.disparity_image_pub.publish(self.disparity_image) + + +if __name__ == '__main__': + rclpy.init() + left_image = cv2.imread(sys.argv[1]) + disparity_image = cv2.imread(sys.argv[2], cv2.IMREAD_GRAYSCALE).astype(float) + publisher = DisparityImagePublisher(left_image, disparity_image) + try: + rclpy.spin(publisher) + except KeyboardInterrupt: + pass + finally: + publisher.destroy_node() + rclpy.shutdown() diff --git a/src/image_pipeline/stereo_image_proc/test/fixtures/stereo_image_publisher.py b/src/image_pipeline/stereo_image_proc/test/fixtures/stereo_image_publisher.py new file mode 100644 index 000000000..7e1e680a7 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/test/fixtures/stereo_image_publisher.py @@ -0,0 +1,125 @@ +# Copyright (c) 2019, Open Source Robotics Foundation, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * 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 OWNER 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. + +import array +import sys + +import cv2 + +import numpy + +import rclpy +from rclpy.node import Node +from sensor_msgs.msg import CameraInfo +from sensor_msgs.msg import Image + + +class StereoImagePublisher(Node): + """ + Stereo image publisher test fixture. + + Publishes test data on topics expected by stereo_image_proc nodes: + + - left image + - right image + - left camera info + - right camera info + """ + + def __init__( + self, + left_image: numpy.ndarray, + right_image: numpy.ndarray, + *, + timer_period: float = 0.1 + ): + """ + Construct a stereo image publisher. + + :param: left_image The image to publish on the left topic. + :param: right_image The image to publish on the right topic. + :param: timer_period The period in seconds at which messages are published. + """ + super().__init__('image_publisher') + self.left_image_and_info = self._create_image_and_info_messages(left_image) + self.right_image_and_info = self._create_image_and_info_messages(right_image) + + self.left_image_pub = self.create_publisher(Image, 'left/image_rect', 1) + self.left_camera_info_pub = self.create_publisher(CameraInfo, 'left/camera_info', 1) + self.right_image_pub = self.create_publisher(Image, 'right/image_rect', 1) + self.right_camera_info_pub = self.create_publisher(CameraInfo, 'right/camera_info', 1) + self.timer = self.create_timer(timer_period, self.timer_callback) + + def _create_image_and_info_messages(self, image): + image_msg = Image() + image_msg.height = image.shape[0] + image_msg.width = image.shape[1] + image_msg.encoding = 'bgr8' + image_msg.step = image_msg.width * 3 + image_msg.data = array.array('B', image.tobytes()) + + camera_info_msg = CameraInfo() + camera_info_msg.height = image.shape[0] + camera_info_msg.width = image.shape[1] + camera_info_msg.p = [ + 1.0, 0.0, 1.0, 0.0, + 0.0, 1.0, 1.0, 0.0, + 0.0, 0.0, 1.0, 0.0 + ] + + return (image_msg, camera_info_msg) + + def timer_callback(self): + now = self.get_clock().now().to_msg() + self.left_image_and_info[0].header.stamp = now + self.left_image_and_info[1].header.stamp = now + self.right_image_and_info[0].header.stamp = now + self.right_image_and_info[1].header.stamp = now + + self.left_image_pub.publish(self.left_image_and_info[0]) + self.left_camera_info_pub.publish(self.left_image_and_info[1]) + self.right_image_pub.publish(self.right_image_and_info[0]) + self.right_camera_info_pub.publish(self.right_image_and_info[1]) + + +if __name__ == '__main__': + rclpy.init() + left_image = cv2.imread(sys.argv[1]) + right_image = cv2.imread(sys.argv[2]) + publisher = StereoImagePublisher(left_image, right_image) + try: + rclpy.spin(publisher) + except KeyboardInterrupt: + pass + finally: + publisher.destroy_node() + rclpy.shutdown() diff --git a/src/image_pipeline/stereo_image_proc/test/test_disparity_node.py b/src/image_pipeline/stereo_image_proc/test/test_disparity_node.py new file mode 100644 index 000000000..35cd951a4 --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/test/test_disparity_node.py @@ -0,0 +1,114 @@ +# Copyright (c) 2019, Open Source Robotics Foundation, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * 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 OWNER 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. + +import os +import sys +import time +import unittest + +from launch import LaunchDescription + +from launch_ros.actions import Node + +import launch_testing + +import pytest + +import rclpy + +from stereo_msgs.msg import DisparityImage + + +@pytest.mark.rostest +def generate_test_description(): + + path_to_stereo_image_publisher_fixture = os.path.join( + os.path.dirname(__file__), 'fixtures', 'stereo_image_publisher.py') + path_to_test_data = os.path.join(os.path.dirname(__file__), 'data') + path_to_left_image = os.path.join(path_to_test_data, 'aloe-L.png') + path_to_right_image = os.path.join(path_to_test_data, 'aloe-R.png') + + return LaunchDescription([ + # Stereo image publisher + Node( + executable=sys.executable, + arguments=[ + path_to_stereo_image_publisher_fixture, + path_to_left_image, + path_to_right_image + ], + output='screen' + ), + # DisparityNode + Node( + package='stereo_image_proc', + executable='disparity_node', + name='disparity_node', + parameters=[ + {'use_system_default_qos': True} + ], + output='screen' + ), + launch_testing.actions.ReadyToTest(), + ]) + + +class TestDisparityNode(unittest.TestCase): + + @classmethod + def setUpClass(cls): + rclpy.init() + cls.node = rclpy.create_node('test_disparity_node') + + @classmethod + def tearDownClass(cls): + cls.node.destroy_node() + rclpy.shutdown() + + def test_message_received(self): + # Expect the disparity node to publish on '/disparity' topic + msgs_received = [] + self.node.create_subscription( + DisparityImage, + 'disparity', + lambda msg: msgs_received.append(msg), + 1 + ) + + # Wait up to 60 seconds to receive message + start_time = time.time() + while len(msgs_received) == 0 and (time.time() - start_time) < 60: + rclpy.spin_once(self.node, timeout_sec=0.1) + + assert len(msgs_received) > 0 + + # TODO(jacobperron): Compare received disparity image against expected diff --git a/src/image_pipeline/stereo_image_proc/test/test_point_cloud_node.py b/src/image_pipeline/stereo_image_proc/test/test_point_cloud_node.py new file mode 100644 index 000000000..91810b73d --- /dev/null +++ b/src/image_pipeline/stereo_image_proc/test/test_point_cloud_node.py @@ -0,0 +1,142 @@ +# Copyright (c) 2019, Open Source Robotics Foundation, Inc. +# All rights reserved. +# +# Software License Agreement (BSD License 2.0) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * 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 OWNER 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. + +import os +import sys +import time +import unittest + +from launch import LaunchDescription + +from launch_ros.actions import Node + +import launch_testing + +import pytest + +import rclpy + +from sensor_msgs.msg import PointCloud2 + + +@pytest.mark.rostest +def generate_test_description(): + + path_to_disparity_image_publisher_fixture = os.path.join( + os.path.dirname(__file__), 'fixtures', 'disparity_image_publisher.py') + path_to_test_data = os.path.join(os.path.dirname(__file__), 'data') + path_to_left_image = os.path.join(path_to_test_data, 'aloe-L.png') + path_to_disparity_image = os.path.join(path_to_test_data, 'aloe-disp.png') + + return LaunchDescription([ + # Disparity image publisher + Node( + executable=sys.executable, + arguments=[ + path_to_disparity_image_publisher_fixture, + path_to_left_image, + path_to_disparity_image, + ], + output='screen' + ), + # PointCloudNode (color enabled) + Node( + package='stereo_image_proc', + executable='point_cloud_node', + name='point_cloud_node', + output='screen', + parameters=[{ + 'use_color': True, + 'use_system_default_qos': True + }], + ), + # PointCloudNode (color disabled) + Node( + package='stereo_image_proc', + executable='point_cloud_node', + name='point_cloud_node_xyz', + output='screen', + parameters=[{ + 'use_color': False, + 'use_system_default_qos': True + }], + remappings=[ + ('/points2', '/xyz/points2'), + ] + ), + launch_testing.actions.ReadyToTest(), + ]) + + +class TestPointCloudNode(unittest.TestCase): + + @classmethod + def setUpClass(cls): + rclpy.init() + cls.node = rclpy.create_node('test_point_cloud_node') + + @classmethod + def tearDownClass(cls): + cls.node.destroy_node() + rclpy.shutdown() + + def test_message_received(self): + # Expect the point cloud node to publish on '/points2' topic + msgs_received_rgb = [] + self.node.create_subscription( + PointCloud2, + 'points2', + lambda msg: msgs_received_rgb.append(msg), + 1 + ) + + msgs_received_xyz = [] + self.node.create_subscription( + PointCloud2, + '/xyz/points2', + lambda msg: msgs_received_xyz.append(msg), + 1 + ) + + # Wait up to 60 seconds to receive message + start_time = time.time() + while (len(msgs_received_rgb) == 0 or len(msgs_received_xyz) == 0 + and (time.time() - start_time) < 60): + rclpy.spin_once(self.node, timeout_sec=(0.1)) + + assert len(msgs_received_rgb) > 0 + assert len(msgs_received_xyz) > 0 + + # point_step is length of point in bytes + # Expect 32 bytes if color is enabled, 16 if disabled + assert msgs_received_rgb[0].point_step == 32 + assert msgs_received_xyz[0].point_step == 16 diff --git a/src/image_pipeline/tools/ros2_dependencies.repos b/src/image_pipeline/tools/ros2_dependencies.repos new file mode 100644 index 000000000..973e6f106 --- /dev/null +++ b/src/image_pipeline/tools/ros2_dependencies.repos @@ -0,0 +1,10 @@ +repositories: + image_common: + type: git + url: https://github.com/ros-perception/image_common.git + version: ros2 + vision_opencv: + type: git + url: https://github.com/ros-perception/vision_opencv.git + version: ros2 + diff --git a/src/image_pipeline/tracetools_image_pipeline/.gitignore b/src/image_pipeline/tracetools_image_pipeline/.gitignore new file mode 100644 index 000000000..c18e2413a --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/.gitignore @@ -0,0 +1,2 @@ +doc_output/ +.DS_Store diff --git a/src/image_pipeline/tracetools_image_pipeline/CHANGELOG.rst b/src/image_pipeline/tracetools_image_pipeline/CHANGELOG.rst new file mode 100644 index 000000000..7b4448dbc --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/CHANGELOG.rst @@ -0,0 +1,201 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package tracetools_image_pipeline +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +4.0.0 (2022-12-24) +------------------ +* ROS 2: Fixed CMake (`#899 `_) (`#902 `_) + #backport `#899 `_ +* add myself as a maintainer (`#846 `_) +* Contributors: Alejandro Hernández Cordero, Michael Ferguson + +3.0.1 (2022-12-04) +------------------ + +3.0.0 (2022-04-29) +------------------ +* tracetools_image_pipeline version consistent with repo +* Omit changelogfile +* Deal with uncrustify and cpplint +* Add tracetools_image_pipeline package to the pipeline +* Contributors: Víctor Mayoral Vilches + +2.2.1 (2020-08-27) +------------------ + +2.2.0 (2020-07-27) +------------------ + +2.1.1 (2020-06-12) +------------------ + +2.1.0 (2020-05-27) +------------------ + +1.12.23 (2018-05-10) +-------------------- + +1.12.22 (2017-12-08) +-------------------- + +1.12.21 (2017-11-05) +-------------------- + +1.12.20 (2017-04-30) +-------------------- + +1.12.19 (2016-07-24) +-------------------- + +1.12.18 (2016-07-12) +-------------------- + +1.12.17 (2016-07-11) +-------------------- + +1.12.16 (2016-03-19) +-------------------- + +1.12.15 (2016-01-17) +-------------------- + +1.12.14 (2015-07-22) +-------------------- + +1.12.13 (2015-04-06) +-------------------- + +1.12.12 (2014-12-31) +-------------------- + +1.12.11 (2014-10-26) +-------------------- + +1.12.10 (2014-09-28) +-------------------- + +1.12.9 (2014-09-21) +------------------- + +1.12.8 (2014-08-19) +------------------- + +1.12.7 (2014-08-03) +------------------- + +1.12.6 (2014-07-27) +------------------- + +1.12.5 (2014-05-11) +------------------- + +1.12.4 (2014-04-28) +------------------- + +1.12.3 (2014-04-12) +------------------- + +1.12.2 (2014-04-08) +------------------- + +1.12.1 (2014-04-06) +------------------- + +1.12.0 (2014-04-04) +------------------- + +1.11.7 (2014-03-28) +------------------- + +1.11.6 (2014-01-29) +------------------- + +1.11.5 (2013-12-07) +------------------- + +1.11.4 (2013-11-23) +------------------- + +1.11.3 (2013-10-06) +------------------- + +1.11.2 (2013-08-27) +------------------- + +1.11.1 (2013-08-03) +------------------- + +1.11.0 (2013-07-11) +------------------- + +1.10.10 (2013-05-29) +-------------------- + +1.10.9 (2013-05-05) +------------------- + +1.10.8 (2013-03-20) +------------------- + +1.10.7 (2013-03-18) +------------------- + +1.10.6 (2013-03-16) +------------------- + +1.10.5 (2013-03-01) +------------------- + +1.10.4 (2013-02-02) +------------------- + +1.10.3 (2013-01-24 16:09) +------------------------- + +1.10.2 (2013-01-24 11:38) +------------------------- + +1.10.1 (2013-01-03 22:38) +------------------------- + +1.10.0 (2013-01-03 16:19) +------------------------- + +1.9.12 (2013-01-03 00:06) +------------------------- + +1.9.11 (2012-12-19) +------------------- + +1.9.10 (2012-11-15) +------------------- + +1.9.9 (2012-11-01) +------------------ + +1.9.8 (2012-10-29) +------------------ + +1.9.7 (2012-10-23) +------------------ + +1.9.6 (2012-10-04) +------------------ + +1.9.5 (2012-10-02) +------------------ + +1.9.4 (2012-10-01 21:30) +------------------------ + +1.9.3 (2012-10-01 19:29) +------------------------ + +1.9.2 (2012-09-13 12:42) +------------------------ + +1.9.1 (2012-09-13 00:47) +------------------------ + +1.9.0 (2012-09-07) +------------------ diff --git a/src/image_pipeline/tracetools_image_pipeline/CMakeLists.txt b/src/image_pipeline/tracetools_image_pipeline/CMakeLists.txt new file mode 100644 index 000000000..988ab9110 --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/CMakeLists.txt @@ -0,0 +1,153 @@ +cmake_minimum_required(VERSION 3.5) +project(tracetools_image_pipeline) + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + add_compile_options(/W4) +endif() + +find_package(ament_cmake_ros REQUIRED) + +if(WIN32) + set(DISABLED_DEFAULT ON) +else() + set(DISABLED_DEFAULT OFF) +endif() +option(TRACETOOLS_DISABLED "Explicitly disable support for tracing" ${DISABLED_DEFAULT}) +option(TRACETOOLS_NO_RDYNAMIC "Disable export of -rdynamic link flag" OFF) +option(TRACETOOLS_STATUS_CHECKING_TOOL "Enable the status checking tool" ON) + +if(NOT TRACETOOLS_DISABLED) + # Set TRACETOOLS_LTTNG_ENABLED if we can find lttng-ust + find_package(PkgConfig) + if(PkgConfig_FOUND) + pkg_check_modules(LTTNG lttng-ust) + if(LTTNG_FOUND) + set(TRACETOOLS_LTTNG_ENABLED TRUE) + message(STATUS "LTTng found: tracing enabled") + endif() + endif() +endif() + +# Store configuration variables for runtime use +# TRACETOOLS_DISABLED +# TRACETOOLS_LTTNG_ENABLED +configure_file(include/${PROJECT_NAME}/config.h.in include/${PROJECT_NAME}/config.h) + +# tracetools_image_pipeline lib +set(SOURCES + src/tracetools.c + src/utils.cpp +) +set(HEADERS + include/${PROJECT_NAME}/tracetools.h + include/${PROJECT_NAME}/utils.hpp + include/${PROJECT_NAME}/visibility_control.hpp +) +if(TRACETOOLS_LTTNG_ENABLED) + # We only need these if we're using LTTng + list(APPEND SOURCES + src/tp_call.c + ) + list(APPEND HEADERS + include/${PROJECT_NAME}/tp_call.h + ) +endif() + +# Copy select headers to the actual include/ directory that we will use and export +foreach(_header ${HEADERS}) + configure_file( + ${PROJECT_SOURCE_DIR}/${_header} + ${PROJECT_BINARY_DIR}/${_header} + COPYONLY + ) +endforeach() + +add_library(${PROJECT_NAME} ${SOURCES}) +if(TRACETOOLS_LTTNG_ENABLED) + target_link_libraries(${PROJECT_NAME} ${LTTNG_LIBRARIES}) + # Export -rdynamic for downtream packages to use when calling + # ament_target_dependencies() + # which is needed to resolve function addresses to symbols when + # using function pointers directly/without std::bind() + # (the flag should not be used on Windows, but TRACETOOLS_LTTNG_ENABLED + # should never be true on Windows anyway, so there is no need to check) + if(NOT TRACETOOLS_NO_RDYNAMIC) + target_link_libraries(${PROJECT_NAME} "-rdynamic") + endif() +endif() +if(WIN32) + # Causes the visibility macros to use dllexport rather than dllimport + # which is appropriate when building the dll but not consuming it. + target_compile_definitions(${PROJECT_NAME} PRIVATE "TRACETOOLS_BUILDING_DLL") +endif() + +# Only use output/binary include directory +target_include_directories(${PROJECT_NAME} PUBLIC + "$" + "$" +) +ament_export_targets(${PROJECT_NAME}_export HAS_LIBRARY_TARGET) + +if(TRACETOOLS_STATUS_CHECKING_TOOL) + # Status checking tool + add_executable(status + src/status.c + ) + target_link_libraries(status + ${PROJECT_NAME} + ) + install(TARGETS + status + DESTINATION lib/${PROJECT_NAME} + ) +endif() + +install( + DIRECTORY ${PROJECT_BINARY_DIR}/include/ + DESTINATION include +) +install( + TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}_export + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + RUNTIME DESTINATION bin + INCLUDES DESTINATION include +) + +ament_export_include_directories(include) +if(TRACETOOLS_LTTNG_ENABLED) + ament_export_libraries(${PROJECT_NAME} ${LTTNG_LIBRARIES}) + # Export -rdynamic for downstream packages using classic CMake variables + if(NOT TRACETOOLS_NO_RDYNAMIC) + ament_export_link_flags("-rdynamic") + endif() +else() + ament_export_libraries(${PROJECT_NAME}) +endif() + +if(BUILD_TESTING) + set(ament_cmake_cppcheck_ADDITIONAL_INCLUDE_DIRS ${LTTNG_INCLUDE_DIRS}) + find_package(ament_lint_auto REQUIRED) + ament_lint_auto_find_test_dependencies() + + if(TRACETOOLS_STATUS_CHECKING_TOOL) + # Run status tool executable as test and set pass/fail expectation appropriately + add_test(test_status_tool status) + if(NOT TRACETOOLS_LTTNG_ENABLED) + set_tests_properties(test_status_tool PROPERTIES WILL_FAIL TRUE) + endif() + endif() + +endif() + +ament_package() + +target_compile_definitions(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_VERSION="${${PROJECT_NAME}_VERSION}") diff --git a/src/image_pipeline/tracetools_image_pipeline/Doxyfile b/src/image_pipeline/tracetools_image_pipeline/Doxyfile new file mode 100644 index 000000000..74b49438f --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/Doxyfile @@ -0,0 +1,32 @@ +# All settings not listed here will use the Doxygen default values. + +PROJECT_NAME = "tracetools_image_pipeline" +PROJECT_NUMBER = master +PROJECT_BRIEF = "LTTng tracing provider wrapper for image_pipeline ROS 2 meta-package." + +INPUT = ./include + +RECURSIVE = YES +OUTPUT_DIRECTORY = doc_output + +EXTRACT_ALL = YES +SORT_MEMBER_DOCS = NO + +GENERATE_LATEX = NO + +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = NO +PREDEFINED = \ + "TRACETOOLS_PUBLIC=" + +EXCLUDE_SYMBOLS = \ + "DECLARE_TRACEPOINT" \ + "_demangle_symbol" \ + "_get_symbol_funcptr" + +# Tag files that do not exist will produce a warning and cross-project linking will not work. +TAGFILES += "../../../../../doxygen_tag_files/cppreference-doxygen-web.tag.xml=http://en.cppreference.com/w/" +# Uncomment to generate tag files for cross-project linking. +# (path relative to workspace dir: doxygen_tag_files/$PACKAGE_NAME.tag) +#GENERATE_TAGFILE = "../../../../../doxygen_tag_files/tracetools.tag" diff --git a/src/image_pipeline/tracetools_image_pipeline/README.md b/src/image_pipeline/tracetools_image_pipeline/README.md new file mode 100644 index 000000000..d1fa1f5fb --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/README.md @@ -0,0 +1,7 @@ +# tracetools_image_pipeline + +LTTng tracing provider wrapper for image_pipeline ROS 2 meta-package. `tracetools_image_pipeline` is a fork of [tracetools](https://gitlab.com/ros-tracing/ros2_tracing/-/tree/master/tracetools), refer to this package for the original work. + +### Quality Declaration + +No quality is claimed according to [REP-2004](https://www.ros.org/reps/rep-2004.html). diff --git a/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/config.h.in b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/config.h.in new file mode 100644 index 000000000..eb9633b77 --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/config.h.in @@ -0,0 +1,22 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// Copyright 2019 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACETOOLS_IMAGE_PIPELINE__CONFIG_H_ +#define TRACETOOLS_IMAGE_PIPELINE__CONFIG_H_ + +#cmakedefine TRACETOOLS_DISABLED +#cmakedefine TRACETOOLS_LTTNG_ENABLED + +#endif // TRACETOOLS_IMAGE_PIPELINE__CONFIG_H_ diff --git a/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/tp_call.h b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/tp_call.h new file mode 100644 index 000000000..70cdac38a --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/tp_call.h @@ -0,0 +1,105 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Provide fake header guard for cpplint +#undef TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ +#ifndef TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ +#define TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ + +#undef TRACEPOINT_PROVIDER +#define TRACEPOINT_PROVIDER ros2_image_pipeline + +#undef TRACEPOINT_INCLUDE +#define TRACEPOINT_INCLUDE "tracetools_image_pipeline/tp_call.h" + +#if !defined(_TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_) || defined(TRACEPOINT_HEADER_MULTI_READ) +#define _TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ + +#include + +#include +#include + + +// image_proc resize init callback +TRACEPOINT_EVENT( + TRACEPOINT_PROVIDER, // tracepoint provider name + image_proc_resize_init, // tracepoint name + TP_ARGS( + // input arguments, see https://lttng.org/docs/v2.12/#doc-tpp-def-input-args + const void *, resize_node_arg, + const void *, resize_image_msg_arg, + const void *, resize_info_msg_arg), + TP_FIELDS( + // output event fields, see https://lttng.org/man/3/lttng-ust/v2.12/#doc-ctf-macros + ctf_integer_hex(const void *, resize_node, resize_node_arg) + ctf_integer_hex(const void *, resize_image_msg, resize_image_msg_arg) + ctf_integer_hex(const void *, resize_info_msg, resize_info_msg_arg) + ctf_string(version, tracetools_image_pipeline_VERSION) + ) +) +// image_proc resize end of callback (after publication) +TRACEPOINT_EVENT( + TRACEPOINT_PROVIDER, + image_proc_resize_fini, + TP_ARGS( + const void *, resize_node_arg, + const void *, resize_image_msg_arg, + const void *, resize_info_msg_arg), + TP_FIELDS( + ctf_integer_hex(const void *, resize_node, resize_node_arg) + ctf_integer_hex(const void *, resize_image_msg, resize_image_msg_arg) + ctf_integer_hex(const void *, resize_info_msg, resize_info_msg_arg) + ctf_string(version, tracetools_image_pipeline_VERSION) + ) +) + +// image_proc rectify init callback +TRACEPOINT_EVENT( + TRACEPOINT_PROVIDER, // tracepoint provider name + image_proc_rectify_init, // tracepoint name + TP_ARGS( + // input arguments, see https://lttng.org/docs/v2.12/#doc-tpp-def-input-args + const void *, rectify_node_arg, + const void *, rectify_image_msg_arg, + const void *, rectify_info_msg_arg), + TP_FIELDS( + // output event fields, see https://lttng.org/man/3/lttng-ust/v2.12/#doc-ctf-macros + ctf_integer_hex(const void *, rectify_node, rectify_node_arg) + ctf_integer_hex(const void *, rectify_image_msg, rectify_image_msg_arg) + ctf_integer_hex(const void *, rectify_info_msg, rectify_info_msg_arg) + ctf_string(version, tracetools_image_pipeline_VERSION) + ) +) +// image_proc rectify end of callback (after publication) +TRACEPOINT_EVENT( + TRACEPOINT_PROVIDER, + image_proc_rectify_fini, + TP_ARGS( + const void *, rectify_node_arg, + const void *, rectify_image_msg_arg, + const void *, rectify_info_msg_arg), + TP_FIELDS( + ctf_integer_hex(const void *, rectify_node, rectify_node_arg) + ctf_integer_hex(const void *, rectify_image_msg, rectify_image_msg_arg) + ctf_integer_hex(const void *, rectify_info_msg, rectify_info_msg_arg) + ctf_string(version, tracetools_image_pipeline_VERSION) + ) +) + +#endif // _TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ + +#include + +#endif // TRACETOOLS_IMAGE_PIPELINE__TP_CALL_H_ diff --git a/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/tracetools.h b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/tracetools.h new file mode 100644 index 000000000..9a5b2fb1a --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/tracetools.h @@ -0,0 +1,131 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/** \mainpage tracetools_image_pipeline: tracing tools and instrumentation for + * for image_pipeline ROS 2 meta-package. + * + * `tracetools_image_pipeline` provides utilities to instrument ROS image_pipeline. + * It provides two main headers: + * + * - tracetools/tracetools.h + * - instrumentation functions + * - tracetools/utils.hpp + * - utility functions + */ + +#ifndef TRACETOOLS_IMAGE_PIPELINE__TRACETOOLS_H_ +#define TRACETOOLS_IMAGE_PIPELINE__TRACETOOLS_H_ + +#include +#include +#include +#include "tracetools_image_pipeline/config.h" +#include "tracetools_image_pipeline/visibility_control.hpp" + +#ifndef TRACETOOLS_DISABLED +/// Call a tracepoint. +/** + * This is the preferred method over calling the actual function directly. + */ +# define TRACEPOINT(event_name, ...) \ + (ros_trace_ ## event_name)(__VA_ARGS__) +# define DECLARE_TRACEPOINT(event_name, ...) \ + TRACETOOLS_PUBLIC void ros_trace_ ## event_name(__VA_ARGS__); +#else +# define TRACEPOINT(event_name, ...) ((void) (0)) +# define DECLARE_TRACEPOINT(event_name, ...) +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +/// Get tracing compilation status. +/** + * \return `true` if tracing is enabled, `false` otherwise + */ +TRACETOOLS_PUBLIC bool ros_trace_compile_status(); + +/// `image_proc_resize_init` +/** + * Tracepoint while initiating the callback of image_proc::ResizeNode component + * + * Notes the `tracetools_image_pipeline` version automatically. + * + * \param[in] resize_node rclcpp::node::Node subject to the callback + * \param[in] resize_image_msg image ROS message stored as sensor_msgs::msg::Image::ConstSharedPtr + * \param[in] resize_info_msg info ROS message as sensor_msgs::msg::CameraInfo::ConstSharedPtr + */ +DECLARE_TRACEPOINT( + image_proc_resize_init, + const void * resize_node, + const void * resize_image_msg, + const void * resize_info_msg) + +/// `image_proc_resize_fini` +/** + * Tracepoint while finishing the callback of image_proc::ResizeNode component + * + * Notes the `tracetools_image_pipeline` version automatically. + * + * \param[in] resize_node rclcpp::node::Node subject to the callback + * \param[in] resize_image_msg image ROS message stored as sensor_msgs::msg::Image::ConstSharedPtr + * \param[in] resize_info_msg info ROS message as sensor_msgs::msg::CameraInfo::ConstSharedPtr + */ +DECLARE_TRACEPOINT( + image_proc_resize_fini, + const void * resize_node, + const void * resize_image_msg, + const void * resize_info_msg) + +/// `image_proc_rectify_init` +/** + * Tracepoint while initiating the callback of image_proc::ResizeNode component + * + * Notes the `tracetools_image_pipeline` version automatically. + * + * \param[in] rectify_node rclcpp::node::Node subject to the callback + * \param[in] rectify_image_msg image ROS message stored as sensor_msgs::msg::Image::ConstSharedPtr + * \param[in] rectify_info_msg info ROS message as sensor_msgs::msg::CameraInfo::ConstSharedPtr + */ +DECLARE_TRACEPOINT( + image_proc_rectify_init, + const void * rectify_node, + const void * rectify_image_msg, + const void * rectify_info_msg) + +/// `image_proc_rectify_fini` +/** + * Tracepoint while finishing the callback of image_proc::ResizeNode component + * + * Notes the `tracetools_image_pipeline` version automatically. + * + * \param[in] rectify_node rclcpp::node::Node subject to the callback + * \param[in] rectify_image_msg image ROS message stored as sensor_msgs::msg::Image::ConstSharedPtr + * \param[in] rectify_info_msg info ROS message as sensor_msgs::msg::CameraInfo::ConstSharedPtr + */ +DECLARE_TRACEPOINT( + image_proc_rectify_fini, + const void * rectify_node, + const void * rectify_image_msg, + const void * rectify_info_msg) + + +#ifdef __cplusplus +} +#endif + +#endif // TRACETOOLS_IMAGE_PIPELINE__TRACETOOLS_H_ diff --git a/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/utils.hpp b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/utils.hpp new file mode 100644 index 000000000..6bc2f3d9f --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/utils.hpp @@ -0,0 +1,66 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// Copyright 2019 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef TRACETOOLS_IMAGE_PIPELINE__UTILS_HPP_ +#define TRACETOOLS_IMAGE_PIPELINE__UTILS_HPP_ + +#include +#include + +#include "tracetools_image_pipeline/visibility_control.hpp" + +/// Default symbol, used when address resolution fails. +#define SYMBOL_UNKNOWN "UNKNOWN" + +TRACETOOLS_PUBLIC const char * _demangle_symbol(const char * mangled); + +TRACETOOLS_PUBLIC const char * _get_symbol_funcptr(void * funcptr); + +/// Get symbol from an std::function object. +/** + * If function address resolution or symbol demangling fails, + * this will return a string that starts with \ref SYMBOL_UNKNOWN. + * + * \param[in] f the std::function object + * \return the symbol, or a placeholder + */ +template +const char * get_symbol(std::function f) +{ + typedef T (fnType)(U...); + fnType ** fnPointer = f.template target(); + // If we get an actual address + if (fnPointer != nullptr) { + void * funcptr = reinterpret_cast(*fnPointer); + return _get_symbol_funcptr(funcptr); + } + // Otherwise we have to go through target_type() + return _demangle_symbol(f.target_type().name()); +} + +/// Get symbol from a function-related object. +/** + * Fallback meant for lambdas with captures. + * + * \param[in] l a generic object + * \return the symbol + */ +template +const char * get_symbol(L && l) +{ + return _demangle_symbol(typeid(l).name()); +} + +#endif // TRACETOOLS_IMAGE_PIPELINE__UTILS_HPP_ diff --git a/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/visibility_control.hpp b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/visibility_control.hpp new file mode 100644 index 000000000..b6ba91f6d --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/include/tracetools_image_pipeline/visibility_control.hpp @@ -0,0 +1,57 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// Copyright 2015 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* This header must be included by all TRACETOOLS headers which declare symbols + * which are defined in the TRACETOOLS library. When not building the TRACETOOLS + * library, i.e. when using the headers in other package's code, the contents + * of this header change the visibility of certain symbols which the TRACETOOLS + * library cannot have, but the consuming code must have inorder to link. + */ + +#ifndef TRACETOOLS_IMAGE_PIPELINE__VISIBILITY_CONTROL_HPP_ +#define TRACETOOLS_IMAGE_PIPELINE__VISIBILITY_CONTROL_HPP_ + +// This logic was borrowed (then namespaced) from the examples on the gcc wiki: +// https://gcc.gnu.org/wiki/Visibility + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef __GNUC__ + #define TRACETOOLS_EXPORT __attribute__ ((dllexport)) + #define TRACETOOLS_IMPORT __attribute__ ((dllimport)) + #else + #define TRACETOOLS_EXPORT __declspec(dllexport) + #define TRACETOOLS_IMPORT __declspec(dllimport) + #endif + #ifdef TRACETOOLS_BUILDING_DLL + #define TRACETOOLS_PUBLIC TRACETOOLS_EXPORT + #else + #define TRACETOOLS_PUBLIC TRACETOOLS_IMPORT + #endif + #define TRACETOOLS_PUBLIC_TYPE TRACETOOLS_PUBLIC + #define TRACETOOLS_LOCAL +#else + #define TRACETOOLS_EXPORT __attribute__ ((visibility("default"))) + #define TRACETOOLS_IMPORT + #if __GNUC__ >= 4 + #define TRACETOOLS_PUBLIC __attribute__ ((visibility("default"))) + #define TRACETOOLS_LOCAL __attribute__ ((visibility("hidden"))) + #else + #define TRACETOOLS_PUBLIC + #define TRACETOOLS_LOCAL + #endif + #define TRACETOOLS_PUBLIC_TYPE +#endif + +#endif // TRACETOOLS_IMAGE_PIPELINE__VISIBILITY_CONTROL_HPP_ diff --git a/src/image_pipeline/tracetools_image_pipeline/package.xml b/src/image_pipeline/tracetools_image_pipeline/package.xml new file mode 100644 index 000000000..a35c6a470 --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/package.xml @@ -0,0 +1,23 @@ + + + + tracetools_image_pipeline + 4.0.0 + + LTTng tracing provider wrapper for image_pipeline ROS 2 meta-package. + + Víctor Mayoral-Vilches + Michael Ferguson + Apache 2.0 + Víctor Mayoral-Vilches + + ament_cmake_ros + pkg-config + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/image_pipeline/tracetools_image_pipeline/src/status.c b/src/image_pipeline/tracetools_image_pipeline/src/status.c new file mode 100644 index 000000000..06b906c1b --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/src/status.c @@ -0,0 +1,34 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// Copyright 2019 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "tracetools_image_pipeline/tracetools.h" + +int main() +{ +#ifndef TRACETOOLS_DISABLED + printf("Tracing "); + if (ros_trace_compile_status()) { + printf("enabled\n"); + return 0; + } else { + printf("disabled\n"); + return 1; + } +#else + printf("Tracing disabled through configuration\n"); + return 1; +#endif +} diff --git a/src/image_pipeline/tracetools_image_pipeline/src/tp_call.c b/src/image_pipeline/tracetools_image_pipeline/src/tp_call.c new file mode 100644 index 000000000..e195274d6 --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/src/tp_call.c @@ -0,0 +1,19 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// Copyright 2019 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define TRACEPOINT_CREATE_PROBES + +#define TRACEPOINT_DEFINE +#include "tracetools_image_pipeline/tp_call.h" diff --git a/src/image_pipeline/tracetools_image_pipeline/src/tracetools.c b/src/image_pipeline/tracetools_image_pipeline/src/tracetools.c new file mode 100644 index 000000000..fd7e3d326 --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/src/tracetools.c @@ -0,0 +1,103 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tracetools_image_pipeline/tracetools.h" + +#ifndef TRACETOOLS_DISABLED + +#ifdef TRACETOOLS_LTTNG_ENABLED +# include "tracetools_image_pipeline/tp_call.h" +# define CONDITIONAL_TP(...) \ + tracepoint(TRACEPOINT_PROVIDER, __VA_ARGS__) +#else +# define CONDITIONAL_TP(...) +#endif + +bool ros_trace_compile_status() +{ +#ifdef TRACETOOLS_LTTNG_ENABLED + return true; +#else + return false; +#endif +} + +#ifndef _WIN32 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +#else +# pragma warning(push) +# pragma warning(disable: 4100) +#endif + + +void TRACEPOINT( + image_proc_resize_init, + const void * resize_node_arg, + const void * resize_image_msg_arg, + const void * resize_info_msg_arg) +{ + CONDITIONAL_TP( + image_proc_resize_init, + resize_node_arg, + resize_image_msg_arg, + resize_info_msg_arg); +} + +void TRACEPOINT( + image_proc_resize_fini, + const void * resize_node_arg, + const void * resize_image_msg_arg, + const void * resize_info_msg_arg) +{ + CONDITIONAL_TP( + image_proc_resize_fini, + resize_node_arg, + resize_image_msg_arg, + resize_info_msg_arg); +} + +void TRACEPOINT( + image_proc_rectify_init, + const void * rectify_node_arg, + const void * rectify_image_msg_arg, + const void * rectify_info_msg_arg) +{ + CONDITIONAL_TP( + image_proc_rectify_init, + rectify_node_arg, + rectify_image_msg_arg, + rectify_info_msg_arg); +} + +void TRACEPOINT( + image_proc_rectify_fini, + const void * rectify_node_arg, + const void * rectify_image_msg_arg, + const void * rectify_info_msg_arg) +{ + CONDITIONAL_TP( + image_proc_rectify_fini, + rectify_node_arg, + rectify_image_msg_arg, + rectify_info_msg_arg); +} + +#ifndef _WIN32 +# pragma GCC diagnostic pop +#else +# pragma warning(pop) +#endif + +#endif // TRACETOOLS_DISABLED diff --git a/src/image_pipeline/tracetools_image_pipeline/src/utils.cpp b/src/image_pipeline/tracetools_image_pipeline/src/utils.cpp new file mode 100644 index 000000000..902864431 --- /dev/null +++ b/src/image_pipeline/tracetools_image_pipeline/src/utils.cpp @@ -0,0 +1,51 @@ +// Copyright 2021 Víctor Mayoral-Vilches +// Copyright 2019 Robert Bosch GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tracetools_image_pipeline/config.h" + +#ifdef TRACETOOLS_LTTNG_ENABLED +#include +#include +#endif +#include "tracetools_image_pipeline/utils.hpp" + +const char * _demangle_symbol(const char * mangled) +{ +#ifdef TRACETOOLS_LTTNG_ENABLED + char * demangled = nullptr; + int status; + demangled = abi::__cxa_demangle(mangled, NULL, 0, &status); + // Use demangled symbol if possible + const char * demangled_val = (status == 0 ? demangled : mangled); + return demangled_val != 0 ? demangled_val : "UNKNOWN_demangling_failed"; +#else + (void)mangled; + return "DISABLED__demangle_symbol"; +#endif +} + +const char * _get_symbol_funcptr(void * funcptr) +{ +#ifdef TRACETOOLS_LTTNG_ENABLED + Dl_info info; + if (dladdr(funcptr, &info) == 0) { + return SYMBOL_UNKNOWN; + } + return _demangle_symbol(info.dli_sname); +#else + (void)funcptr; + return "DISABLED__get_symbol_funcptr"; +#endif +} diff --git a/src/image_pipeline/wiki_files/dcam-driver.svg b/src/image_pipeline/wiki_files/dcam-driver.svg new file mode 100644 index 000000000..5bbf2465b --- /dev/null +++ b/src/image_pipeline/wiki_files/dcam-driver.svg @@ -0,0 +1,249 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + camera driver + + + Cam + + + /<camera> /camera_info [CameraInfo] /image_raw [Image] + + + diff --git a/src/image_pipeline/wiki_files/image_proc.svg b/src/image_pipeline/wiki_files/image_proc.svg new file mode 100644 index 000000000..cae39c140 --- /dev/null +++ b/src/image_pipeline/wiki_files/image_proc.svg @@ -0,0 +1,335 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + camera driver + + + Cam + + + + + + + + image_proc + + + + + camera_info [CameraInfo]image_raw [Image] + image_mono [Image]image_color [Image]image_rect [Image]image_rect_color [Image] + + diff --git a/src/image_pipeline/wiki_files/image_proc_dual.svg b/src/image_pipeline/wiki_files/image_proc_dual.svg new file mode 100644 index 000000000..887bd242f --- /dev/null +++ b/src/image_pipeline/wiki_files/image_proc_dual.svg @@ -0,0 +1,335 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + camera driver + + + Cam + + + + + + + + image_proc + + + + + /<camera> /camera_info [CameraInfo] /image_raw [Image] + /<camera> /image_mono [Image] /image_color [Image] /image_rect [Image] /image_rect_color [Image] + + diff --git a/src/image_pipeline/wiki_files/rospack_nosubdirs b/src/image_pipeline/wiki_files/rospack_nosubdirs new file mode 100644 index 000000000..e69de29bb diff --git a/src/image_pipeline/wiki_files/stereo_image_proc.svg b/src/image_pipeline/wiki_files/stereo_image_proc.svg new file mode 100644 index 000000000..4fa399768 --- /dev/null +++ b/src/image_pipeline/wiki_files/stereo_image_proc.svg @@ -0,0 +1,477 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + camera driver + + Cam + + + + + + Cam + + + + + + stereo_image_proc + + + + + + left/ camera_info [CameraInfo] image_raw [Image] + right/ camera_info [CameraInfo] image_raw [Image] + camera driver left/ image_mono [Image] image_color [Image] image_rect [Image] image_rect_color [Image]right/ image_mono [Image] image_color [Image] image_rect [Image] image_rect_color [Image]disparity [DisparityImage]image_disparity [Image]points [PointCloud] + + diff --git a/src/image_pipeline/wiki_files/stereo_image_proc_stereo.svg b/src/image_pipeline/wiki_files/stereo_image_proc_stereo.svg new file mode 100644 index 000000000..01fb69a00 --- /dev/null +++ b/src/image_pipeline/wiki_files/stereo_image_proc_stereo.svg @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + camera driver + + + /<camera> /left/camera_info [CameraInfo] /left/image_raw [Image] /right/camera_info [CameraInfo] /right/image_raw [Image] + + + Cam + Cam + + + + + stereo_image_proc + + + + /<camera> /image_disparity [DisparityImage] /left/image_mono [Image] /left/image_color [Image] /left/image_rect [Image] /left/image_rect_color [Image] /right/image_mono [Image] /right/image_color [Image] /right/image_rect [Image] /right/image_rect_color [Image] + + diff --git a/src/image_pipeline/wiki_files/stereocam-driver.svg b/src/image_pipeline/wiki_files/stereocam-driver.svg new file mode 100644 index 000000000..32a5057ee --- /dev/null +++ b/src/image_pipeline/wiki_files/stereocam-driver.svg @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + camera driver + + + + /<camera> /left/camera_info [CameraInfo] /left/image_raw [Image] /right/camera_info [CameraInfo] /right/image_raw [Image] + + + Cam + Cam + + + diff --git a/src/image_pipeline/wiki_files/stoc.svg b/src/image_pipeline/wiki_files/stoc.svg new file mode 100644 index 000000000..361c6fe49 --- /dev/null +++ b/src/image_pipeline/wiki_files/stoc.svg @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Cam + Cam + + + + + stereo_image_proc + + + + /stereo /image_disparity [DisparityImage] /left/image_mono [Image] /left/image_color [Image] /left/image_rect [Image] /left/image_rect_color [Image] /right/image_mono [Image] /right/image_color [Image] /right/image_rect [Image] /right/image_rect_color [Image] + + diff --git a/src/rktl_autonomy/config/rocket_league.yaml b/src/rktl_autonomy/config/rocket_league.yaml new file mode 100644 index 000000000..230fa6b28 --- /dev/null +++ b/src/rktl_autonomy/config/rocket_league.yaml @@ -0,0 +1,37 @@ +/**: + ros__parameters: + reward: + # reward to be given each frame + # constant: 0.0 + # # reward as a function of squared distance from the ball + # ball_dist_sq: -0.5 + # # reward as a function of squared distance from the ball to the goal + # goal_dist_sq: -1.0 + # reward given when car changes velocity direction + direction_change: 0 + # reward given when the episode ends with the car scoring the proper goal + win: [100, 200, 300] + # reward given when the episode ends with the car scoring on the wrong goal + loss: [100, 100, 100] + # reward given each frame when the car is in reverse + # reverse: -25 + # # reward given each frame when the car is near the walls + # walls: + # # actual reward value + # value: -50 + # # threshold distance (meters) + # threshold: 0.25 + + # duration when the episode will be terminated. Unit is seconds (sim time) + max_episode_time: 15 + + log: + base_dir: "~/catkin_ws/data/rocket_league/" + # frequency to save progress plot. Unit is episodes + plot_freq: 50 + # variables to display in progress plot + basic: + - "duration" + - "goals" + advanced: + - "net_reward" diff --git a/src/rktl_autonomy/launch/rocket_league_agent.launch.py b/src/rktl_autonomy/launch/rocket_league_agent.launch.py new file mode 100644 index 000000000..e5594ef41 --- /dev/null +++ b/src/rktl_autonomy/launch/rocket_league_agent.launch.py @@ -0,0 +1,48 @@ +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument, LogInfo +from launch.conditions import IfCondition +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node + +def generate_launch_description(): + plot_log = LaunchConfiguration('plot_log') # True if plotting is enabled. Else False + weights = LaunchConfiguration('weights') # Filepath to weights + + plot_log_launch_arg = DeclareLaunchArgument( + 'plot_log', + default_value='false', + description='Set to true to enable logging for plotting performance.' + ) + + weights_launch_arg = DeclareLaunchArgument( + 'weights', + default_value='~/catkin_ws/data/rocket_league/model', + description='filepath to weights.' + ) + + plot_log_info = LogInfo(condition=IfCondition(plot_log), msg="Enabling performance plotting...") + + agent_node = Node( + package='rktl_autonomy', + executable='rocket_league_agent', + name='rocket_league_agent', + output='screen', + parameters=[{'weights': weights}], + ) + + plotter_node = Node( + condition=IfCondition(plot_log), + package='rktl_autonomy', + executable='plotter', + name='plotter', + output='screen', + remappings=[('~log', 'rocket_league_agent/log')], + ) + + return LaunchDescription([ + plot_log_launch_arg, + weights_launch_arg, + plot_log_info, + agent_node, + plotter_node + ]) diff --git a/src/rktl_autonomy/launch/rocket_league_train.launch.py b/src/rktl_autonomy/launch/rocket_league_train.launch.py new file mode 100644 index 000000000..7ba40f1e2 --- /dev/null +++ b/src/rktl_autonomy/launch/rocket_league_train.launch.py @@ -0,0 +1,103 @@ +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument, LogInfo +from launch.conditions import IfCondition +from launch.substitutions import LaunchConfiguration +from launch_ros.actions import Node +from launch.actions import IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import PathJoinSubstitution +from launch_ros.substitutions import FindPackageShare +from ament_index_python.packages import get_package_share_directory +import os + +def generate_launch_description(): + plot_log_launch_arg = DeclareLaunchArgument( + 'plot_log', + default_value='true', + description='Set to true to enable logging for plotting performance.' + ) + + agent_name_launch_arg = DeclareLaunchArgument( + 'agent_name', + default_value='rocket_league_agent', + description='Name of the agent.' + ) + + log_file_launch_arg = DeclareLaunchArgument( + 'log_file', + default_value='rocket_league_agent/log', + description='Filepath for logger output' + ) + + render_launch_arg = DeclareLaunchArgument( + 'render', + default_value='false', + description='Set to true to enable rendering.' + ) + + sim_mode_launch_arg = DeclareLaunchArgument( + 'sim_mode', + default_value='ideal', + description='Simulation mode.' + ) + + rate_launch_arg = DeclareLaunchArgument( + 'rate', + default_value='10.0', + description='Rate parameter.' + ) + + agent_type_launch_arg = DeclareLaunchArgument( + 'agent_type', + default_value='none', + description='Agent type.' + ) + + plot_log_info = LogInfo( + condition=IfCondition(LaunchConfiguration('plot_log')), + msg="Enabling performance plotting..." + ) + + agent_node = Node( + package='rktl_autonomy', + executable='rocket_league_agent', + name=LaunchConfiguration('agent_name'), + output='screen', + parameters=[{'rate': LaunchConfiguration('rate')}], + namespace=LaunchConfiguration('agent_name') + ) + + # sim_path = PathJoinSubstitution([FindPackageShare('rktl_launch'), 'launch', 'rocket_league_sim.launch.py']) + sim_path = os.path.join(get_package_share_directory('rktl_launch'), 'launch', 'rocket_league_sim.launch.py') + sim_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource([sim_path]), # TODO: Replace with the path to the launch file + launch_arguments={ + 'render': LaunchConfiguration('render'), + 'sim_mode': LaunchConfiguration('sim_mode'), + 'agent_type': LaunchConfiguration('agent_type'), + }.items(), + ) + + plotter_node = Node( + condition=IfCondition(LaunchConfiguration('plot_log')), + package='rktl_autonomy', + executable='plotter', + name='plotter', + output='screen', + remappings=[('~log', LaunchConfiguration('log_file'))], + namespace=LaunchConfiguration('agent_name') + ) + + return LaunchDescription([ + plot_log_launch_arg, + agent_name_launch_arg, + log_file_launch_arg, + render_launch_arg, + sim_mode_launch_arg, + rate_launch_arg, + agent_type_launch_arg, + # plot_log_info, + agent_node, + sim_launch, + # plotter_node + ]) diff --git a/src/rktl_autonomy/package.xml b/src/rktl_autonomy/package.xml new file mode 100644 index 000000000..79097d1ca --- /dev/null +++ b/src/rktl_autonomy/package.xml @@ -0,0 +1,19 @@ + + + + rktl_autonomy + 0.0.0 + TODO: Package description + rtjord + TODO: License declaration + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + ros2launch + + + ament_python + + diff --git a/src/rktl_autonomy/resource/rktl_autonomy b/src/rktl_autonomy/resource/rktl_autonomy new file mode 100644 index 000000000..e69de29bb diff --git a/rktl_autonomy/src/rktl_autonomy/__init__.py b/src/rktl_autonomy/rktl_autonomy/__init__.py similarity index 69% rename from rktl_autonomy/src/rktl_autonomy/__init__.py rename to src/rktl_autonomy/rktl_autonomy/__init__.py index e91dad629..b2673365d 100644 --- a/rktl_autonomy/src/rktl_autonomy/__init__.py +++ b/src/rktl_autonomy/rktl_autonomy/__init__.py @@ -15,16 +15,17 @@ """ from ._ros_interface import ROSInterface -from .cartpole_interface import CartpoleInterface -from .cartpole_direct_interface import CartpoleDirectInterface -from .snake_interface import SnakeInterface +# from .cartpole_interface import CartpoleInterface +# from .cartpole_direct_interface import CartpoleDirectInterface +# from .snake_interface import SnakeInterface from .rocket_league_interface import RocketLeagueInterface -from .env_counter import EnvCounter +# from .env_counter import EnvCounter __all__ = [ "ROSInterface", - "CartpoleInterface", - "CartpoleDirectInterface", - "SnakeInterface", - "RocketLeagueInterface", - "EnvCounter"] \ No newline at end of file + # "CartpoleInterface", + # "CartpoleDirectInterface", + # "SnakeInterface", + "RocketLeagueInterface" + # "EnvCounter" +] \ No newline at end of file diff --git a/rktl_autonomy/src/rktl_autonomy/_ros_interface.py b/src/rktl_autonomy/rktl_autonomy/_ros_interface.py similarity index 79% rename from rktl_autonomy/src/rktl_autonomy/_ros_interface.py rename to src/rktl_autonomy/rktl_autonomy/_ros_interface.py index c199fd7ae..8c8f2d917 100644 --- a/rktl_autonomy/src/rktl_autonomy/_ros_interface.py +++ b/src/rktl_autonomy/rktl_autonomy/_ros_interface.py @@ -12,9 +12,10 @@ from threading import Condition import time, uuid, socket, os -from gym import Env +from gymnasium import Env -import rospy, roslaunch +import rclpy +from rclpy.duration import Duration from rosgraph_msgs.msg import Clock from diagnostic_msgs.msg import DiagnosticStatus, KeyValue @@ -75,45 +76,53 @@ def __init__(self, node_name='gym_interface', eval=False, launch_file=None, laun # find a random open one sock.bind(('localhost', 0)) port = sock.getsockname()[1] - # launch the training ROS network - ros_id = roslaunch.rlutil.get_or_generate_uuid(None, False) - roslaunch.configure_logging(ros_id) - launch_file = roslaunch.rlutil.resolve_launch_arguments(launch_file)[0] - launch_args = [f'render:={port==11311}', f'plot_log:={port==11311}'] + launch_args + [f'agent_name:={node_name}'] - launch = roslaunch.parent.ROSLaunchParent(ros_id, [(launch_file, launch_args)], port=port) - launch.start() - self.close = lambda : launch.shutdown() + # # launch the training ROS network + # ros_id = roslaunch.rlutil.get_or_generate_uuid(None, False) + # roslaunch.configure_logging(ros_id) + # launch_file = roslaunch.rlutil.resolve_launch_arguments(launch_file)[0] + # launch_args = [f'render:={port==11311}', f'plot_log:={port==11311}'] + launch_args + [f'agent_name:={node_name}'] + # launch = roslaunch.parent.ROSLaunchParent(ros_id, [(launch_file, launch_args)], port=port) + # launch.start() + # self.close = lambda : launch.shutdown() # initialize self os.environ['ROS_MASTER_URI'] = f'http://localhost:{port}' - rospy.init_node(node_name) + # rospy.init_node(node_name) + self.node = rclpy.create_node(node_name) # let someone else take a turn os.remove(f'/tmp/{run_id}_launch') else: # use an existing ROS network - rospy.init_node(node_name) + # rospy.init_node(node_name) + self.node = rclpy.create_node(node_name) # private variables self._cond = Condition() # additional set up for training if not self.__EVAL_MODE: - self.__DELTA_T = rospy.Duration.from_sec(1.0 / rospy.get_param('~rate', 30.0)) - self.__clock_pub = rospy.Publisher('/clock', Clock, queue_size=1, latch=True) + # self.__DELTA_T = rospy.Duration.from_sec(1.0 / rospy.get_param('~rate', 30.0)) + # self.__clock_pub = rospy.Publisher('/clock', Clock, queue_size=1, latch=True) + + self.__DELTA_T = int(1e9 / self.node.get_parameter_or('rate', rclpy.Parameter(30.0)).get_parameter_value().double_value) # nanoseconds + self.__clock_pub = self.node.create_publisher(Clock, '/clock', qos_profile=1) # initialize sim time - self.__time = rospy.Time.from_sec(time.time()) - self.__clock_pub.publish(self.__time) + # self.__time = rospy.Time.from_sec(time.time()) + self.__time = int(time.time() * 1e9) # nanoseconds + self.__clock_pub.publish(Duration(nanoseconds=self.__time)) # additional set up for logging if run_id is None: run_id = uuid.uuid4() if self.__EVAL_MODE: port = 11311 + else: + port = None self.__LOG_ID = f'{run_id}:{port}' - self.__log_pub = rospy.Publisher('~log', DiagnosticStatus, queue_size=1) + self.__log_pub = self.node.create_publisher(DiagnosticStatus, '~log', qos_profile=1) self.__episode = 0 self.__net_reward = 0 - self.__start_time = rospy.Time.now() + self.__start_time = self.node.get_clock().now() def step(self, action): """ @@ -155,7 +164,7 @@ def reset(self): info = { 'episode' : self.__episode, 'net_reward' : self.__net_reward, - 'duration' : (rospy.Time.now() - self.__start_time).to_sec() + 'duration' : self.node.get_clock().now() - self.__start_time } info.update(self._get_state()[3]) # send message @@ -175,7 +184,7 @@ def reset(self): self._reset_env() self._reset_self() self.__step_time_and_wait_for_state(5) - self.__start_time = rospy.Time.now() # logging + self.__start_time = self.node.get_clock().now() # logging return self._get_state()[0] def __step_time_and_wait_for_state(self, max_retries=1): @@ -186,11 +195,11 @@ def __step_time_and_wait_for_state(self, max_retries=1): retries = 0 while not self.__wait_once_for_state(): if retries >= max_retries: - rospy.logerr("Failed to get new state.") + self.node.get_logger().warn('Failed to get new state.') raise SimTimeException else: self.__time += self.__DELTA_T - self.__clock_pub.publish(self.__time) + self.__clock_pub.publish(Duration(nanoseconds = int(self.__time))) retries += 1 else: while not self.__wait_once_for_state(): @@ -200,8 +209,10 @@ def __wait_once_for_state(self): """Wait and allow other threads to run.""" with self._cond: has_state = self._cond.wait_for(self._has_state, 0.25) - if rospy.is_shutdown(): - raise rospy.ROSInterruptException() + # if rospy.is_shutdown(): + if not rclpy.ok(): + # raise rospy.ROSInterruptException() + raise Exception() return has_state # All the below abstract methods / properties must be implemented by subclasses diff --git a/rktl_autonomy/nodes/plotter b/src/rktl_autonomy/rktl_autonomy/plotter.py similarity index 75% rename from rktl_autonomy/nodes/plotter rename to src/rktl_autonomy/rktl_autonomy/plotter.py index d3638180a..4404eff17 100755 --- a/rktl_autonomy/nodes/plotter +++ b/src/rktl_autonomy/rktl_autonomy/plotter.py @@ -6,7 +6,10 @@ All rights reserved. """ -import rospy +# import rospy +import rclpy +from rclpy.node import Node +from rclpy.parameter import Parameter from diagnostic_msgs.msg import DiagnosticStatus from mpl_toolkits.axes_grid1 import host_subplot import mpl_toolkits.axisartist as AA @@ -15,29 +18,34 @@ import matplotlib.pyplot as plt from numpy import append from os.path import expanduser, normpath - -class Plotter(object): +class Plotter(rclpy.create_node): """Plot progress during training.""" def __init__(self): - rospy.init_node('model_progress_plotter') + #rospy.init_node('model_progress_plotter') + super().__init__('model_progress_plotter') # Constants - self.LOG_DIR = normpath(expanduser(rospy.get_param('~log/base_dir'))) - self.PLOT_FREQ = rospy.get_param('~log/plot_freq', 25) - self.BASIC_VARS = rospy.get_param('~log/basic', ["duration"]) - self.ADVANCED_VARS = rospy.get_param('~log/advanced', ["net_reward"]) - - self.KEYS = ["episode"] + self.BASIC_VARS + self.ADVANCED_VARS + self.LOG_DIR = normpath(expanduser(self.get_parameter('log/base_dir').get_parameter_value().string_value)) + #self.PLOT_FREQ = rospy.get_param('~log/plot_freq', 25) + self.PLOT_FREQ = self.get_parameter_or('log/plot_freq', Parameter(25)).get_parameter_value().integer_value + #self.BASIC_VARS = rospy.get_param('~log/basic', ["duration"]) + self.BASIC_VARS = self.get_parameter_or('log/basic', Parameter(["duration"])).get_parameter_value().string_value + #self.ADVANCED_VARS = rospy.get_param('~log/advanced', ["net_reward"]) + self.ADVANCED_VARS = self.get_parameter_or('log/advanced', Parameter(["net_reward"])).get_parameter_value().string_value + + self.KEYS = ["episode"] + self.KEYS.append(self.BASIC_VARS) + self.KEYS.append(self.ADVANCED_VARS) # Subscribers - rospy.Subscriber('~log', DiagnosticStatus, self.progress_cb) + #rospy.Subscriber('~log', DiagnosticStatus, self.progress_cb) + self.create_subscription(DiagnosticStatus, '~log', self.progress_cb, qos_profile=10) - self.history = None + self.history = [] self.LOG_NAME = None self.next_plot_episode = self.PLOT_FREQ self.init_plot() - - rospy.spin() + rclpy.spin(self) def init_plot(self): """Initialize the plot and all axes.""" @@ -96,13 +104,13 @@ def progress_cb(self, progress_msg): self.LOG_NAME = '/' + progress_msg.hardware_id.replace(':', '/plot_') + '.png' data = {} - + for item in progress_msg.values: if item.key in self.KEYS: data[item.key] = float(item.value) if data["episode"] is not None: - if self.history is None: + if self.history is []: self.history = [data] else: self.history.append(data) @@ -110,9 +118,10 @@ def progress_cb(self, progress_msg): if data["episode"] >= self.next_plot_episode: self.plot() self.next_plot_episode += self.PLOT_FREQ - self.history = None + self.history = [] else: - rospy.logerr("Bad progress message.") + #rospy.logerr("Bad progress message.") + self.get_logger().warn("Bad progress message.") def plot(self): """Add new data to plot, show, and save""" @@ -165,8 +174,9 @@ def plot(self): plt.draw() # update file - rospy.loginfo(f"Saving training progress to {self.LOG_DIR}{self.LOG_NAME}") - plt.savefig(self.LOG_DIR + self.LOG_NAME) + #rospy.loginfo(f"Saving training progress to {self.LOG_DIR}{self.LOG_NAME}") + self.get_logger().info(f"Saving training progress to {self.LOG_DIR}{self.LOG_NAME}") + plt.savefig(self.LOG_DIR + str(self.LOG_NAME)) if __name__ == "__main__": Plotter() diff --git a/src/rktl_autonomy/rktl_autonomy/rocket_league_agent.py b/src/rktl_autonomy/rktl_autonomy/rocket_league_agent.py new file mode 100755 index 000000000..c51e5d860 --- /dev/null +++ b/src/rktl_autonomy/rktl_autonomy/rocket_league_agent.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +"""Real-time evaluation of the agent trained for the Rocket League project. +License: + BSD 3-Clause License + Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + All rights reserved. +""" + +from rktl_autonomy.rocket_league_interface import RocketLeagueInterface +from stable_baselines3 import PPO +from os.path import expanduser +from rclpy.exceptions import ROSInterruptException + + +def main(): + # create interface (and init ROS) + env = RocketLeagueInterface(eval=True) + + # load the model + weights = expanduser(env.node.get_parameter('weights').get_parameter_value().string_value) + model = PPO.load(weights) + + # evaluate in real-time + obs = env.reset() + while True: + action, __ = model.predict(obs) + try: + obs, __, __, __ = env.step(action) + # except rospy.ROSInterruptException: + except ROSInterruptException: + exit() + +if __name__=='__main__': + main() + \ No newline at end of file diff --git a/rktl_autonomy/src/rktl_autonomy/rocket_league_interface.py b/src/rktl_autonomy/rktl_autonomy/rocket_league_interface.py similarity index 57% rename from rktl_autonomy/src/rktl_autonomy/rocket_league_interface.py rename to src/rktl_autonomy/rktl_autonomy/rocket_league_interface.py index e8b343570..462f1e6d9 100755 --- a/rktl_autonomy/src/rktl_autonomy/rocket_league_interface.py +++ b/src/rktl_autonomy/rktl_autonomy/rocket_league_interface.py @@ -11,18 +11,20 @@ """ # package -from rktl_autonomy import ROSInterface -from gym.spaces import Box, Discrete +from rktl_autonomy._ros_interface import ROSInterface +from gymnasium.spaces import Box, Discrete # ROS -import rospy +import rclpy +from rclpy.node import Node +from rclpy.parameter import Parameter from nav_msgs.msg import Odometry from rktl_msgs.msg import ControlCommand, MatchStatus from std_srvs.srv import Empty # System import numpy as np -from tf.transformations import euler_from_quaternion +from transformations import euler_from_quaternion from enum import IntEnum, unique, auto from math import pi, tan @@ -47,63 +49,67 @@ def __init__(self, eval=False, launch_file=('rktl_autonomy', 'rocket_league_trai # Constants self.env_number = env_number # Actions - self._MIN_VELOCITY = -rospy.get_param('/cars/throttle/max_speed') - self._MAX_VELOCITY = rospy.get_param('/cars/throttle/max_speed') - self._MIN_CURVATURE = -tan(rospy.get_param('/cars/steering/max_throw')) / rospy.get_param('cars/length') - self._MAX_CURVATURE = tan(rospy.get_param('/cars/steering/max_throw')) / rospy.get_param('cars/length') + self._MIN_VELOCITY = -self.node.get_parameter('/cars/throttle/max_speed').get_parameter_value().double_value + self._MAX_VELOCITY = self.node.get_parameter('/cars/throttle/max_speed').get_parameter_value().double_value + self._MIN_CURVATURE = -tan(self.node.get_parameter('/cars/steering/max_throw').get_parameter_value().double_value) / self.node.get_parameter('cars/length').get_parameter_value().double_value + self._MAX_CURVATURE = tan(self.node.get_parameter('/cars/steering/max_throw').get_parameter_value().double_value) / self.node.get_parameter('cars/length').get_parameter_value().double_value # Action space overrides - if rospy.has_param('~action_space/velocity/min'): - min_velocity = rospy.get_param('~action_space/velocity/min') + if self.node.has_parameter('~action_space/velocity/min'): + min_velocity = self.node.get_parameter('action_space/velocity/min').get_parameter_value().double_value assert min_velocity > self._MIN_VELOCITY self._MIN_VELOCITY = min_velocity - if rospy.has_param('~action_space/velocity/max'): - max_velocity = rospy.get_param('~action_space/velocity/max') + + if self.node.has_parameter('~action_space/velocity/max'): + max_velocity = self.node.get_parameter('action_space/velocity/max').get_parameter_value().double_value assert max_velocity < self._MAX_VELOCITY self._MAX_VELOCITY = max_velocity - if rospy.has_param('~action_space/curvature/min'): - min_curvature = rospy.get_param('~action_space/curvature/min') + + if self.node.has_parameter('~action_space/curvature/min'): + min_curvature = self.node.get_parameter('action_space/curvature/min').get_parameter_value().double_value assert min_curvature > self._MIN_CURVATURE self._MIN_CURVATURE = min_curvature - if rospy.has_param('~action_space/curvature/max'): - max_curvature = rospy.get_param('~action_space/curvature/max') + + if self.node.has_parameter('~action_space/curvature/max'): + max_curvature = self.node.get_parameter('action_space/curvature/max').get_parameter_value().double_value assert max_curvature < self._MAX_CURVATURE self._MAX_CURVATURE = max_curvature # Observations - self._FIELD_WIDTH = rospy.get_param('/field/width') - self._FIELD_LENGTH = rospy.get_param('/field/length') - self._GOAL_DEPTH = rospy.get_param('~observation/goal_depth', 0.075) - self._MAX_OBS_VEL = rospy.get_param('~observation/velocity/max_abs', 3.0) - self._MAX_OBS_ANG_VEL = rospy.get_param('~observation/angular_velocity/max_abs', 2*pi) + self._FIELD_WIDTH = self.node.get_parameter('/field/width').get_parameter_value().double_value + self._FIELD_LENGTH = self.node.get_parameter('/field/length').get_parameter_value().double_value + self._GOAL_DEPTH = self.node.get_parameter_or('observation/goal_depth', Parameter(0.075)).get_parameter_value().double_value + self._MAX_OBS_VEL = self.node.get_parameter_or('observation/velocity/max_abs', Parameter(3.0)).get_parameter_value().double_value + self._MAX_OBS_ANG_VEL = self.node.get_parameter_or('observation/angular_velocity/max_abs', Parameter(2*pi)).get_parameter_value().double_value # Learning - self._MAX_TIME = rospy.get_param('~max_episode_time', 30.0) - self._CONSTANT_REWARD = rospy.get_param('~reward/constant', 0.0) - self._BALL_DISTANCE_REWARD = rospy.get_param('~reward/ball_dist_sq', 0.0) - self._GOAL_DISTANCE_REWARD = rospy.get_param('~reward/goal_dist_sq', 0.0) - self._DIRECTION_CHANGE_REWARD = rospy.get_param('~reward/direction_change', 0.0) - if isinstance(rospy.get_param('~reward/win', [100.0]), int): - self._WIN_REWARD = rospy.get_param('~reward/win', [100.0]) + self._MAX_TIME = self.node.get_parameter_or('max_episode_time', Parameter(30.0)).get_parameter_value().double_value + self._CONSTANT_REWARD = self.node.get_parameter_or('reward/constant', Parameter(0.0)).get_parameter_value().double_value + self._BALL_DISTANCE_REWARD = self.node.get_parameter_or('reward/ball_dist_sq', Parameter(0.0)).get_parameter_value().double_value + self._GOAL_DISTANCE_REWARD = self.node.get_parameter_or('reward/goal_dist_sq', Parameter(0.0)).get_parameter_value().double_value + self._DIRECTION_CHANGE_REWARD = self.node.get_parameter_or('reward/direction_change', Parameter(0.0)).get_parameter_value().double_value + + if isinstance(self.node.get_parameter_or('reward/win', Parameter([100.0])).get_parameter_value().double_array_value, int): + self._WIN_REWARD = self.node.get_parameter_or('reward/win', Parameter([100.0])).get_parameter_value().double_array_value else: - if len(rospy.get_param('~reward/win', [100.0])) >= self.env_number: - self._WIN_REWARD = rospy.get_param('~reward/win', [100.0])[0] + if len(self.node.get_parameter_or('reward/win', Parameter([100.0])).get_parameter_value().double_array_value) >= self.env_number: + self._WIN_REWARD = self.node.get_parameter_or('reward/win', Parameter([100.0])).get_parameter_value().double_array_value[0] else: - self._WIN_REWARD = rospy.get_param('~reward/win', [100.0])[self.env_number] - if isinstance(rospy.get_param('~reward/loss', [100.0]), int): - self._LOSS_REWARD = rospy.get_param('~reward/loss', [100.0]) + self._WIN_REWARD = self.node.get_parameter_or('reward/win', Parameter([100.0])).get_parameter_value().double_array_value[self.env_number] + if isinstance(self.node.get_parameter_or('reward/loss', Parameter([100.0])).get_parameter_value().double_array_value, int): + self._LOSS_REWARD = self.node.get_parameter_or('reward/loss', Parameter([100.0])).get_parameter_value().double_array_value else: - if len(rospy.get_param('~reward/loss', [100.0])) >= self.env_number: - self._LOSS_REWARD = rospy.get_param('~reward/loss', [100.0])[0] + if len(self.node.get_parameter_or('reward/loss', Parameter([100.0])).get_parameter_value().double_array_value) >= self.env_number: + self._LOSS_REWARD = self.node.get_parameter_or('reward/loss', Parameter([100.0], type_=8)).get_parameter_value().double_array_value[0] else: - self._LOSS_REWARD = rospy.get_param('~reward/loss', [100.0])[self.env_number] - self._REVERSE_REWARD = rospy.get_param('~reward/reverse', 0.0) - self._WALL_REWARD = rospy.get_param('~reward/walls/value', 0.0) - self._WALL_THRESHOLD = rospy.get_param('~reward/walls/threshold', 0.0) + self._LOSS_REWARD = self.node.get_parameter_or('reward/loss', Parameter([100.0])).get_parameter_value().double_array_value[self.env_number] + self._REVERSE_REWARD = self.node.get_parameter_or('reward/reverse', Parameter(0.0)).get_parameter_value().double_value + self._WALL_REWARD = self.node.get_parameter_or('reward/walls/value', Parameter(0.0)).get_parameter_value().double_value + self._WALL_THRESHOLD = self.node.get_parameter_or('reward/walls/threshold', Parameter(0.0)).get_parameter_value().double_value # Publishers - self._command_pub = rospy.Publisher('cars/car0/command', ControlCommand, queue_size=1) - self._reset_srv = rospy.ServiceProxy('sim_reset', Empty) + self._command_pub = self.node.create_publisher(ControlCommand, 'cars/car0/command', 1) + self._reset_srv = self.node.create_client(Empty, 'sim_reset', qos_profile=1) # State variables self._car_odom = None @@ -113,13 +119,14 @@ def __init__(self, eval=False, launch_file=('rktl_autonomy', 'rocket_league_trai self._prev_vel = None # Subscribers - rospy.Subscriber('cars/car0/odom', Odometry, self._car_odom_cb) - rospy.Subscriber('ball/odom', Odometry, self._ball_odom_cb) - rospy.Subscriber('match_status', MatchStatus, self._score_cb) + self.node.create_subscription(Odometry, 'cars/car0/odom', self._car_odom_cb, qos_profile=1) + self.node.create_subscription(Odometry, 'ball/odom', self._ball_odom_cb, qos_profile=1) + self.node.create_subscription(MatchStatus, 'match_status', self._score_cb, qos_profile=1) # block until environment is ready if not eval: - rospy.wait_for_service('sim_reset') + while not self._reset_srv.wait_for_service(timeout_sec=1.0): + self.node.get_logger().info('service not available, waiting again...') @property def action_space(self): @@ -151,7 +158,8 @@ def observation_space(self): def _reset_env(self): """Reset environment for a new training episode.""" - self._reset_srv.call() + + self._reset_srv.call(Empty) def _reset_self(self): """Reset internally for a new episode.""" @@ -182,7 +190,8 @@ def _get_state(self): # ensure it fits within the observation limits if not self.observation_space.contains(observation): - rospy.logwarn_throttle(5, "Coercing observation into valid bounds") + # rospy.logwarn_throttle(5, "Coercing observation into valid bounds") + self.node.get_logger().warn("Coercing observations into valid bounds") np.clip( observation, self.observation_space.low, @@ -191,8 +200,9 @@ def _get_state(self): # check if time exceeded if self._start_time is None: - self._start_time = rospy.Time.now() - done = (rospy.Time.now() - self._start_time).to_sec() >= self._MAX_TIME + self._start_time = self.node.get_clock().now() + done = (self.node.get_clock().now() - self._start_time) >= self._MAX_TIME + # Determine reward reward = self._CONSTANT_REWARD @@ -203,14 +213,15 @@ def _get_state(self): goal_dist_sq = np.sum(np.square(ball[0:2] - np.array([self._FIELD_LENGTH/2, 0]))) reward += self._GOAL_DISTANCE_REWARD * goal_dist_sq - if self._score != 0: + if self._score is not None and self._score != 0: done = True if self._score > 0: reward += self._WIN_REWARD else: reward += self._LOSS_REWARD - x, y, __, v, __ = self._car_odom + if self._car_odom is not None: + x, y, __, v, __ = self._car_odom if self._prev_vel is None: self._prev_vel = v @@ -234,7 +245,7 @@ def _publish_action(self, action): assert self.action_space.contains(action) msg = ControlCommand() - msg.header.stamp = rospy.Time.now() + msg.header.stamp = self.node.get_clock().now() if ( action == CarActions.FWD or action == CarActions.FWD_RIGHT or diff --git a/rktl_autonomy/scripts/README.md b/src/rktl_autonomy/scripts/README.md similarity index 100% rename from rktl_autonomy/scripts/README.md rename to src/rktl_autonomy/scripts/README.md diff --git a/rktl_autonomy/scripts/eval_rocket_league.py b/src/rktl_autonomy/scripts/eval_rocket_league.py similarity index 96% rename from rktl_autonomy/scripts/eval_rocket_league.py rename to src/rktl_autonomy/scripts/eval_rocket_league.py index 4a0bd09b2..d6c81edcc 100755 --- a/rktl_autonomy/scripts/eval_rocket_league.py +++ b/src/rktl_autonomy/scripts/eval_rocket_league.py @@ -6,7 +6,7 @@ All rights reserved. """ -from rktl_autonomy import RocketLeagueInterface +from rktl_autonomy.rocket_league_interface import RocketLeagueInterface from stable_baselines3 import PPO from stable_baselines3.common.vec_env import SubprocVecEnv from stable_baselines3.common.env_util import make_vec_env diff --git a/rktl_autonomy/scripts/modular_train.py b/src/rktl_autonomy/scripts/modular_train.py similarity index 100% rename from rktl_autonomy/scripts/modular_train.py rename to src/rktl_autonomy/scripts/modular_train.py diff --git a/rktl_autonomy/scripts/train_batch.sh b/src/rktl_autonomy/scripts/train_batch.sh similarity index 100% rename from rktl_autonomy/scripts/train_batch.sh rename to src/rktl_autonomy/scripts/train_batch.sh diff --git a/rktl_autonomy/scripts/train_rocket_league.py b/src/rktl_autonomy/scripts/train_rocket_league.py similarity index 92% rename from rktl_autonomy/scripts/train_rocket_league.py rename to src/rktl_autonomy/scripts/train_rocket_league.py index b8869c442..9314d363f 100755 --- a/rktl_autonomy/scripts/train_rocket_league.py +++ b/src/rktl_autonomy/scripts/train_rocket_league.py @@ -6,7 +6,7 @@ All rights reserved. """ -from rktl_autonomy import RocketLeagueInterface +from rktl_autonomy.rocket_league_interface import RocketLeagueInterface from stable_baselines3 import PPO from stable_baselines3.common.vec_env import SubprocVecEnv from stable_baselines3.common.env_util import make_vec_env @@ -39,7 +39,7 @@ def train(n_envs=24, n_saves=100, n_steps=240000000, env_number=0): # log model weights freq = n_steps / (n_saves * n_envs) - callback = CheckpointCallback(save_freq=freq, save_path=log_dir) + callback = CheckpointCallback(save_freq=int(freq), save_path=log_dir) # run training steps = n_steps diff --git a/rktl_autonomy/scripts/tune_rocket_league.py b/src/rktl_autonomy/scripts/tune_rocket_league.py similarity index 98% rename from rktl_autonomy/scripts/tune_rocket_league.py rename to src/rktl_autonomy/scripts/tune_rocket_league.py index 060fd1f08..6d0e9aafe 100755 --- a/rktl_autonomy/scripts/tune_rocket_league.py +++ b/src/rktl_autonomy/scripts/tune_rocket_league.py @@ -6,7 +6,7 @@ All rights reserved. """ -from rktl_autonomy import RocketLeagueInterface +from rktl_autonomy.rocket_league_interface import RocketLeagueInterface import numpy as np from stable_baselines3 import PPO diff --git a/src/rktl_autonomy/setup.cfg b/src/rktl_autonomy/setup.cfg new file mode 100644 index 000000000..f7f7fbabd --- /dev/null +++ b/src/rktl_autonomy/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rktl_autonomy +[install] +install_scripts=$base/lib/rktl_autonomy diff --git a/src/rktl_autonomy/setup.py b/src/rktl_autonomy/setup.py new file mode 100644 index 000000000..d36b0dce2 --- /dev/null +++ b/src/rktl_autonomy/setup.py @@ -0,0 +1,32 @@ +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = 'rktl_autonomy' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))), + (os.path.join('lib', package_name), glob(os.path.join(package_name, '*.py'))), + (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.yaml'))), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='rtjord', + maintainer_email='rtjord@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'rocket_league_agent = rktl_autonomy.rocket_league_agent:main', + 'plotter = rktl_autonomy.plotter:main' + ], + }, +) diff --git a/src/rktl_autonomy/test/test_copyright.py b/src/rktl_autonomy/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/src/rktl_autonomy/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/rktl_autonomy/test/test_flake8.py b/src/rktl_autonomy/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/src/rktl_autonomy/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/rktl_autonomy/test/test_pep257.py b/src/rktl_autonomy/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/src/rktl_autonomy/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/rktl_autonomy/test/test_step.test b/src/rktl_autonomy/test/test_step.test similarity index 100% rename from rktl_autonomy/test/test_step.test rename to src/rktl_autonomy/test/test_step.test diff --git a/rktl_autonomy/test/test_step_node b/src/rktl_autonomy/test/test_step_node similarity index 74% rename from rktl_autonomy/test/test_step_node rename to src/rktl_autonomy/test/test_step_node index 594fc5249..85a7d424d 100755 --- a/rktl_autonomy/test/test_step_node +++ b/src/rktl_autonomy/test/test_step_node @@ -7,13 +7,15 @@ License: All rights reserved. """ -import unittest, rostest, rospy +# import unittest, rostest, rospy +import unittest, rostest, rclpy +from rclpy.duration import Duration from nav_msgs.msg import Odometry from rktl_msgs.msg import MatchStatus -from std_srvs.srv import Empty, EmptyResponse +from std_srvs.srv import Empty, Empty_Response from rosgraph_msgs.msg import Clock import numpy as np -from rktl_autonomy import RocketLeagueInterface +from rktl_autonomy.rocket_league_interface import RocketLeagueInterface import uuid def easy_odom(x, y, v): @@ -28,12 +30,23 @@ def easy_odom(x, y, v): class TestStep(unittest.TestCase): def test_all(self): # initialize node and interface to code under test - rospy.init_node('rocket_league_agent') - self.car_pub = rospy.Publisher('cars/car0/odom', Odometry, queue_size=1) - self.ball_pub = rospy.Publisher('ball/odom', Odometry, queue_size=1) - self.match_pub = rospy.Publisher('match_status', MatchStatus, queue_size=1) - rospy.Subscriber('/clock', Clock, self.clock_cb) - rospy.Service('sim_reset', Empty, self.reset) + #rospy.init_node('rocket_league_agent') + self.node = rclpy.create_node('rocket_league_agent') + + #self.car_pub = rospy.Publisher('cars/car0/odom', Odometry, queue_size=1) + self.car_pub = self.node.create_publisher(Odometry, 'cars/car0/odom', 1) + + #self.ball_pub = rospy.Publisher('ball/odom', Odometry, queue_size=1) + self.ball_pub =self.node.create_publisher(Odometry, 'ball/odom', 1) + + #self.match_pub = rospy.Publisher('match_status', MatchStatus, queue_size=1) + self.match_pub = self.node.create_publisher(MatchStatus, 'match_status', 1) + + #rospy.Subscriber('/clock', Clock, self.clock_cb) + self.node.create_subscription(Clock, '/clock', self.clock_cb, 10) + + #rospy.Service('sim_reset', Empty, self.reset) + self.node.create_service(Empty, 'sim_reset', callback=self.reset) # member variables used for test self.send_car = None @@ -55,7 +68,8 @@ class TestStep(unittest.TestCase): self.send_ball = easy_odom(-2, -1, -2.5) self.send_match = MatchStatus(status=MatchStatus.ONGOING) self.want_reset = True - max_time = self.last_time + rospy.Duration.from_sec(0.6) + #max_time = self.last_time + rospy.Duration.from_sec(0.6) + max_time = self.last_time + Duration(nanoseconds = 600) obs = env.reset() @@ -67,7 +81,8 @@ class TestStep(unittest.TestCase): self.send_ball = easy_odom(0, 0, 0) self.send_match = MatchStatus(status=MatchStatus.ONGOING) self.want_reset = False - max_time = self.last_time + rospy.Duration.from_sec(0.2) + #max_time = self.last_time + rospy.Duration.from_sec(0.2) + max_time = self.last_time + Duration(nanoseconds=200) obs, reward, done, __ = env.step(env.action_space.sample()) @@ -81,7 +96,8 @@ class TestStep(unittest.TestCase): self.send_ball = easy_odom(1, 0, 0) self.send_match = MatchStatus(status=MatchStatus.VICTORY_TEAM_A) self.want_reset = False - max_time = self.last_time + rospy.Duration.from_sec(0.2) + #max_time = self.last_time + rospy.Duration.from_sec(0.2) + max_time = self.last_time + Duration(nanoseconds=200) obs, reward, done, __ = env.step(env.action_space.sample()) @@ -95,7 +111,8 @@ class TestStep(unittest.TestCase): self.send_ball = easy_odom(0, 0, 0) self.send_match = MatchStatus(status=MatchStatus.ONGOING) self.want_reset = False - max_time = self.last_time + rospy.Duration.from_sec(0.2) + #max_time = self.last_time + rospy.Duration.from_sec(0.2) + max_time = self.last_time + Duration(nanoseconds = 200) obs, reward, done, __ = env.step(env.action_space.sample()) @@ -109,7 +126,8 @@ class TestStep(unittest.TestCase): self.send_ball = easy_odom(0, 0, 0) self.send_match = MatchStatus(status=MatchStatus.VICTORY_TEAM_A) self.want_reset = False - max_time = self.last_time + rospy.Duration.from_sec(0.2) + #max_time = self.last_time + rospy.Duration.from_sec(0.2) + max_time = self.last_time + Duration(nanoseconds = 200) obs, reward, done, __ = env.step(env.action_space.sample()) @@ -123,7 +141,8 @@ class TestStep(unittest.TestCase): self.send_ball = easy_odom(-2.3, 1.0, 0) self.send_match = MatchStatus(status=MatchStatus.VICTORY_TEAM_B) self.want_reset = False - max_time = self.last_time + rospy.Duration.from_sec(0.2) + #max_time = self.last_time + rospy.Duration.from_sec(0.2) + max_time = self.last_time + Duration(nanoseconds=200) obs, reward, done, __ = env.step(env.action_space.sample()) @@ -145,7 +164,7 @@ class TestStep(unittest.TestCase): def reset(self, __): """Do nothing.""" self.assertTrue(self.want_reset, msg='env reset called when unexpected') - return EmptyResponse() + return Empty_Response() if __name__ == '__main__': rostest.rosrun('rktl_autonomy', 'test_rktl_rewards', TestStep) \ No newline at end of file diff --git a/src/rktl_control/config/controller.yaml b/src/rktl_control/config/controller.yaml new file mode 100644 index 000000000..e25c2e78f --- /dev/null +++ b/src/rktl_control/config/controller.yaml @@ -0,0 +1,35 @@ +/**: + ros__parameters: + # controller type: 'lead_lag', 'pid', or 'none' + controller_type: 'none' + # parameters for all controllers + limits: + throttle: + min: -1.5 + max: 1.5 + # parameters for none (open loop) + open_loop: + publish_early: true + # parameters for lead_lag (stable with 0.2 sec delay) + # lead: + # gain: 2.2696 + # alpha: -0.6146 + # beta: -0.7130 + # lag: + # alpha: -0.9608 + # beta: -0.9951 + # alternate lead_lag (with predictive control) + lead: + gain: 3.9229 + alpha: -0.2584 + beta: -0.9231 + lag: + alpha: -0.573 + beta: -0.9786 + # parameters for pid + pid: + kp: 1.0 + ki: 0.0 + kd: 0.0 + anti_windup: 0.0 + deadband: 0.0 \ No newline at end of file diff --git a/rktl_control/config/esc_settings.dat b/src/rktl_control/config/esc_settings.dat similarity index 100% rename from rktl_control/config/esc_settings.dat rename to src/rktl_control/config/esc_settings.dat diff --git a/src/rktl_control/config/hardware_interface.yaml b/src/rktl_control/config/hardware_interface.yaml new file mode 100644 index 000000000..a573cf462 --- /dev/null +++ b/src/rktl_control/config/hardware_interface.yaml @@ -0,0 +1,36 @@ +/**: + ros__parameters: + # hardware configuration + port: /dev/ttyACM0 + + # servo adjustments (uS) + car0: + throttle_throw: 90 + steering_left: 950 + steering_center: 1450 + steering_right: 1850 + car1: + throttle_throw: 90 + steering_left: 950 + steering_center: 1450 + steering_right: 1850 + car2: + throttle_throw: 90 + steering_left: 950 + steering_center: 1450 + steering_right: 1850 + car3: + throttle_throw: 90 + steering_left: 950 + steering_center: 1450 + steering_right: 1850 + car4: + throttle_throw: 90 + steering_left: 950 + steering_center: 1450 + steering_right: 1850 + car5: + throttle_throw: 90 + steering_left: 950 + steering_center: 1450 + steering_right: 1850 \ No newline at end of file diff --git a/src/rktl_control/config/mean_odom_filter.yaml b/src/rktl_control/config/mean_odom_filter.yaml new file mode 100644 index 000000000..31e17e4cc --- /dev/null +++ b/src/rktl_control/config/mean_odom_filter.yaml @@ -0,0 +1,8 @@ +/**: + ros__parameters: + delay: + compensate: false + duration: 0.15 + buffer_size: + position: 3 + velocity: 3 \ No newline at end of file diff --git a/src/rktl_control/config/particle_odom_filter.yaml b/src/rktl_control/config/particle_odom_filter.yaml new file mode 100644 index 000000000..008791683 --- /dev/null +++ b/src/rktl_control/config/particle_odom_filter.yaml @@ -0,0 +1,32 @@ +/**: + ros__parameters: + rate: 10.0 + supersampling: 2 + publish_particles: true + num_particles: 100 + resample_proportion: 0.05 + boundary_check: true + delay: + compensate: true + duration: 0.1 + allowable_latency: 1.25 + open_loop_limit: 10 + measurement_error: + location: 0.1 + orientation: 0.2 + generator_noise: + location: 0.5 + orientation: 0.75 + velocity: 0.1 + steering_angle: 0.05 + efforts: + enable: true + buffer_size: 15 + throttle: + noise: 0.2 + max: 1.0 + min: -1.0 + steering: + noise: 0.2 + max: 1.0 + min: -1.0 \ No newline at end of file diff --git a/src/rktl_control/config/pose_synchronizer.yaml b/src/rktl_control/config/pose_synchronizer.yaml new file mode 100644 index 000000000..292cacbd6 --- /dev/null +++ b/src/rktl_control/config/pose_synchronizer.yaml @@ -0,0 +1,25 @@ +/**: + ros__parameters: + # rate of the whole perception system (Hz) + rate: 10.0 + + # delay in seconds. Lower is better, but AprilTags needs to keep up + delay: 0.1 + + # should debug info on latency be published? + publish_latency: true + + # buffer size for each topic. + # 12 potential detections per update (4 x 30 hz cameras) + # delay of ~ 2 updates + buffer_size: 30 + + # topics to be synchronized + topics: + - cars/car0/pose + - ball/pose + + # if Z in the message corresponds to a confidence weight + use_weights: + - false + - true \ No newline at end of file diff --git a/rktl_control/config/velocity_controller.yml b/src/rktl_control/config/velocity_controller.yml similarity index 100% rename from rktl_control/config/velocity_controller.yml rename to src/rktl_control/config/velocity_controller.yml diff --git a/rktl_control/firmware/README.md b/src/rktl_control/firmware/README.md similarity index 100% rename from rktl_control/firmware/README.md rename to src/rktl_control/firmware/README.md diff --git a/rktl_control/firmware/hardware_interface/hardware_interface.ino b/src/rktl_control/firmware/hardware_interface/hardware_interface.ino similarity index 100% rename from rktl_control/firmware/hardware_interface/hardware_interface.ino rename to src/rktl_control/firmware/hardware_interface/hardware_interface.ino diff --git a/rktl_control/launch/README.md b/src/rktl_control/launch/README.md similarity index 100% rename from rktl_control/launch/README.md rename to src/rktl_control/launch/README.md diff --git a/rktl_control/launch/ball.launch b/src/rktl_control/launch/ball.launch similarity index 100% rename from rktl_control/launch/ball.launch rename to src/rktl_control/launch/ball.launch diff --git a/src/rktl_control/launch/ball.launch.py b/src/rktl_control/launch/ball.launch.py new file mode 100644 index 000000000..1d4e2fc23 --- /dev/null +++ b/src/rktl_control/launch/ball.launch.py @@ -0,0 +1,25 @@ +import launch +import launch_ros.actions +from launch.substitutions import PathJoinSubstitution +from launch_ros.substitutions import FindPackageShare + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch_ros.actions.Node( + namespace='ball', + package='rktl_control', + executable='mean_odom_filter', + name='mean_odom_filter', + output='screen', + parameters=[ + { + PathJoinSubstitution([FindPackageShare('rktl_control'), 'config', 'mean_odom_filter.yaml']) + } + ] + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() \ No newline at end of file diff --git a/rktl_control/launch/car.launch b/src/rktl_control/launch/car.launch similarity index 100% rename from rktl_control/launch/car.launch rename to src/rktl_control/launch/car.launch diff --git a/src/rktl_control/launch/car.launch.py b/src/rktl_control/launch/car.launch.py new file mode 100644 index 000000000..c14293a44 --- /dev/null +++ b/src/rktl_control/launch/car.launch.py @@ -0,0 +1,76 @@ +import launch +import launch_ros.actions +from launch.substitutions import * +from launch.conditions import IfCondition + +from launch_ros.substitutions import FindPackageShare + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='car_name', + default_value='car0' + ), + launch.actions.DeclareLaunchArgument( + name='use_particle_filter', + default_value='true' + ), + launch.actions.GroupAction( + actions=[ + #launch_ros.actions.PushRosNamespace("cars/" + launch.substitutions.LaunchConfiguration("car_name")), + + launch_ros.actions.Node( + package='rktl_control', + executable='particle_odom_filter', + name='particle_odom_filter', + output='screen', + condition=IfCondition( + EqualsSubstitution(LaunchConfiguration('use_particle_filter'), 'true') + ), + parameters=[ + { + PathJoinSubstitution([FindPackageShare('rktl_control'), '/config/particle_odom_filter.yaml']) + }, + { + 'frame_ids/body': LaunchConfiguration('car_name') + } + ] + ), + + launch_ros.actions.Node( + package='rktl_control', + executable='mean_odom_filter', + name='mean_odom_filter', + output='screen', + condition=IfCondition( + EqualsSubstitution(LaunchConfiguration('use_particle_filter'), 'true') + ), + parameters=[ + { + PathJoinSubstitution([FindPackageShare('rktl_control'), '/config/mean_odom_filter.yaml']) + }, + { + 'frame_ids/body': LaunchConfiguration('car_name') + } + ] + ), + + launch_ros.actions.Node( + package='rktl_control', + executable='controller', + name='controller', + output='screen', + parameters=[ + { + PathJoinSubstitution([FindPackageShare('rktl_control'), '/config/controller.yaml']) + } + ] + ) + ] + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() \ No newline at end of file diff --git a/rktl_control/launch/hardware_interface.launch b/src/rktl_control/launch/hardware_interface.launch similarity index 100% rename from rktl_control/launch/hardware_interface.launch rename to src/rktl_control/launch/hardware_interface.launch diff --git a/src/rktl_control/launch/hardware_interface.launch.py b/src/rktl_control/launch/hardware_interface.launch.py new file mode 100644 index 000000000..65c94fb2e --- /dev/null +++ b/src/rktl_control/launch/hardware_interface.launch.py @@ -0,0 +1,23 @@ +import launch +import launch_ros.actions +from ament_index_python.packages import get_package_share_directory + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch_ros.actions.Node( + package='rosserial_arduino', + executable='serial_node.py', + name='hardware_interface', + parameters=[ + { + launch.substitutions.PathJoinSubstitution([launch_ros.substitutions.FindPackageShare('rktl_control'), 'config', 'hardware_interface.yaml']) + } + ] + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() \ No newline at end of file diff --git a/rktl_control/launch/keyboard_control.launch b/src/rktl_control/launch/keyboard_control.launch similarity index 100% rename from rktl_control/launch/keyboard_control.launch rename to src/rktl_control/launch/keyboard_control.launch diff --git a/src/rktl_control/launch/keyboard_control.launch.py b/src/rktl_control/launch/keyboard_control.launch.py new file mode 100644 index 000000000..cd801b30c --- /dev/null +++ b/src/rktl_control/launch/keyboard_control.launch.py @@ -0,0 +1,23 @@ +import launch +import launch_ros.actions + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='car_name', + default_value='car0' + ), + launch_ros.actions.Node( + namespace=launch.substitutions.PathJoinSubstitution(['cars', launch_ros.substitutions.FindPackageShare('car_name')]), + package='rktl_control', + executable='keyboard_interface', + name='keyboard_interface', + output='screen' + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() \ No newline at end of file diff --git a/rktl_control/launch/xbox_control.launch b/src/rktl_control/launch/xbox_control.launch similarity index 100% rename from rktl_control/launch/xbox_control.launch rename to src/rktl_control/launch/xbox_control.launch diff --git a/src/rktl_control/launch/xbox_control.launch.py b/src/rktl_control/launch/xbox_control.launch.py new file mode 100644 index 000000000..b0373a1cb --- /dev/null +++ b/src/rktl_control/launch/xbox_control.launch.py @@ -0,0 +1,72 @@ +import launch +import launch_ros.actions +from launch.substitutions import PathJoinSubstitution +from ament_index_python.packages import get_package_share_directory + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='device', + default_value='/dev/input/js0' + ), + launch.actions.DeclareLaunchArgument( + name='car_name', + default_value='car0' + ), + launch.actions.DeclareLaunchArgument( + name='delay', + default_value='0.1' + ), + launch.actions.GroupAction( + actions=[ + launch_ros.actions.PushRosNamespace("cars" + get_package_share_directory('car_name')), + + launch_ros.actions.Node( + package='joy', + executable='joy_node', + name='joy_node', + output='screen', + parameters=[ + { + 'dev': launch.substitutions.LaunchConfiguration('device') + }, + { + 'default_trig_val': 'true' + } + ] + ), + launch_ros.actions.Node( + package='rktl_control', + executable='xbox_interface', + name='xbox_interface', + output='screen', + parameters=[ + { + 'base_throttle': '0.75' + }, + { + 'boost_throttle': '1.25' + }, + { + 'cooldown_ratio': '3' + }, + { + 'max_boost': '2' + } + ], + actions=[ + launch_ros.actions.SetRemap( + src="joy", + dst="joy)mux", + ) + ] + ) + ] + ) + + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() \ No newline at end of file diff --git a/src/rktl_control/package.xml b/src/rktl_control/package.xml new file mode 100644 index 000000000..1f229aea9 --- /dev/null +++ b/src/rktl_control/package.xml @@ -0,0 +1,29 @@ + + + + rktl_control + 0.0.0 + TODO: Package description + DinoSage + TODO: License declaration + + + rclpy + std_msgs + nav_msgs + geometry_msgs + rosserial_arduino + joy + topic_tools + roblib + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + ros2launch + + + ament_python + + diff --git a/src/rktl_control/resource/rktl_control b/src/rktl_control/resource/rktl_control new file mode 100644 index 000000000..e69de29bb diff --git a/rktl_control/nodes/README.md b/src/rktl_control/rktl_control/README.md similarity index 100% rename from rktl_control/nodes/README.md rename to src/rktl_control/rktl_control/README.md diff --git a/src/rktl_control/rktl_control/__init__.py b/src/rktl_control/rktl_control/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rktl_control/nodes/controller b/src/rktl_control/rktl_control/controller.py old mode 100755 new mode 100644 similarity index 66% rename from rktl_control/nodes/controller rename to src/rktl_control/rktl_control/controller.py index a8d9053cb..3fe47a33a --- a/rktl_control/nodes/controller +++ b/src/rktl_control/rktl_control/controller.py @@ -9,7 +9,8 @@ All rights reserved. """ -import rospy +import rclpy +import sys from nav_msgs.msg import Odometry from rktl_msgs.msg import ControlCommand, ControlEffort from math import atan, cos, sin @@ -92,54 +93,57 @@ class Controller(object): """Controller for car.""" def __init__(self): - rospy.init_node('controller') + rclpy.init(args=sys.argv) + self.node = rclpy.create_node('controller') # Constants - self.MAX_SPEED = rospy.get_param('/cars/throttle/max_speed') - self.STEERING_THROW = rospy.get_param('/cars/steering/max_throw') - self.BODY_LENGTH = rospy.get_param('/cars/length') + self.MAX_SPEED = self.node.declare_parameter('/cars/throttle/max_speed', 2.3).value + self.STEERING_THROW = self.node.declare_parameter('/cars/steering/max_throw', 0.1826).value + self.BODY_LENGTH = self.node.declare_parameter('/cars/length', 0.12).value - self.MIN_THROTTLE_EFFORT = rospy.get_param('~limits/throttle/min', -1.0) - self.MAX_THROTTLE_EFFORT = rospy.get_param('~limits/throttle/max', 1.0) - self.MIN_STEERING_EFFORT = rospy.get_param('~limits/steering/min', -1.0) - self.MAX_STEERING_EFFORT = rospy.get_param('~limits/steering/max', 1.0) + self.MIN_THROTTLE_EFFORT = self.node.declare_parameter('~limits/throttle/min', -1.0).value + self.MAX_THROTTLE_EFFORT = self.node.declare_parameter('~limits/throttle/max', 1.0).value + self.MIN_STEERING_EFFORT = self.node.declare_parameter('~limits/steering/min', -1.0).value + self.MAX_STEERING_EFFORT = self.node.declare_parameter('~limits/steering/max', 1.0).value - self.PUBLISH_EARLY = rospy.get_param('~open_loop/publish_early', True) + self.PUBLISH_EARLY = self.node.declare_parameter('~open_loop/publish_early', True).value + + self.CONTROLLER_TYPE = self.node.declare_parameter('~controller_type', 'none').value # Make closed loop velocity controller - if rospy.get_param('~controller_type') == 'lead_lag': + if self.CONTROLLER_TYPE == 'lead_lag': self.controller = LeadLagController( - rospy.get_param('~lead/gain'), - rospy.get_param('~lead/alpha'), - rospy.get_param('~lead/beta'), - rospy.get_param('~lag/alpha'), - rospy.get_param('~lag/beta')) - elif rospy.get_param('~controller_type') == 'pid': + self.node.declare_parameter('~lead/gain').value, + self.node.declare_parameter('~lead/alpha').value, + self.node.declare_parameter('~lead/beta').value, + self.node.declare_parameter('~lag/alpha').value, + self.node.declare_parameter('~lag/beta').value) + elif self.CONTROLLER_TYPE == 'pid': self.controller = PIDController( - rospy.get_param('~pid/kp'), - rospy.get_param('~pid/ki'), - rospy.get_param('~pid/kd'), - rospy.get_param('~pid/anti_windup'), - rospy.get_param('~pid/deadband'), - 1.0 / rospy.get_param('~rate', 10.0)) - elif rospy.get_param('~controller_type') == 'none': + self.node.declare_parameter('~pid/kp').value, + self.node.declare_parameter('~pid/ki').value, + self.node.declare_parameter('~pid/kd').value, + self.node.declare_parameter('~pid/anti_windup').value, + self.node.declare_parameter('~pid/deadband').value, + 1.0 / self.node.declare_parameter('~rate', 10.0).value) + elif self.CONTROLLER_TYPE == 'none': self.controller = None else: - raise NotImplementedError(f"unrecognized controller type: {rospy.get_param('controller_type')}") + raise NotImplementedError(f"unrecognized controller type: {self.node.declare_parameter('controller_type').value}") # State variables self.vr_ref = None self.psi_ref = None # Publishers - self.pub = rospy.Publisher('effort', ControlEffort, queue_size=1) + self.pub = self.node.create_publisher(ControlEffort, 'effort', 1) # Subscribers - rospy.Subscriber('command', ControlCommand, self.command_cb) - rospy.Subscriber('odom', Odometry, self.odom_cb) + self.node.create_subscription(ControlCommand, 'command', self.command_cb, 1) + self.node.create_subscription(Odometry, 'odom', self.odom_cb, 1) # trust that odom_cb runs at proper rate - rospy.spin() + rclpy.spin(self.node) def command_cb(self, cmd_msg): """Callback for command messages for car.""" @@ -178,11 +182,13 @@ def odom_cb(self, odom_msg): # publish actuator efforts msg = ControlEffort() - msg.header.stamp = rospy.Time.now() + msg.header.stamp = self.node.get_clock().now().to_msg() msg.throttle = throttle_effort msg.steering = steering_effort self.pub.publish(msg) +def main(): + Controller() if __name__ == "__main__": - Controller() + main() \ No newline at end of file diff --git a/rktl_control/nodes/keyboard_interface b/src/rktl_control/rktl_control/keyboard_interface.py old mode 100755 new mode 100644 similarity index 84% rename from rktl_control/nodes/keyboard_interface rename to src/rktl_control/rktl_control/keyboard_interface.py index 046daf46c..dcf3d10b9 --- a/rktl_control/nodes/keyboard_interface +++ b/src/rktl_control/rktl_control/keyboard_interface.py @@ -9,7 +9,8 @@ All rights reserved. """ -import rospy +import rclpy +import sys from rktl_msgs.msg import ControlEffort from std_srvs.srv import Empty @@ -21,7 +22,7 @@ def main(win): win.nodelay(True) throttle_effort = 0 steering_effort = 0 - while not rospy.is_shutdown(): + while rclpy.ok(): key = "" try: key = win.getkey() @@ -47,7 +48,7 @@ def main(win): steering_effort = max(min(steering_effort, 1.0), -1.0) msg = ControlEffort() - msg.header.stamp = rospy.Time.now() + msg.header.stamp = node.get_clock().now().to_msg() msg.throttle = throttle_effort msg.steering = steering_effort effort_pub.publish(msg) @@ -62,9 +63,15 @@ def main(win): win.addstr("q to quit") sleep(0.01) -if __name__ == "__main__": - rospy.init_node('keyboard') - effort_pub = rospy.Publisher('effort', ControlEffort, queue_size=1) - reset_srv = rospy.ServiceProxy('/sim_reset', Empty) +def main(): + rclpy.init(args=sys.argv) + node = rclpy.create_node('keyboard') + + effort_pub = node.create_publisher(ControlEffort, 'effort') + reset_srv = node.create_client(Empty, '/sim_reset') wrapper(main) + +if __name__ == "__main__": + main() + diff --git a/rktl_control/nodes/mean_odom_filter b/src/rktl_control/rktl_control/mean_odom_filter.py old mode 100755 new mode 100644 similarity index 73% rename from rktl_control/nodes/mean_odom_filter rename to src/rktl_control/rktl_control/mean_odom_filter.py index 8d30256f5..14a051a43 --- a/rktl_control/nodes/mean_odom_filter +++ b/src/rktl_control/rktl_control/mean_odom_filter.py @@ -10,13 +10,14 @@ """ # ROS -import rospy +import rclpy +import sys from nav_msgs.msg import Odometry from geometry_msgs.msg import PoseWithCovarianceStamped -from tf.transformations import euler_from_quaternion, quaternion_from_euler +from rktl_dependencies.transformations import euler_from_quaternion, quaternion_from_euler from collections import deque -from angles import shortest_angular_distance as sad +from rktl_dependencies.angles import shortest_angular_distance as sad from math import sin, cos, atan2 @@ -24,27 +25,28 @@ class MeanOdomFilter(object): """Class to smooth pose estimations and predict velocity.""" def __init__(self): - rospy.init_node('mean_odom_filter') + rclpy.init(args=sys.argv) + self.node = rclpy.create_node('mean_odom_filter') # constants - self.MAP_FRAME = rospy.get_param('~frame_ids/map', 'map') - self.BODY_FRAME = rospy.get_param('~frame_ids/body', 'base_link') + self.MAP_FRAME = self.node.declare_parameter('~frame_ids/map', 'map') + self.BODY_FRAME = self.node.declare_parameter('~frame_ids/body', 'base_link') - self.POS_COUNT = rospy.get_param('~buffer_size/position', 3) - self.VEL_COUNT = rospy.get_param('~buffer_size/velocity', 3) + self.POS_COUNT = self.node.declare_parameter('~buffer_size/position', 3) + self.VEL_COUNT = self.node.declare_parameter('~buffer_size/velocity', 3) - self.PREDICT_ENABLE = rospy.get_param('~delay/compensate', False) - self.PREDICT_TIME = rospy.get_param('~delay/duration', 0.0) + self.PREDICT_ENABLE = self.node.declare_parameter('~delay/compensate', False) + self.PREDICT_TIME = self.node.declare_parameter('~delay/duration', 0.0) # variables - self.buffer = deque(maxlen=max(self.POS_COUNT, self.VEL_COUNT+1)) + self.buffer = deque(maxlen=max(self.POS_COUNT.get_parameter_value().integer_value, self.VEL_COUNT.get_parameter_value().integer_value+1)) # pubs / subs - self.odom_pub = rospy.Publisher('odom', Odometry, queue_size=1) - rospy.Subscriber('pose_sync', PoseWithCovarianceStamped, self.pose_cb) + self.odom_pub = self.node.create_publisher(Odometry, 'odom', qos_profile=1) + self.node.create_subscription(PoseWithCovarianceStamped, 'pose_sync', self.pose_cb, qos_profile=1) # main loop - rospy.spin() + rclpy.spin(self.node) def pose_cb(self, pose_msg): """Callback for new poses.""" @@ -78,9 +80,9 @@ def pose_cb(self, pose_msg): # wildly extrapolate to a future time if requested if self.PREDICT_ENABLE: # perform a very simple prediction of location / orientation - x += vx * self.PREDICT_TIME - y += vy * self.PREDICT_TIME - yaw += omega * self.PREDICT_TIME + x += vx * self.PREDICT_TIME.get_parameter_value().double_value + y += vy * self.PREDICT_TIME.get_parameter_value().double_value + yaw += omega * self.PREDICT_TIME.get_parameter_value().double_value # publish message odom_msg = Odometry() @@ -147,6 +149,8 @@ def sample_velocity(self): return (avg_vx, avg_vy, avg_omega) +def main(): + MeanOdomFilter() if __name__ == "__main__": - MeanOdomFilter() + main() diff --git a/rktl_control/nodes/particle_odom_filter b/src/rktl_control/rktl_control/particle_odom_filter.py old mode 100755 new mode 100644 similarity index 79% rename from rktl_control/nodes/particle_odom_filter rename to src/rktl_control/rktl_control/particle_odom_filter.py index dd1434f83..34221f299 --- a/rktl_control/nodes/particle_odom_filter +++ b/src/rktl_control/rktl_control/particle_odom_filter.py @@ -9,14 +9,15 @@ """ # ROS -import rospy +import rclpy +import sys from nav_msgs.msg import Odometry from geometry_msgs.msg import PoseWithCovarianceStamped, PoseArray, Pose from rktl_msgs.msg import ControlEffort -from tf.transformations import euler_from_quaternion, quaternion_from_euler +from rktl_dependencies.transformations import euler_from_quaternion, quaternion_from_euler import numpy as np -from pfilter import ParticleFilter +from rktl_dependencies.pfilter import ParticleFilter from collections import deque from threading import Lock from math import sin, cos, tan, atan, sqrt, pi, exp @@ -26,70 +27,72 @@ class ParticleOdomFilter(object): """Class to estimate pose and velocity using Particle filter.""" def __init__(self): - rospy.init_node('particle_odom_filter') + rclpy.init(args=sys.argv) + self.node = rclpy.create_node('particle_odom_filter') # physical constants (global) - self.FIELD_WIDTH = rospy.get_param('/field/width') - self.FIELD_HEIGHT = rospy.get_param('/field/length') - self.CAR_LENGTH = rospy.get_param('/cars/length') - self.MAX_SPEED = rospy.get_param('/cars/throttle/max_speed') - self.THROTTLE_TAU = rospy.get_param('/cars/throttle/tau') - self.STEERING_THROW = rospy.get_param('/cars/steering/max_throw') - self.STEERING_RATE = rospy.get_param('/cars/steering/rate') + self.FIELD_WIDTH = self.node.declare_parameter('/field/width', 3).value + self.FIELD_HEIGHT = self.node.declare_parameter('/field/length', 4.25).value + self.CAR_LENGTH = self.node.declare_parameter('/cars/length', 0.12).value + self.MAX_SPEED = self.node.declare_parameter('/cars/throttle/max_speed', 2.3).value + self.THROTTLE_TAU = self.node.declare_parameter('/cars/throttle/tau', 0.2).value + self.STEERING_THROW = self.node.declare_parameter('/cars/steering/max_throw', 0.1826).value + self.STEERING_RATE = self.node.declare_parameter('/cars/steering/rate', 0.9128).value # node configuration - self.MAP_FRAME = rospy.get_param('~frame_ids/map', 'map') - self.BODY_FRAME = rospy.get_param('~frame_ids/body', 'base_link') - self.DELTA_T = rospy.Duration(1.0/rospy.get_param('~rate', 10.0)) - self.SUPERSAMPLING = rospy.get_param('~supersampling', 1) - self.PUB_PARTICLES = rospy.get_param('~publish_particles', False) - self.WATCHDOG_DELTA_T = self.DELTA_T * \ - rospy.get_param('~allowable_latency', 1.2) - self.OPEN_LOOP_LIMIT = rospy.get_param('~open_loop_limit', 10) + self.MAP_FRAME = self.node.declare_parameter('~frame_ids/map', 'map').value + self.BODY_FRAME = self.node.declare_parameter('~frame_ids/body', 'base_link').value + self.TEMP_RATE = self.node.declare_parameter('~rate', 10.0).value + self.DELTA_T = rclpy.duration.Duration(nanoseconds=1.0/self.TEMP_RATE) + self.SUPERSAMPLING = self.node.declare_parameter('~supersampling', 1).value + self.PUB_PARTICLES = self.node.declare_parameter('~publish_particles', False).value + self.WATCHDOG_DELTA_T = self.DELTA_T.nanoseconds * \ + self.node.declare_parameter('~allowable_latency', 1.2).value + self.OPEN_LOOP_LIMIT = self.node.declare_parameter('~open_loop_limit', 10).value # should the filter compensate for delay by trying to predict the future? - self.PREDICT_ENABLE = rospy.get_param('~delay/compensate', False) - self.PREDICT_TIME = rospy.Duration( - rospy.get_param('~delay/duration', 0.0)) + self.PREDICT_ENABLE = self.node.declare_parameter('~delay/compensate', False).value + self.PREDICT_TIME = rclpy.time.Time(seconds= + self.node.declare_parameter('~delay/duration', 0.0).value) # should the filter weigh particles based on a boundary check? - boundary_check = rospy.get_param('~boundary_check', False) + boundary_check = self.node.declare_parameter('~boundary_check', False).value # filter tuning options - num_particles = rospy.get_param('~num_particles', 1000) - resample_proportion = rospy.get_param('~resample_proportion', 0.1) + num_particles = self.node.declare_parameter('~num_particles', 1000).value + resample_proportion = self.node.declare_parameter('~resample_proportion', 0.1).value # standard deviation of incoming measurements used to assign particle weights - self.MEAS_LOC_STD_DEV = rospy.get_param( - '~measurement_error/location', 0.05) - self.MEAS_DIR_STD_DEV = rospy.get_param( - '~measurement_error/orientation', np.deg2rad(5)) + self.MEAS_LOC_STD_DEV = self.node.declare_parameter( + '~measurement_error/location', 0.05).value + self.MEAS_DIR_STD_DEV = self.node.declare_parameter( + '~measurement_error/orientation', np.deg2rad(5)).value # standard deviation when generating random states based off current guess - self.GEN_LOC_STD_DEV = rospy.get_param( - '~generator_noise/location', 0.05) - self.GEN_DIR_STD_DEV = rospy.get_param( - '~generator_noise/orientation', 0.05) - self.GEN_VEL_STD_DEV = rospy.get_param( - '~generator_noise/velocity', 0.05) - self.GEN_PSI_STD_DEV = rospy.get_param( - '~generator_noise/steering_angle', np.deg2rad(1)) + self.GEN_LOC_STD_DEV = self.node.declare_parameter( + '~generator_noise/location', 0.05).value + self.GEN_DIR_STD_DEV = self.node.declare_parameter( + '~generator_noise/orientation', 0.05).value + self.GEN_VEL_STD_DEV = self.node.declare_parameter( + '~generator_noise/velocity', 0.05).value + self.GEN_PSI_STD_DEV = self.node.declare_parameter( + '~generator_noise/steering_angle', np.deg2rad(1)).value # should the filter use historic effort data to get a more accurate idea of where the car is? - use_efforts = rospy.get_param('~efforts/enable', False) - effort_buffer_size = rospy.get_param('~efforts/buffer_size', 0) + use_efforts = self.node.declare_parameter('~efforts/enable', False).value + effort_buffer_size = self.node.declare_parameter('~efforts/buffer_size', 0).value # standard deviation to add noise to effort when effort is known and enabled - self.THR_EFFORT_STD_DEV = rospy.get_param( - '~efforts/throttle/noise', 0.05) - self.STR_EFFORT_STD_DEV = rospy.get_param( - '~efforts/steering/noise', 0.05) + self.THR_EFFORT_STD_DEV = self.node.declare_parameter( + '~efforts/throttle/noise', 0.05).value + self.STR_EFFORT_STD_DEV = self.node.declare_parameter( + '~efforts/steering/noise', 0.05).value # max and min for uniform effort distribution when effort is not known or disabled - self.MAX_THROTTLE = rospy.get_param('~efforts/throttle/max', 1.0) - self.MIN_THROTTLE = rospy.get_param('~efforts/throttle/min', -1.0) - self.MAX_STEERING = rospy.get_param('~efforts/steering/max', 1.0) - self.MIN_STEERING = rospy.get_param('~efforts/steering/min', -1.0) + self.MAX_THROTTLE = self.node.declare_parameter('~efforts/throttle/max', 1.0).value + self.MIN_THROTTLE = self.node.declare_parameter('~efforts/throttle/min', -1.0).value + self.MAX_STEERING = self.node.declare_parameter('~efforts/steering/max', 1.0).value + self.MIN_STEERING = self.node.declare_parameter('~efforts/steering/min', -1.0).value # variables self.effort_buffer = deque(maxlen=effort_buffer_size) @@ -111,16 +114,16 @@ def __init__(self): self.filter.mean_state = None # pubs / subs - self.odom_pub = rospy.Publisher('odom', Odometry, queue_size=1) - rospy.Subscriber('pose_sync', PoseWithCovarianceStamped, self.pose_cb) + self.odom_pub = self.node.create_publisher(Odometry, 'odom', 1) + self.node.create_subscription(PoseWithCovarianceStamped, 'pose_sync', self.pose_cb, 1) if use_efforts: - rospy.Subscriber('effort', ControlEffort, self.effort_cb) + self.node.create_subscription(ControlEffort, 'effort', self.effort_cb, 1) if self.PUB_PARTICLES: - self.cloud_pub = rospy.Publisher( - 'odom_particles', PoseArray, queue_size=1) + self.cloud_pub = self.node.create_publisher(PoseArray, + 'odom_particles', 1) # main loop - rospy.spin() + rclpy.spin(self.node) def effort_cb(self, effort_msg): """Callback for new efforts.""" @@ -134,11 +137,11 @@ def watchdog_cb(self, timer_event): return assert self.current_time is not None - rospy.loginfo( + self.node.get_logger().info( "Dropped pose message detected, extrapolating with no measurement") # set a new watchdog - self.watchdog = rospy.Timer( + self.watchdog = self.node.create_timer( self.WATCHDOG_DELTA_T, self.watchdog_cb, True) # get the exact time since the watchdog was set @@ -152,7 +155,7 @@ def watchdog_cb(self, timer_event): self.publish_odom() self.open_loop_count += 1 else: - rospy.logerr("Halting filter due to too many lost poses") + self.node.get_logger().error("Halting filter due to too many lost poses") self.filter_reset() def pose_cb(self, pose_msg): @@ -174,7 +177,7 @@ def pose_cb(self, pose_msg): # reset the watchdog if self.watchdog is not None: self.watchdog.shutdown() - self.watchdog = rospy.Timer( + self.watchdog = self.node.create_timer( self.WATCHDOG_DELTA_T, self.watchdog_cb, True) # initialize the current time (as one update ago) if needed @@ -188,7 +191,7 @@ def pose_cb(self, pose_msg): self.publish_odom() self.open_loop_count = 0 else: - rospy.logwarn( + self.node.get_logger().warn( "Incoming measurement out of sync. Possible watchdog runaway. Resetting filter. Consider turning down supersampling") self.filter_reset() @@ -215,7 +218,7 @@ def publish_odom(self): # validate output if np.isnan(state).any(): - rospy.logwarn( + self.node.get_logger().warn( "Performing automatic filter reset due to NaN output") self.filter_reset() state = np.sum( @@ -465,6 +468,8 @@ def particle_init(self, num_particles): random_internal), axis=1) +def main(): + ParticleOdomFilter() if __name__ == "__main__": - ParticleOdomFilter() + main() diff --git a/rktl_control/nodes/pose_synchronizer b/src/rktl_control/rktl_control/pose_synchronizer.py old mode 100755 new mode 100644 similarity index 69% rename from rktl_control/nodes/pose_synchronizer rename to src/rktl_control/rktl_control/pose_synchronizer.py index c5db8db03..33e4b3548 --- a/rktl_control/nodes/pose_synchronizer +++ b/src/rktl_control/rktl_control/pose_synchronizer.py @@ -9,10 +9,11 @@ """ # ROS -import rospy +import rclpy +import sys from geometry_msgs.msg import PoseWithCovarianceStamped from std_msgs.msg import Float32 -from tf.transformations import euler_from_quaternion, quaternion_from_euler +from rktl_dependencies.transformations import euler_from_quaternion, quaternion_from_euler from collections import deque from math import sin, cos, atan2 @@ -22,45 +23,46 @@ class PoseSynchronizer(object): """Class to synchronize and buffer all poses.""" def __init__(self): - rospy.init_node('pose_synchronizer') + rclpy.init(args=sys.argv) + self.node = rclpy.create_node('pose_synchronizer') # constants - self.MAP_FRAME = rospy.get_param('~map_frame', 'map') - self.TOPICS = rospy.get_param('~topics') - self.PERIOD = rospy.Duration(1/rospy.get_param('~rate', 10.0)) - self.DELAY = rospy.Duration(rospy.get_param('~delay', 0.15)) - self.PUB_LATENCY = rospy.get_param('~publish_latency', False) + self.MAP_FRAME = self.node.declare_parameter('~map_frame', 'map') + self.TOPICS = self.node.declare_parameter('~topics') + self.PERIOD = rclpy.time.Time(1/self.node.declare_parameter('~rate', 10.0)) + self.DELAY = rclpy.time.Time(self.node.declare_parameter('~delay', 0.15)) + self.PUB_LATENCY = self.node.declare_parameter('~publish_latency', False) self.USE_WEIGHTS = { - topic: rospy.get_param('~use_weights')[i] for i, + topic: self.node.declare_parameter('~use_weights')[i] for i, topic in enumerate(self.TOPICS)} # variables self.buffers = { - topic: deque(maxlen=rospy.get_param('buffer_size', 15)) + topic: deque(maxlen=self.node.declare_parameter('buffer_size', 15)) for topic in self.TOPICS} self.pubs = { - topic: rospy.Publisher( - topic + '_sync', PoseWithCovarianceStamped, queue_size=1) + topic: self.node.create_publisher(PoseWithCovarianceStamped, + topic + '_sync', queue_size=1) for topic in self.TOPICS} if self.PUB_LATENCY: self.latency_pubs = { - topic: rospy.Publisher( - topic + '_sync_latency', Float32, queue_size=1) + topic: self.node.create_publisher(Float32, + topic + '_sync_latency', queue_size=1) for topic in self.TOPICS} for topic in self.TOPICS: - rospy.Subscriber( - topic, PoseWithCovarianceStamped, lambda msg, + self.node.create_subscription(PoseWithCovarianceStamped, + topic, lambda msg, topic=topic: self.recv_pose(topic, msg)) # main loop - rate = rospy.Rate(1/self.PERIOD.to_sec()) - while not rospy.is_shutdown(): - now = rospy.Time.now() + rate = self.node.create_rate(1/self.PERIOD.to_sec()) + while rclpy.ok(): + now = self.node.get_clock().now().to_msg()() for topic in self.TOPICS: self.send_pose(topic, now) try: rate.sleep() - except rospy.ROSInterruptException: + except rclpy.ROSInterruptException: pass def recv_pose(self, topic, pose_msg): @@ -83,8 +85,8 @@ def recv_pose(self, topic, pose_msg): # append to buffer (thread safe) sample = (t, x, y, yaw, weight) self.buffers[topic].append(sample) - latency = (rospy.Time.now() - t).to_sec() - rospy.loginfo("%s: received with %0.3f sec delay", topic, latency) + latency = (self.node.get_clock().now().to_msg() - t).to_sec() + self.node.get_logger().info("%s: received with %0.3f sec delay", topic, latency) if self.PUB_LATENCY: self.latency_pubs[topic].publish(latency) @@ -102,13 +104,13 @@ def send_pose(self, topic, now): t, x, y, yaw, weight = self.buffers[topic].popleft() # check if too old if t < now - self.DELAY - self.PERIOD/2: - rospy.logdebug( + self.node.get_logger().debug( "%s: discarding pose from %0.3f sec ago", topic, (now - t).to_sec()) # check if too new elif t > now - self.DELAY + self.PERIOD/2: self.buffers[topic].append((t, x, y, yaw, weight)) - rospy.logdebug( + self.node.get_logger().debug( "%s: skipping pose from %0.3f sec ago", topic, (now - t).to_sec()) # add to averages else: @@ -121,14 +123,14 @@ def send_pose(self, topic, now): # check there is sufficient data if samples == 0: - rospy.logwarn_throttle(1.0, "%s: insufficient pose data", topic) + self.node.get_logger().warn(1.0, "%s: insufficient pose data", topic) return # take avg avg_x /= total_weight avg_y /= total_weight avg_yaw = atan2(avg_hy, avg_hx) - rospy.loginfo("%s: using %d samples", topic, samples) + self.node.get_logger().info("%s: using %d samples", topic, samples) # publish message pose_msg = PoseWithCovarianceStamped() diff --git a/src/rktl_control/rktl_control/topic_delay.py b/src/rktl_control/rktl_control/topic_delay.py new file mode 100644 index 000000000..2c31bef9d --- /dev/null +++ b/src/rktl_control/rktl_control/topic_delay.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +""" +Delay an arbitrary ROS topic without modifying message + +License: + BSD 3-Clause License + Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + All rights reserved. +""" + +import rclpy +import sys +from collections import deque +from time import sleep +import sys + +# message type imports +from geometry_msgs.msg import PoseWithCovarianceStamped + + +class Delay(object): + """Class to delay arbitrary messages.""" + + def __init__(self): + + # get args + assert len(sys.argv) >= 5 + input_name = sys.argv[1] + output_name = sys.argv[2] + topic_type = convertStrToType(sys.argv[3]) + delay = float(sys.argv[4]) + + # create node + rclpy.init(args=sys.argv) + self.node = rclpy.create_node('delay') + + self.node.get_logger().info(f"Args: {sys.argv}") + + # get message type + # msg_type = None + # topics_types = self.node.get_topic_names_and_types() + # for topic, type in topics_types: + # if str(type) == topic_type: + # msg_type = type + # assert msg_type is not None + + pub = self.node.create_publisher(topic_type, output_name, 1) + self.node.create_subscription(topic_type, input_name, self.msg_cb, 1) + self.buffer = deque() + + while rclpy.ok(): + if len(self.buffer) > 0: + time, msg = self.buffer.popleft() + wait = delay - (self.node.get_clock().now().to_msg() - time).to_sec() + if wait > 0: + sleep(wait) + pub.publish(msg) + else: + sleep(delay/2) + + def msg_cb(self, msg): + """Callback for enqueuing new message""" + self.buffer.append((self.node.get_clock().now().to_msg(), msg)) + +def main(): + Delay() + +if __name__ == "__main__": + main() + +def convertStrToType(string): + if string == "geometry_msgs/PoseWithCovarianceStamped": + return PoseWithCovarianceStamped \ No newline at end of file diff --git a/rktl_control/nodes/websocket_node.py b/src/rktl_control/rktl_control/websocket_node.py similarity index 77% rename from rktl_control/nodes/websocket_node.py rename to src/rktl_control/rktl_control/websocket_node.py index 662643bce..cb66193e7 100644 --- a/rktl_control/nodes/websocket_node.py +++ b/src/rktl_control/rktl_control/websocket_node.py @@ -4,7 +4,8 @@ import os import websockets -import rospy +import rclpy +import sys from rktl_msgs.msg import ControlEffort car_num = 0; # How do we get this? @@ -22,14 +23,15 @@ async def socket_handler(websocket): print(len(packet)) await websocket.send(packet) -async def main(): +async def main(self): try: async with websockets.serve(socket_handler, None, 8765): await asyncio.Future() except asyncio.CancelledError: running = False print("Quitting") - rospy.signal_shutdown() + # was rospy.signal_shutdown() + self.destroy_node() os._exit(0) def receive_callback(thr, str, data): @@ -41,8 +43,9 @@ def receive_callback(thr, str, data): if __name__ == '__main__': print("Initing") print("Starting") - rospy.init_node('car_websocket', anonymous=True) - rospy.Subscriber(f'/cars/car{car_num}/effort', ControlEffort, receive_callback) + rclpy.init(args=sys.args) + node = rclpy.create_node('car_websocket') + node.create_subscription(ControlEffort, f'/cars/car{car_num}/effort', receive_callback, 1) print("Running") loop = asyncio.get_event_loop() task = asyncio.run(main()) @@ -52,5 +55,6 @@ def receive_callback(thr, str, data): loop.run_until_complete(task) print("Spinning") finally: - rospy.signal_shutdown() + # was rospy.signal_shutdown() + rclpy.shutdown() loop.close() diff --git a/rktl_control/nodes/xbox_interface b/src/rktl_control/rktl_control/xbox_interface old mode 100755 new mode 100644 similarity index 67% rename from rktl_control/nodes/xbox_interface rename to src/rktl_control/rktl_control/xbox_interface index 0731a2b5c..fc30d607b --- a/rktl_control/nodes/xbox_interface +++ b/src/rktl_control/rktl_control/xbox_interface @@ -8,7 +8,8 @@ License: All rights reserved. """ -import rospy +import rclpy +import sys from rktl_msgs.msg import ControlEffort from std_srvs.srv import Empty from sensor_msgs.msg import Joy @@ -16,32 +17,33 @@ from sensor_msgs.msg import Joy class XboxInterface(): def __init__(self): - rospy.init_node('xbox_interface') + rclpy.init(args=sys.args) + self.node = rclpy.create_node('xbox_interface') - self.base_throttle = rospy.get_param( + self.base_throttle = self.node.declare_parameter( '~base_throttle') # throttle when not boosting - self.boost_throttle = rospy.get_param( + self.boost_throttle = self.node.declare_parameter( '~boost_throttle') # throttle when boosting # how long should one second of boost take to recharge? - self.cooldown_ratio = rospy.get_param('~cooldown_ratio') + self.cooldown_ratio = self.node.declare_parameter('~cooldown_ratio') # max time boost can be active - self.max_boost = rospy.get_param('~max_boost') * self.cooldown_ratio + self.max_boost = self.node.declare_parameter('~max_boost') * self.cooldown_ratio self.boost_tank = self.max_boost # this is how much boost is left self.is_boosting = False # is the car currently boosting? # Publishers - self.effort_pub = rospy.Publisher( - 'effort', ControlEffort, queue_size=1) + self.effort_pub = self.node.create_publisher(ControlEffort, + 'effort', queue_size=1) # Subscribers - rospy.Subscriber('joy', Joy, self.callback) + self.node.create_subscription(Joy, 'joy', self.callback, 1) # Services - self.reset_srv = rospy.ServiceProxy('/sim_reset', Empty) + self.reset_srv = self.node.create_client(Empty, '/sim_reset') # Manages the cooldown - rate = rospy.Rate(1) - while not rospy.is_shutdown(): + rate = self.node.create_rate(1) + while not rclpy.ok(): if not self.is_boosting: if self.boost_tank < self.max_boost: self.boost_tank += 1 @@ -52,7 +54,7 @@ class XboxInterface(): def callback(self, joy: Joy): msg = ControlEffort() - msg.header.stamp = rospy.Time.now() + msg.header.stamp = self.node.get_clock().now().to_msg() if (joy.buttons[6] == 1): self.reset_srv.call() msg.throttle = ((joy.axes[2] - joy.axes[5]) / 2) diff --git a/src/rktl_control/setup.cfg b/src/rktl_control/setup.cfg new file mode 100644 index 000000000..3763f88e8 --- /dev/null +++ b/src/rktl_control/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rktl_control +[install] +install_scripts=$base/lib/rktl_control diff --git a/src/rktl_control/setup.py b/src/rktl_control/setup.py new file mode 100644 index 000000000..bf91df992 --- /dev/null +++ b/src/rktl_control/setup.py @@ -0,0 +1,35 @@ +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = 'rktl_control' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))), + (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.yaml'))), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='DinoSage', + maintainer_email='anshag@gmail.com', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + "controller = rktl_control.controller:main", + "mean_odom_filter = rktl_control.mean_odom_filter:main", + "particle_odom_filter = rktl_control.particle_odom_filter:main", + "topic_delay = rktl_control.topic_delay:main", + "keyboard_interface = rktl_control.keyboard_interface:main", + "pose_synchronizer.py = rktl_control.pose_synchronizer:main" + ], + }, +) diff --git a/src/rktl_control/test/test_copyright.py b/src/rktl_control/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/src/rktl_control/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/rktl_control/test/test_flake8.py b/src/rktl_control/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/src/rktl_control/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/rktl_control/test/test_mean_filter.test b/src/rktl_control/test/test_mean_filter.test similarity index 100% rename from rktl_control/test/test_mean_filter.test rename to src/rktl_control/test/test_mean_filter.test diff --git a/rktl_control/test/test_mean_filter_node b/src/rktl_control/test/test_mean_filter_node similarity index 86% rename from rktl_control/test/test_mean_filter_node rename to src/rktl_control/test/test_mean_filter_node index 7683cdf8b..0da400a85 100755 --- a/rktl_control/test/test_mean_filter_node +++ b/src/rktl_control/test/test_mean_filter_node @@ -7,10 +7,11 @@ License: All rights reserved. """ -import unittest, rostest, rospy, time +import unittest, rostest, rclpy, time +import sys from geometry_msgs.msg import PoseWithCovarianceStamped from nav_msgs.msg import Odometry -from tf.transformations import euler_from_quaternion, quaternion_from_euler +from rktl_dependencies.transformations import euler_from_quaternion, quaternion_from_euler import numpy as np def set_pose(t, x, y, yaw): @@ -48,18 +49,19 @@ def get_odom(odom_msg): class TestMeanFilter(unittest.TestCase): def test_mean_filter(self): # initialize node and interface to code under test - rospy.init_node('test_mean_filter_node') + rclpy.init(args=sys.argv) + self.node = rclpy.create_node('test_mean_filter_node') - pub = rospy.Publisher('pose_sync', PoseWithCovarianceStamped, queue_size=1) - rospy.Subscriber('odom', Odometry, self.odom_cb) + pub = self.node.create_publisher(PoseWithCovarianceStamped, 'pose_sync') + self.node.create_subscription(Odometry, 'odom', self.odom_cb, 1) # member variables used for test self.odom = None # publish messages to get things going - t = rospy.Time.now() - rospy.Duration(0.1) + t = self.node.get_clock().now().to_msg() - rclpy.time.Time(0.1) while(self.odom is None): - t += rospy.Duration(0.1) + t += rclpy.time.Time(0.1) pub.publish(set_pose(t, 0.0, 0.0, 0.0)) time.sleep(0.1) @@ -69,7 +71,7 @@ class TestMeanFilter(unittest.TestCase): # send and wait (3rd msg) self.odom = None - t += rospy.Duration(0.1) + t += rclpy.time.Time(0.1) pub.publish(set_pose(t, 3.0, -3.0, 0.0)) while(self.odom is None): pass @@ -80,7 +82,7 @@ class TestMeanFilter(unittest.TestCase): # send and wait (4th msg) self.odom = None - t += rospy.Duration(0.1) + t += rclpy.time.Time(0.1) pub.publish(set_pose(t, 6.0, 0.0, 1.5708)) while(self.odom is None): pass @@ -92,7 +94,7 @@ class TestMeanFilter(unittest.TestCase): # send and wait (5th - 7th msgs) for __ in range(3): self.odom = None - t += rospy.Duration(0.1) + t += rclpy.time.Time(0.1) pub.publish(set_pose(t, 0.0, 0.0, 0.0)) while(self.odom is None): pass @@ -103,7 +105,7 @@ class TestMeanFilter(unittest.TestCase): # send and wait (8th msg) self.odom = None - t += rospy.Duration(0.1) + t += rclpy.time.Time(0.1) pub.publish(set_pose(t, 0.0, 0.0, 0.0)) while(self.odom is None): pass diff --git a/rktl_control/test/test_particle_filter.test b/src/rktl_control/test/test_particle_filter.test similarity index 100% rename from rktl_control/test/test_particle_filter.test rename to src/rktl_control/test/test_particle_filter.test diff --git a/rktl_control/test/test_particle_filter_node b/src/rktl_control/test/test_particle_filter_node similarity index 97% rename from rktl_control/test/test_particle_filter_node rename to src/rktl_control/test/test_particle_filter_node index 50bf6ee3f..5a22d52b5 100755 --- a/rktl_control/test/test_particle_filter_node +++ b/src/rktl_control/test/test_particle_filter_node @@ -6,7 +6,7 @@ License: All rights reserved. """ -import unittest, rostest, rospy +import unittest, rostest, rclpy import numpy as np # nasty way to import ParticleOdomFilter @@ -18,7 +18,7 @@ from particle_odom_filter import ParticleOdomFilter class TestBicycle(unittest.TestCase): def test_bicycle(self): # avoid spinning in init function - rospy.spin = lambda : None + rclpy.spin = lambda : None filter = ParticleOdomFilter() particles = np.array([ diff --git a/src/rktl_control/test/test_pep257.py b/src/rktl_control/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/src/rktl_control/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/rktl_control/test/test_sync.test b/src/rktl_control/test/test_sync.test similarity index 100% rename from rktl_control/test/test_sync.test rename to src/rktl_control/test/test_sync.test diff --git a/rktl_control/test/test_sync_node b/src/rktl_control/test/test_sync_node similarity index 80% rename from rktl_control/test/test_sync_node rename to src/rktl_control/test/test_sync_node index 889e96a2f..46805405b 100755 --- a/rktl_control/test/test_sync_node +++ b/src/rktl_control/test/test_sync_node @@ -6,9 +6,10 @@ License: All rights reserved. """ -import unittest, rostest, rospy +import unittest, rostest, rclpy +import sys from geometry_msgs.msg import PoseWithCovarianceStamped -from tf.transformations import euler_from_quaternion, quaternion_from_euler +from rktl_dependencies.transformations import euler_from_quaternion, quaternion_from_euler import numpy as np def set_pose(t, x, y, yaw, weight=1.0): @@ -43,15 +44,16 @@ def get_pose(pose_msg): class TestSync(unittest.TestCase): def test_sync(self): # initialize node and interface to code under test - rospy.init_node('test_sync_node') + rclpy.init(args=sys.args) + self.node = rclpy.create_node('test_sync_node') - car0_pub = rospy.Publisher('cars/car0/pose', PoseWithCovarianceStamped, queue_size=0, latch=True) - car1_pub = rospy.Publisher('cars/car1/pose', PoseWithCovarianceStamped, queue_size=0, latch=True) - ball_pub = rospy.Publisher('ball/pose', PoseWithCovarianceStamped, queue_size=0, latch=True) + car0_pub = self.node.create_publisher(PoseWithCovarianceStamped, 'cars/car0/pose') + car1_pub = self.node.create_publisher(PoseWithCovarianceStamped, 'cars/car1/pose') + ball_pub = self.node.create_publisher(PoseWithCovarianceStamped, 'ball/pose') - rospy.Subscriber('cars/car0/pose_sync', PoseWithCovarianceStamped, self.car0_cb) - rospy.Subscriber('cars/car1/pose_sync', PoseWithCovarianceStamped, self.car1_cb) - rospy.Subscriber('ball/pose_sync', PoseWithCovarianceStamped, self.ball_cb) + self.node.create_subscription(PoseWithCovarianceStamped, 'cars/car0/pose_sync', self.car0_cb, 1) + self.node.create_subscription(PoseWithCovarianceStamped, 'cars/car1/pose_sync', self.car1_cb, 1) + self.node.create_subscription(PoseWithCovarianceStamped, 'ball/pose_sync', self.ball_cb, 1) # member variables used for test self.last_car0 = None @@ -60,8 +62,8 @@ class TestSync(unittest.TestCase): # publish messages to get things going - rospy.loginfo("synchronizing with node under test") - t = rospy.Time.now() + rospy.Duration(0.5) + self.node.get_logger().info("synchronizing with node under test") + t = self.node.get_clock().now().to_msg() + rclpy.time.Time(0.5) car0_pub.publish(set_pose(t, 0.0, 0.0, 0.0)) car1_pub.publish(set_pose(t, 0.0, 0.0, 0.0)) ball_pub.publish(set_pose(t, 0.0, 0.0, 0.0)) @@ -71,8 +73,8 @@ class TestSync(unittest.TestCase): self.assertTrue(self.times_match(), msg='output time is not synchronized') # case 1 - rospy.loginfo("testing independence of channels, divide by zero, weights") - t_next = self.last_car0[0] + rospy.Duration(0.1) + self.node.get_logger().info("testing independence of channels, divide by zero, weights") + t_next = self.last_car0[0] + rclpy.time.Time(0.1) self.clear_poses() # send poses @@ -99,11 +101,11 @@ class TestSync(unittest.TestCase): self.assertTrue(np.allclose(self.last_ball[1:], [2.75, -2.75, 0.0]), msg='ball pose is incorrect') # case 2 - rospy.loginfo("testing old data rejection, caching of future data") - t_next = self.last_car0[0] + rospy.Duration(0.1) + self.node.get_logger().info("testing old data rejection, caching of future data") + t_next = self.last_car0[0] + rclpy.time.Time(0.1) self.clear_poses() - t_old = t_next - rospy.Duration(0.06) - t_future = t_next + rospy.Duration(0.06) + t_old = t_next - rclpy.time.Time(0.06) + t_future = t_next + rclpy.time.Time(0.06) # send poses car0_pub.publish(set_pose(t_old, 0.0, 0.0, 0.0)) @@ -130,10 +132,10 @@ class TestSync(unittest.TestCase): self.assertTrue(np.allclose(self.last_ball[1:], [1.0, 1.0, 1.0]), msg='ball pose is incorrect') # case 3 - rospy.loginfo("testing caching of future data, sorting out of order data") - t_next = self.last_car0[0] + rospy.Duration(0.1) + self.node.get_logger().info("testing caching of future data, sorting out of order data") + t_next = self.last_car0[0] + rclpy.time.Time(0.1) self.clear_poses() - t_delayed = t_next - rospy.Duration(0.04) + t_delayed = t_next - rclpy.time.Time(0.04) # send poses car0_pub.publish(set_pose(t_delayed, 0.0, 0.0, 0.0)) diff --git a/src/rktl_dependencies/package.xml b/src/rktl_dependencies/package.xml new file mode 100644 index 000000000..f1d3ba972 --- /dev/null +++ b/src/rktl_dependencies/package.xml @@ -0,0 +1,23 @@ + + + + rktl_dependencies + 1.0.0 + Contains external dependencies required for other packages. + https://www.purduearc.com + Adam Schmok + BSD 3 Clause + + rclpy + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + python-numpy + + + ament_python + + diff --git a/src/rktl_dependencies/resource/rktl_dependencies b/src/rktl_dependencies/resource/rktl_dependencies new file mode 100644 index 000000000..e69de29bb diff --git a/src/rktl_dependencies/rktl_dependencies/__init__.py b/src/rktl_dependencies/rktl_dependencies/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/rktl_dependencies/rktl_dependencies/angles.py b/src/rktl_dependencies/rktl_dependencies/angles.py new file mode 100644 index 000000000..c5a20e371 --- /dev/null +++ b/src/rktl_dependencies/rktl_dependencies/angles.py @@ -0,0 +1,238 @@ +#********************************************************************* +# Software License Agreement (BSD License) +# +# Copyright (c) 2015, Bossa Nova Robotics +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the Bossa Nova Robotics 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 OWNER 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. +#********************************************************************/ + +from math import fmod, pi, fabs + +def normalize_angle_positive(angle): + """ Normalizes the angle to be 0 to 2*pi + It takes and returns radians. """ + return angle % (2.0*pi) + +def normalize_angle(angle): + """ Normalizes the angle to be -pi to +pi + It takes and returns radians.""" + a = normalize_angle_positive(angle) + if a > pi: + a -= 2.0 *pi + return a + +def shortest_angular_distance(from_angle, to_angle): + """ Given 2 angles, this returns the shortest angular + difference. The inputs and ouputs are of course radians. + + The result would always be -pi <= result <= pi. Adding the result + to "from" will always get you an equivelent angle to "to". + """ + return normalize_angle(to_angle-from_angle) + +def two_pi_complement(angle): + """ returns the angle in [-2*pi, 2*pi] going the other way along the unit circle. + \param angle The angle to which you want to turn in the range [-2*pi, 2*pi] + E.g. two_pi_complement(-pi/4) returns 7_pi/4 + two_pi_complement(pi/4) returns -7*pi/4 + """ + #check input conditions + if angle > 2*pi or angle < -2.0*pi: + angle = fmod(angle, 2.0*pi) + if angle < 0: + return 2*pi+angle + elif angle > 0: + return -2*pi+angle + + return 2*pi + +def _find_min_max_delta(from_angle, left_limit, right_limit): + """ This function is only intended for internal use and not intended for external use. + If you do use it, read the documentation very carefully. + + Returns the min and max amount (in radians) that can be moved + from "from" angle to "left_limit" and "right_limit". + + \param from - "from" angle - must lie in [-pi, pi) + \param left_limit - left limit of valid interval for angular position + - must lie in [-pi, pi], left and right limits are specified on + the unit circle w.r.t to a reference pointing inwards + \param right_limit - right limit of valid interval for angular position + - must lie in [-pi, pi], left and right limits are specified on + the unit circle w.r.t to a reference pointing inwards + \return (valid, min, max) - angle in radians that can be moved from "from" position before hitting the joint stop + valid is False if "from" angle does not lie in the interval [left_limit,right_limit] + """ + delta = [0]*4 + delta[0] = shortest_angular_distance(from_angle,left_limit) + delta[1] = shortest_angular_distance(from_angle,right_limit) + delta[2] = two_pi_complement(delta[0]) + delta[3] = two_pi_complement(delta[1]) + + if delta[0] == 0: + return True, delta[0], max(delta[1], delta[3]) + + if delta[1] == 0: + return True, min(delta[0], delta[2]), delta[1] + + delta_min = delta[0] + delta_min_2pi = delta[2] + if delta[2] < delta_min: + delta_min = delta[2] + delta_min_2pi = delta[0] + + delta_max = delta[1] + delta_max_2pi = delta[3] + if delta[3] > delta_max: + delta_max = delta[3] + delta_max_2pi = delta[1] + + # printf("%f %f %f %f\n",delta_min,delta_min_2pi,delta_max,delta_max_2pi) + if (delta_min <= delta_max_2pi) or (delta_max >= delta_min_2pi): + if left_limit == -pi and right_limit == pi: + return (True, delta_max_2pi, delta_min_2pi) + else: + return (False, delta_max_2pi, delta_min_2pi) + return True, delta_min, delta_max + +def shortest_angular_distance_with_limits(from_angle, to_angle, left_limit, right_limit): + """ Returns the delta from "from_angle" to "to_angle" making sure it does not violate limits specified by left_limit and right_limit. + The valid interval of angular positions is [left_limit,right_limit]. E.g., [-0.25,0.25] is a 0.5 radians wide interval that contains 0. + But [0.25,-0.25] is a 2*pi-0.5 wide interval that contains pi (but not 0). + The value of shortest_angle is the angular difference between "from" and "to" that lies within the defined valid interval. + + E.g. shortest_angular_distance_with_limits(-0.5,0.5,0.25,-0.25) returns 2*pi-1.0 + shortest_angular_distance_with_limits(-0.5,0.5,-0.25,0.25) returns None since -0.5 and 0.5 do not lie in the interval [-0.25,0.25] + + \param left_limit - left limit of valid interval for angular position + - must lie in [-pi, pi], left and right limits are specified on + the unit circle w.r.t to a reference pointing inwards + \param right_limit - right limit of valid interval for angular position + - must lie in [-pi, pi], left and right limits are specified on + the unit circle w.r.t to a reference pointing inwards + \returns valid_flag, shortest_angle + """ + min_delta = -2*pi + max_delta = 2*pi + min_delta_to = -2*pi + max_delta_to = 2*pi + flag, min_delta, max_delta = _find_min_max_delta(from_angle, left_limit, right_limit) + delta = shortest_angular_distance(from_angle,to_angle) + delta_mod_2pi = two_pi_complement(delta) + + if flag: #from position is within the limits + if delta >= min_delta and delta <= max_delta: + return True, delta + elif delta_mod_2pi >= min_delta and delta_mod_2pi <= max_delta: + return True, delta_mod_2pi + else: #to position is outside the limits + flag, min_delta_to, max_delta_to = _find_min_max_delta(to_angle,left_limit,right_limit) + if fabs(min_delta_to) < fabs(max_delta_to): + shortest_angle = max(delta, delta_mod_2pi) + elif fabs(min_delta_to) > fabs(max_delta_to): + shortest_angle = min(delta,delta_mod_2pi) + else: + if fabs(delta) < fabs(delta_mod_2pi): + shortest_angle = delta + else: + shortest_angle = delta_mod_2pi + return False, shortest_angle + else: # from position is outside the limits + flag, min_delta_to, max_delta_to = _find_min_max_delta(to_angle,left_limit,right_limit) + + if fabs(min_delta) < fabs(max_delta): + shortest_angle = min(delta,delta_mod_2pi) + elif fabs(min_delta) > fabs(max_delta): + shortest_angle = max(delta,delta_mod_2pi) + else: + if fabs(delta) < fabs(delta_mod_2pi): + shortest_angle = delta + else: + shortest_angle = delta_mod_2pi + return False, shortest_angle + +def shortest_angular_distance_with_large_limits(from_angle, to_angle, left_limit, right_limit): + """ Returns the delta from `from_angle` to `to_angle`, making sure it does not violate limits specified by `left_limit` and `right_limit`. + This function is similar to `shortest_angular_distance_with_limits()`, with the main difference that it accepts limits outside the `[-M_PI, M_PI]` range. + Even if this is quite uncommon, one could indeed consider revolute joints with large rotation limits, e.g., in the range `[-2*M_PI, 2*M_PI]`. + + In this case, a strict requirement is to have `left_limit` smaller than `right_limit`. + Note also that `from_angle` must lie inside the valid range, while `to_angle` does not need to. + In fact, this function will evaluate the shortest (valid) angle `shortest_angle` so that `from_angle+shortest_angle` equals `to_angle` up to an integer multiple of `2*M_PI`. + As an example, a call to `shortest_angular_distance_with_large_limits(0, 10.5*M_PI, -2*M_PI, 2*M_PI)` will return `true`, with `shortest_angle=0.5*M_PI`. + This is because `from_angle` and `from_angle+shortest_angle` are both inside the limits, and `fmod(to_angle+shortest_angle, 2*M_PI)` equals `fmod(to_angle, 2*M_PI)`. + On the other hand, `shortest_angular_distance_with_large_limits(10.5*M_PI, 0, -2*M_PI, 2*M_PI)` will return false, since `from_angle` is not in the valid range. + Finally, note that the call `shortest_angular_distance_with_large_limits(0, 10.5*M_PI, -2*M_PI, 0.1*M_PI)` will also return `true`. + However, `shortest_angle` in this case will be `-1.5*M_PI`. + + \return valid_flag, shortest_angle - valid_flag will be true if `left_limit < right_limit` and if "from_angle" and "from_angle+shortest_angle" positions are within the valid interval, false otherwise. + \param left_limit - left limit of valid interval, must be smaller than right_limit. + \param right_limit - right limit of valid interval, must be greater than left_limit. + """ + # Shortest steps in the two directions + delta = shortest_angular_distance(from_angle, to_angle) + delta_2pi = two_pi_complement(delta) + + # "sort" distances so that delta is shorter than delta_2pi + if fabs(delta) > fabs(delta_2pi): + delta, delta_2pi = delta_2pi, delta + + if left_limit > right_limit: + # If limits are something like [PI/2 , -PI/2] it actually means that we + # want rotations to be in the interval [-PI,PI/2] U [PI/2,PI], ie, the + # half unit circle not containing the 0. This is already gracefully + # handled by shortest_angular_distance_with_limits, and therefore this + # function should not be called at all. However, if one has limits that + # are larger than PI, the same rationale behind shortest_angular_distance_with_limits + # does not hold, ie, M_PI+x should not be directly equal to -M_PI+x. + # In this case, the correct way of getting the shortest solution is to + # properly set the limits, eg, by saying that the interval is either + # [PI/2, 3*PI/2] or [-3*M_PI/2, -M_PI/2]. For this reason, here we + # return false by default. + return False, delta + + + # Check in which direction we should turn (clockwise or counter-clockwise). + + # start by trying with the shortest angle (delta). + to2 = from_angle + delta + if left_limit <= to2 and to2 <= right_limit: + # we can move in this direction: return success if the "from" angle is inside limits + valid_flag = left_limit <= from_angle and from_angle <= right_limit + return valid_flag, delta + + # delta is not ok, try to move in the other direction (using its complement) + to2 = from_angle + delta_2pi + if left_limit <= to2 and to2 <= right_limit: + # we can move in this direction: return success if the "from" angle is inside limits + valid_flag = left_limit <= from_angle and from_angle <= right_limit + return valid_flag, delta_2pi + + # nothing works: we always go outside limits + return False, delta \ No newline at end of file diff --git a/src/rktl_dependencies/rktl_dependencies/pfilter.py b/src/rktl_dependencies/rktl_dependencies/pfilter.py new file mode 100644 index 000000000..7c5dd1b3a --- /dev/null +++ b/src/rktl_dependencies/rktl_dependencies/pfilter.py @@ -0,0 +1,485 @@ +import numpy as np +import numpy.ma as ma + +# return a new function that has the heat kernel (given by delta) applied. +def make_heat_adjusted(sigma): + def heat_distance(d): + return np.exp(-(d**2) / (2.0 * sigma**2)) + + return heat_distance + + +## Resampling based on the examples at: https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/12-Particle-Filters.ipynb +## originally by Roger Labbe, under an MIT License +def systematic_resample(weights): + n = len(weights) + positions = (np.arange(n) + np.random.uniform(0, 1)) / n + return create_indices(positions, weights) + + +def stratified_resample(weights): + n = len(weights) + positions = (np.random.uniform(0, 1, n) + np.arange(n)) / n + return create_indices(positions, weights) + + +def residual_resample(weights): + n = len(weights) + indices = np.zeros(n, np.uint32) + # take int(N*w) copies of each weight + num_copies = (n * weights).astype(np.uint32) + k = 0 + for i in range(n): + for _ in range(num_copies[i]): # make n copies + indices[k] = i + k += 1 + # use multinormial resample on the residual to fill up the rest. + residual = weights - num_copies # get fractional part + residual /= np.sum(residual) + cumsum = np.cumsum(residual) + cumsum[-1] = 1 + indices[k:n] = np.searchsorted(cumsum, np.random.uniform(0, 1, n - k)) + return indices + + +def create_indices(positions, weights): + n = len(weights) + indices = np.zeros(n, np.uint32) + cumsum = np.cumsum(weights) + i, j = 0, 0 + while i < n: + if positions[i] < cumsum[j]: + indices[i] = j + i += 1 + else: + j += 1 + + return indices + + +### end rlabbe's resampling functions + + +def multinomial_resample(weights): + return np.random.choice(np.arange(len(weights)), p=weights, size=len(weights)) + + +# resample function from http://scipy-cookbook.readthedocs.io/items/ParticleFilter.html +def resample(weights): + n = len(weights) + indices = [] + C = [0.0] + [np.sum(weights[: i + 1]) for i in range(n)] + u0, j = np.random.random(), 0 + for u in [(u0 + i) / n for i in range(n)]: + while u > C[j]: + j += 1 + indices.append(j - 1) + return indices + + +# identity function for clearer naming +identity = lambda x: x + + +def squared_error(x, y, sigma=1): + """ + RBF kernel, supporting masked values in the observation + Parameters: + ----------- + x : array (N,D) array of values + y : array (N,D) array of values + + Returns: + ------- + + distance : scalar + Total similarity, using equation: + + d(x,y) = e^((-1 * (x - y) ** 2) / (2 * sigma ** 2)) + + summed over all samples. Supports masked arrays. + """ + dx = (x - y) ** 2 + d = np.ma.sum(dx, axis=1) + return np.exp(-d / (2.0 * sigma**2)) + + +def gaussian_noise(x, sigmas): + """Apply diagonal covaraiance normally-distributed noise to the N,D array x. + Parameters: + ----------- + x : array + (N,D) array of values + sigmas : array + D-element vector of std. dev. for each column of x + """ + n = np.random.normal(np.zeros(len(sigmas)), sigmas, size=(x.shape[0], len(sigmas))) + return x + n + + +def t_noise(x, sigmas, df=1.0): + """Apply diagonal covaraiance t-distributed noise to the N,D array x. + Parameters: + ----------- + x : array + (N,D) array of values + sigmas : array + D-element vector of std. dev. for each column of x + df : degrees of freedom (shape of the t distribution) + Must be a scalar + """ + n = np.random.standard_t(df, size=(x.shape[0], len(sigmas))) * sigmas + return x + n + + +def cauchy_noise(x, sigmas): + """Apply diagonal covaraiance Cauchy-distributed noise to the N,D array x. + Parameters: + ----------- + x : array + (N,D) array of values + sigmas : array + D-element vector of std. dev. for each column of x + """ + n = np.random.standard_cauchy(size=(x.shape[0], len(sigmas))) * np.array(sigmas) + return x + n + + +def independent_sample(fn_list): + """Take a list of functions that each draw n samples from a distribution + and concatenate the result into an n, d matrix + Parameters: + ----------- + fn_list: list of functions + A list of functions of the form `sample(n)` that will take n samples + from a distribution. + Returns: + ------- + sample_fn: a function that will sample from all of the functions and concatenate + them + """ + + def sample_fn(n): + return np.stack([fn(n) for fn in fn_list]).T + + return sample_fn + + +class ParticleFilter(object): + """A particle filter object which maintains the internal state of a population of particles, and can + be updated given observations. + + Attributes: + ----------- + + n_particles : int + number of particles used (N) + d : int + dimension of the internal state + resample_proportion : float + fraction of particles resampled from prior at each step + particles : array + (N,D) array of particle states + original_particles : array + (N,D) array of particle states *before* any random resampling replenishment + This should be used for any computation on the previous time step (e.g. computing + expected values, etc.) + mean_hypothesis : array + The current mean hypothesized observation + mean_state : array + The current mean hypothesized internal state D + map_hypothesis: + The current most likely hypothesized observation + map_state: + The current most likely hypothesized state + n_eff: + Normalized effective sample size, in range 0.0 -> 1.0 + weight_informational_energy: + Informational energy of the distribution (Onicescu's) + weight_entropy: + Entropy of the weight distribution (in nats) + hypotheses : array + The (N,...) array of hypotheses for each particle + weights : array + N-element vector of normalized weights for each particle. + """ + + def __init__( + self, + prior_fn, + observe_fn=None, + resample_fn=None, + n_particles=200, + dynamics_fn=None, + noise_fn=None, + weight_fn=None, + resample_proportion=None, + column_names=None, + internal_weight_fn=None, + transform_fn=None, + n_eff_threshold=1.0, + ): + """ + + Parameters: + ----------- + + prior_fn : function(n) = > states + a function that generates N samples from the prior over internal states, as + an (N,D) particle array + observe_fn : function(states) => observations + transformation function from the internal state to the sensor state. Takes an (N,D) array of states + and returns the expected sensor output as an array (e.g. a (N,W,H) tensor if generating W,H dimension images). + resample_fn: A resampling function weights (N,) => indices (N,) + n_particles : int + number of particles in the filter + dynamics_fn : function(states) => states + dynamics function, which takes an (N,D) state array and returns a new one with the dynamics applied. + noise_fn : function(states) => states + noise function, takes a state vector and returns a new one with noise added. + weight_fn : function(hypothesized, real) => weights + computes the distance from the real sensed variable and that returned by observe_fn. Takes + a an array of N hypothesised sensor outputs (e.g. array of dimension (N,W,H)) and the observed output (e.g. array of dimension (W,H)) and + returns a strictly positive weight for the each hypothesis as an N-element vector. + This should be a *similarity* measure, with higher values meaning more similar, for example from an RBF kernel. + internal_weight_fn : function(states, observed) => weights + Reweights the particles based on their *internal* state. This is function which takes + an (N,D) array of internal states and the observation and + returns a strictly positive weight for the each state as an N-element vector. + Typically used to force particles inside of bounds, etc. + transform_fn: function(states, weights) => transformed_states + Applied at the very end of the update step, if specified. Updates the attribute + `transformed_particles`. Useful when the particle state needs to be projected + into a different space. + resample_proportion : float + proportion of samples to draw from the initial on each iteration. + n_eff_threshold=1.0: float + effective sample size at which resampling will be performed (0.0->1.0). Values + <1.0 will allow samples to propagate without the resampling step until + the effective sample size (n_eff) drops below the specified threshold. + column_names : list of strings + names of each the columns of the state vector + + """ + self.resample_fn = resample_fn or resample + self.column_names = column_names + self.prior_fn = prior_fn + self.n_particles = n_particles + self.init_filter() + self.n_eff_threshold = n_eff_threshold + self.d = self.particles.shape[1] + self.observe_fn = observe_fn or identity + self.dynamics_fn = dynamics_fn or identity + self.noise_fn = noise_fn or identity + self.weight_fn = weight_fn or squared_error + self.weights = np.ones(self.n_particles) / self.n_particles + self.transform_fn = transform_fn + self.transformed_particles = None + self.resample_proportion = resample_proportion or 0.0 + self.internal_weight_fn = internal_weight_fn + self.original_particles = np.array(self.particles) + self.original_weights = np.array(self.weights) + + def copy(self): + """Copy this filter at its current state. Returns + an exact copy, that can be run forward indepedently of the first. + Beware that if your passed in functions (e.g. dynamics) are stateful, behaviour + might not be independent! (tip: write stateless functions!) + + Returns: + --------- + A new, independent copy of this filter. + """ + # construct the filter + new_copy = ParticleFilter( + observe_fn=self.observe_fn, + resample_fn=self.resample_fn, + n_particles=self.n_particles, + prior_fn=self.prior_fn, + dynamics_fn=self.dynamics_fn, + weight_fn=self.weight_fn, + resample_proportion=self.resample_proportion, + column_names=self.column_names, + internal_weight_fn=self.internal_weight_fn, + transform_fn=self.transform_fn, + n_eff_threshold=self.n_eff_threshold, + ) + + # copy particle state + for array in ["particles", "original_particles", "original_weights", "weights"]: + setattr(new_copy, array, np.array(getattr(self, array))) + + # copy any attributes + for array in [ + "mean_hypothesis", + "mean_state", + "map_state", + "map_hypothesis", + "hypotheses", + "n_eff", + "weight_informational_energy", + "weight_entropy", + ]: + if hasattr(self, array): + setattr(new_copy, array, getattr(self, array).copy()) + + return new_copy + + def predictor(self, n=None, observed=None): + """Return an generator that runs a copy of the filter forward for prediction. + Yields the copied filter object at each step. Useful for making predictions + without inference. + + By default, the filter will run without observations. Pass observed to set the initial observation. + Use send() to send new observations to the filter. If no send() is used on any given iteration, the filter + will revert to prediction without observation. + + If n is specified, runs for n steps; otherwise, runs forever. + + Parameters: + ---------- + + n: integer + Number of steps to run for. If None, run forever. + + observed: array + The initial observed output, in the same format as observe_fn() will produce. This is typically the + input from the sensor observing the process (e.g. a camera image in optical tracking). + If None, then the observation step is skipped + + """ + copy = self.copy() + observed = None + if n is not None: + for i in range(n): + copy.update(observed) + observed = yield copy + else: + while True: + copy.update(observed) + observed = yield copy + + + def init_filter(self, mask=None): + """Initialise the filter by drawing samples from the prior. + + Parameters: + ----------- + mask : array, optional + boolean mask specifying the elements of the particle array to draw from the prior. None (default) + implies all particles will be resampled (i.e. a complete reset) + """ + new_sample = self.prior_fn(self.n_particles) + + # resample from the prior + if mask is None: + self.particles = new_sample + else: + self.particles[mask, :] = new_sample[mask, :] + + def update(self, observed=None, **kwargs): + """Update the state of the particle filter given an observation. + + Parameters: + ---------- + + observed: array + The observed output, in the same format as observe_fn() will produce. This is typically the + input from the sensor observing the process (e.g. a camera image in optical tracking). + If None, then the observation step is skipped, and the filter will run one step in prediction-only mode. + + kwargs: any keyword arguments specified will be passed on to: + observe_fn(y, **kwargs) + weight_fn(x, **kwargs) + dynamics_fn(x, **kwargs) + noise_fn(x, **kwargs) + internal_weight_function(x, y, **kwargs) + transform_fn(x, **kwargs) + """ + + # apply dynamics and noise + self.particles = self.noise_fn( + self.dynamics_fn(self.particles, **kwargs), **kwargs + ) + + # hypothesise observations + self.hypotheses = self.observe_fn(self.particles, **kwargs) + + if observed is not None: + # compute similarity to observations + # force to be positive + if type(observed)==list or type(observed)==tuple or type(observed)==float or type(observed)==int: + observed = np.array(observed, dtype=np.float64) + + weights = np.clip( + self.weights + * np.array( + self.weight_fn( + self.hypotheses.reshape(self.n_particles, -1), + observed.reshape(1, -1), + **kwargs + ) + ), + 0, + np.inf, + ) + else: + # we have no observation, so all particles weighted the same + weights = self.weights * np.ones((self.n_particles,)) + + # apply weighting based on the internal state + # most filters don't use this, but can be a useful way of combining + # forward and inverse models + if self.internal_weight_fn is not None: + internal_weights = self.internal_weight_fn( + self.particles, observed, **kwargs + ) + internal_weights = np.clip(internal_weights, 0, np.inf) + internal_weights = internal_weights / np.sum(internal_weights) + weights *= internal_weights + + # normalise weights to resampling probabilities + self.weight_normalisation = np.sum(weights) + self.weights = weights / self.weight_normalisation + + # Compute effective sample size and entropy of weighting vector. + # These are useful statistics for adaptive particle filtering. + self.n_eff = (1.0 / np.sum(self.weights**2)) / self.n_particles + self.weight_informational_energy = np.sum(self.weights**2) + self.weight_entropy = np.sum(self.weights * np.log(self.weights)) + + # preserve current sample set before any replenishment + self.original_particles = np.array(self.particles) + + # store mean (expected) hypothesis + self.mean_hypothesis = np.sum(self.hypotheses.T * self.weights, axis=-1).T + self.mean_state = np.sum(self.particles.T * self.weights, axis=-1).T + self.cov_state = np.cov(self.particles, rowvar=False, aweights=self.weights) + + # store MAP estimate + argmax_weight = np.argmax(self.weights) + self.map_state = self.particles[argmax_weight] + self.map_hypothesis = self.hypotheses[argmax_weight] + self.original_weights = np.array(self.weights) # before any resampling + + # apply any post-processing + if self.transform_fn: + self.transformed_particles = self.transform_fn( + self.original_particles, self.weights, **kwargs + ) + else: + self.transformed_particles = self.original_particles + + # resampling (systematic resampling) step + if self.n_eff < self.n_eff_threshold: + indices = self.resample_fn(self.weights) + self.particles = self.particles[indices, :] + self.weights = np.ones(self.n_particles) / self.n_particles + + # randomly resample some particles from the prior + if self.resample_proportion > 0: + random_mask = ( + np.random.random(size=(self.n_particles,)) < self.resample_proportion + ) + self.resampled_particles = random_mask + self.init_filter(mask=random_mask) \ No newline at end of file diff --git a/src/rktl_dependencies/rktl_dependencies/transformations.py b/src/rktl_dependencies/rktl_dependencies/transformations.py new file mode 100644 index 000000000..c7d778a15 --- /dev/null +++ b/src/rktl_dependencies/rktl_dependencies/transformations.py @@ -0,0 +1,1705 @@ +# -*- coding: utf-8 -*- +# transformations.py + +# Copyright (c) 2006, Christoph Gohlke +# Copyright (c) 2006-2009, The Regents of the University of California +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * 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. +# * Neither the name of the copyright holders nor the names of any +# 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 OWNER 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. + +"""Homogeneous Transformation Matrices and Quaternions. + +A library for calculating 4x4 matrices for translating, rotating, reflecting, +scaling, shearing, projecting, orthogonalizing, and superimposing arrays of +3D homogeneous coordinates as well as for converting between rotation matrices, +Euler angles, and quaternions. Also includes an Arcball control object and +functions to decompose transformation matrices. + +:Authors: + `Christoph Gohlke `__, + Laboratory for Fluorescence Dynamics, University of California, Irvine + +:Version: 20090418 + +Requirements +------------ + +* `Python 2.6 `__ +* `Numpy 1.3 `__ +* `transformations.c 20090418 `__ + (optional implementation of some functions in C) + +Notes +----- + +Matrices (M) can be inverted using numpy.linalg.inv(M), concatenated using +numpy.dot(M0, M1), or used to transform homogeneous coordinates (v) using +numpy.dot(M, v) for shape (4, \*) "point of arrays", respectively +numpy.dot(v, M.T) for shape (\*, 4) "array of points". + +Calculations are carried out with numpy.float64 precision. + +This Python implementation is not optimized for speed. + +Vector, point, quaternion, and matrix function arguments are expected to be +"array like", i.e. tuple, list, or numpy arrays. + +Return types are numpy arrays unless specified otherwise. + +Angles are in radians unless specified otherwise. + +Quaternions ix+jy+kz+w are represented as [x, y, z, w]. + +Use the transpose of transformation matrices for OpenGL glMultMatrixd(). + +A triple of Euler angles can be applied/interpreted in 24 ways, which can +be specified using a 4 character string or encoded 4-tuple: + + *Axes 4-string*: e.g. 'sxyz' or 'ryxy' + + - first character : rotations are applied to 's'tatic or 'r'otating frame + - remaining characters : successive rotation axis 'x', 'y', or 'z' + + *Axes 4-tuple*: e.g. (0, 0, 0, 0) or (1, 1, 1, 1) + + - inner axis: code of axis ('x':0, 'y':1, 'z':2) of rightmost matrix. + - parity : even (0) if inner axis 'x' is followed by 'y', 'y' is followed + by 'z', or 'z' is followed by 'x'. Otherwise odd (1). + - repetition : first and last axis are same (1) or different (0). + - frame : rotations are applied to static (0) or rotating (1) frame. + +References +---------- + +(1) Matrices and transformations. Ronald Goldman. + In "Graphics Gems I", pp 472-475. Morgan Kaufmann, 1990. +(2) More matrices and transformations: shear and pseudo-perspective. + Ronald Goldman. In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991. +(3) Decomposing a matrix into simple transformations. Spencer Thomas. + In "Graphics Gems II", pp 320-323. Morgan Kaufmann, 1991. +(4) Recovering the data from the transformation matrix. Ronald Goldman. + In "Graphics Gems II", pp 324-331. Morgan Kaufmann, 1991. +(5) Euler angle conversion. Ken Shoemake. + In "Graphics Gems IV", pp 222-229. Morgan Kaufmann, 1994. +(6) Arcball rotation control. Ken Shoemake. + In "Graphics Gems IV", pp 175-192. Morgan Kaufmann, 1994. +(7) Representing attitude: Euler angles, unit quaternions, and rotation + vectors. James Diebel. 2006. +(8) A discussion of the solution for the best rotation to relate two sets + of vectors. W Kabsch. Acta Cryst. 1978. A34, 827-828. +(9) Closed-form solution of absolute orientation using unit quaternions. + BKP Horn. J Opt Soc Am A. 1987. 4(4), 629-642. +(10) Quaternions. Ken Shoemake. + http://www.sfu.ca/~jwa3/cmpt461/files/quatut.pdf +(11) From quaternion to matrix and back. JMP van Waveren. 2005. + http://www.intel.com/cd/ids/developer/asmo-na/eng/293748.htm +(12) Uniform random rotations. Ken Shoemake. + In "Graphics Gems III", pp 124-132. Morgan Kaufmann, 1992. + + +Examples +-------- + +>>> alpha, beta, gamma = 0.123, -1.234, 2.345 +>>> origin, xaxis, yaxis, zaxis = (0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1) +>>> I = identity_matrix() +>>> Rx = rotation_matrix(alpha, xaxis) +>>> Ry = rotation_matrix(beta, yaxis) +>>> Rz = rotation_matrix(gamma, zaxis) +>>> R = concatenate_matrices(Rx, Ry, Rz) +>>> euler = euler_from_matrix(R, 'rxyz') +>>> numpy.allclose([alpha, beta, gamma], euler) +True +>>> Re = euler_matrix(alpha, beta, gamma, 'rxyz') +>>> is_same_transform(R, Re) +True +>>> al, be, ga = euler_from_matrix(Re, 'rxyz') +>>> is_same_transform(Re, euler_matrix(al, be, ga, 'rxyz')) +True +>>> qx = quaternion_about_axis(alpha, xaxis) +>>> qy = quaternion_about_axis(beta, yaxis) +>>> qz = quaternion_about_axis(gamma, zaxis) +>>> q = quaternion_multiply(qx, qy) +>>> q = quaternion_multiply(q, qz) +>>> Rq = quaternion_matrix(q) +>>> is_same_transform(R, Rq) +True +>>> S = scale_matrix(1.23, origin) +>>> T = translation_matrix((1, 2, 3)) +>>> Z = shear_matrix(beta, xaxis, origin, zaxis) +>>> R = random_rotation_matrix(numpy.random.rand(3)) +>>> M = concatenate_matrices(T, R, Z, S) +>>> scale, shear, angles, trans, persp = decompose_matrix(M) +>>> numpy.allclose(scale, 1.23) +True +>>> numpy.allclose(trans, (1, 2, 3)) +True +>>> numpy.allclose(shear, (0, math.tan(beta), 0)) +True +>>> is_same_transform(R, euler_matrix(axes='sxyz', *angles)) +True +>>> M1 = compose_matrix(scale, shear, angles, trans, persp) +>>> is_same_transform(M, M1) +True + +""" + +from __future__ import division + +import warnings +import math + +import numpy + +# Documentation in HTML format can be generated with Epydoc +__docformat__ = "restructuredtext en" + + +def identity_matrix(): + """Return 4x4 identity/unit matrix. + + >>> I = identity_matrix() + >>> numpy.allclose(I, numpy.dot(I, I)) + True + >>> numpy.sum(I), numpy.trace(I) + (4.0, 4.0) + >>> numpy.allclose(I, numpy.identity(4, dtype=numpy.float64)) + True + + """ + return numpy.identity(4, dtype=numpy.float64) + + +def translation_matrix(direction): + """Return matrix to translate by direction vector. + + >>> v = numpy.random.random(3) - 0.5 + >>> numpy.allclose(v, translation_matrix(v)[:3, 3]) + True + + """ + M = numpy.identity(4) + M[:3, 3] = direction[:3] + return M + + +def translation_from_matrix(matrix): + """Return translation vector from translation matrix. + + >>> v0 = numpy.random.random(3) - 0.5 + >>> v1 = translation_from_matrix(translation_matrix(v0)) + >>> numpy.allclose(v0, v1) + True + + """ + return numpy.array(matrix, copy=False)[:3, 3].copy() + + +def reflection_matrix(point, normal): + """Return matrix to mirror at plane defined by point and normal vector. + + >>> v0 = numpy.random.random(4) - 0.5 + >>> v0[3] = 1.0 + >>> v1 = numpy.random.random(3) - 0.5 + >>> R = reflection_matrix(v0, v1) + >>> numpy.allclose(2., numpy.trace(R)) + True + >>> numpy.allclose(v0, numpy.dot(R, v0)) + True + >>> v2 = v0.copy() + >>> v2[:3] += v1 + >>> v3 = v0.copy() + >>> v2[:3] -= v1 + >>> numpy.allclose(v2, numpy.dot(R, v3)) + True + + """ + normal = unit_vector(normal[:3]) + M = numpy.identity(4) + M[:3, :3] -= 2.0 * numpy.outer(normal, normal) + M[:3, 3] = (2.0 * numpy.dot(point[:3], normal)) * normal + return M + + +def reflection_from_matrix(matrix): + """Return mirror plane point and normal vector from reflection matrix. + + >>> v0 = numpy.random.random(3) - 0.5 + >>> v1 = numpy.random.random(3) - 0.5 + >>> M0 = reflection_matrix(v0, v1) + >>> point, normal = reflection_from_matrix(M0) + >>> M1 = reflection_matrix(point, normal) + >>> is_same_transform(M0, M1) + True + + """ + M = numpy.array(matrix, dtype=numpy.float64, copy=False) + # normal: unit eigenvector corresponding to eigenvalue -1 + l, V = numpy.linalg.eig(M[:3, :3]) + i = numpy.where(abs(numpy.real(l) + 1.0) < 1e-8)[0] + if not len(i): + raise ValueError("no unit eigenvector corresponding to eigenvalue -1") + normal = numpy.real(V[:, i[0]]).squeeze() + # point: any unit eigenvector corresponding to eigenvalue 1 + l, V = numpy.linalg.eig(M) + i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] + if not len(i): + raise ValueError("no unit eigenvector corresponding to eigenvalue 1") + point = numpy.real(V[:, i[-1]]).squeeze() + point /= point[3] + return point, normal + + +def rotation_matrix(angle, direction, point=None): + """Return matrix to rotate about axis defined by point and direction. + + >>> angle = (random.random() - 0.5) * (2*math.pi) + >>> direc = numpy.random.random(3) - 0.5 + >>> point = numpy.random.random(3) - 0.5 + >>> R0 = rotation_matrix(angle, direc, point) + >>> R1 = rotation_matrix(angle-2*math.pi, direc, point) + >>> is_same_transform(R0, R1) + True + >>> R0 = rotation_matrix(angle, direc, point) + >>> R1 = rotation_matrix(-angle, -direc, point) + >>> is_same_transform(R0, R1) + True + >>> I = numpy.identity(4, numpy.float64) + >>> numpy.allclose(I, rotation_matrix(math.pi*2, direc)) + True + >>> numpy.allclose(2., numpy.trace(rotation_matrix(math.pi/2, + ... direc, point))) + True + + """ + sina = math.sin(angle) + cosa = math.cos(angle) + direction = unit_vector(direction[:3]) + # rotation matrix around unit vector + R = numpy.array(((cosa, 0.0, 0.0), + (0.0, cosa, 0.0), + (0.0, 0.0, cosa)), dtype=numpy.float64) + R += numpy.outer(direction, direction) * (1.0 - cosa) + direction *= sina + R += numpy.array((( 0.0, -direction[2], direction[1]), + ( direction[2], 0.0, -direction[0]), + (-direction[1], direction[0], 0.0)), + dtype=numpy.float64) + M = numpy.identity(4) + M[:3, :3] = R + if point is not None: + # rotation not around origin + point = numpy.array(point[:3], dtype=numpy.float64, copy=False) + M[:3, 3] = point - numpy.dot(R, point) + return M + + +def rotation_from_matrix(matrix): + """Return rotation angle and axis from rotation matrix. + + >>> angle = (random.random() - 0.5) * (2*math.pi) + >>> direc = numpy.random.random(3) - 0.5 + >>> point = numpy.random.random(3) - 0.5 + >>> R0 = rotation_matrix(angle, direc, point) + >>> angle, direc, point = rotation_from_matrix(R0) + >>> R1 = rotation_matrix(angle, direc, point) + >>> is_same_transform(R0, R1) + True + + """ + R = numpy.array(matrix, dtype=numpy.float64, copy=False) + R33 = R[:3, :3] + # direction: unit eigenvector of R33 corresponding to eigenvalue of 1 + l, W = numpy.linalg.eig(R33.T) + i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] + if not len(i): + raise ValueError("no unit eigenvector corresponding to eigenvalue 1") + direction = numpy.real(W[:, i[-1]]).squeeze() + # point: unit eigenvector of R33 corresponding to eigenvalue of 1 + l, Q = numpy.linalg.eig(R) + i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] + if not len(i): + raise ValueError("no unit eigenvector corresponding to eigenvalue 1") + point = numpy.real(Q[:, i[-1]]).squeeze() + point /= point[3] + # rotation angle depending on direction + cosa = (numpy.trace(R33) - 1.0) / 2.0 + if abs(direction[2]) > 1e-8: + sina = (R[1, 0] + (cosa-1.0)*direction[0]*direction[1]) / direction[2] + elif abs(direction[1]) > 1e-8: + sina = (R[0, 2] + (cosa-1.0)*direction[0]*direction[2]) / direction[1] + else: + sina = (R[2, 1] + (cosa-1.0)*direction[1]*direction[2]) / direction[0] + angle = math.atan2(sina, cosa) + return angle, direction, point + + +def scale_matrix(factor, origin=None, direction=None): + """Return matrix to scale by factor around origin in direction. + + Use factor -1 for point symmetry. + + >>> v = (numpy.random.rand(4, 5) - 0.5) * 20.0 + >>> v[3] = 1.0 + >>> S = scale_matrix(-1.234) + >>> numpy.allclose(numpy.dot(S, v)[:3], -1.234*v[:3]) + True + >>> factor = random.random() * 10 - 5 + >>> origin = numpy.random.random(3) - 0.5 + >>> direct = numpy.random.random(3) - 0.5 + >>> S = scale_matrix(factor, origin) + >>> S = scale_matrix(factor, origin, direct) + + """ + if direction is None: + # uniform scaling + M = numpy.array(((factor, 0.0, 0.0, 0.0), + (0.0, factor, 0.0, 0.0), + (0.0, 0.0, factor, 0.0), + (0.0, 0.0, 0.0, 1.0)), dtype=numpy.float64) + if origin is not None: + M[:3, 3] = origin[:3] + M[:3, 3] *= 1.0 - factor + else: + # nonuniform scaling + direction = unit_vector(direction[:3]) + factor = 1.0 - factor + M = numpy.identity(4) + M[:3, :3] -= factor * numpy.outer(direction, direction) + if origin is not None: + M[:3, 3] = (factor * numpy.dot(origin[:3], direction)) * direction + return M + + +def scale_from_matrix(matrix): + """Return scaling factor, origin and direction from scaling matrix. + + >>> factor = random.random() * 10 - 5 + >>> origin = numpy.random.random(3) - 0.5 + >>> direct = numpy.random.random(3) - 0.5 + >>> S0 = scale_matrix(factor, origin) + >>> factor, origin, direction = scale_from_matrix(S0) + >>> S1 = scale_matrix(factor, origin, direction) + >>> is_same_transform(S0, S1) + True + >>> S0 = scale_matrix(factor, origin, direct) + >>> factor, origin, direction = scale_from_matrix(S0) + >>> S1 = scale_matrix(factor, origin, direction) + >>> is_same_transform(S0, S1) + True + + """ + M = numpy.array(matrix, dtype=numpy.float64, copy=False) + M33 = M[:3, :3] + factor = numpy.trace(M33) - 2.0 + try: + # direction: unit eigenvector corresponding to eigenvalue factor + l, V = numpy.linalg.eig(M33) + i = numpy.where(abs(numpy.real(l) - factor) < 1e-8)[0][0] + direction = numpy.real(V[:, i]).squeeze() + direction /= vector_norm(direction) + except IndexError: + # uniform scaling + factor = (factor + 2.0) / 3.0 + direction = None + # origin: any eigenvector corresponding to eigenvalue 1 + l, V = numpy.linalg.eig(M) + i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] + if not len(i): + raise ValueError("no eigenvector corresponding to eigenvalue 1") + origin = numpy.real(V[:, i[-1]]).squeeze() + origin /= origin[3] + return factor, origin, direction + + +def projection_matrix(point, normal, direction=None, + perspective=None, pseudo=False): + """Return matrix to project onto plane defined by point and normal. + + Using either perspective point, projection direction, or none of both. + + If pseudo is True, perspective projections will preserve relative depth + such that Perspective = dot(Orthogonal, PseudoPerspective). + + >>> P = projection_matrix((0, 0, 0), (1, 0, 0)) + >>> numpy.allclose(P[1:, 1:], numpy.identity(4)[1:, 1:]) + True + >>> point = numpy.random.random(3) - 0.5 + >>> normal = numpy.random.random(3) - 0.5 + >>> direct = numpy.random.random(3) - 0.5 + >>> persp = numpy.random.random(3) - 0.5 + >>> P0 = projection_matrix(point, normal) + >>> P1 = projection_matrix(point, normal, direction=direct) + >>> P2 = projection_matrix(point, normal, perspective=persp) + >>> P3 = projection_matrix(point, normal, perspective=persp, pseudo=True) + >>> is_same_transform(P2, numpy.dot(P0, P3)) + True + >>> P = projection_matrix((3, 0, 0), (1, 1, 0), (1, 0, 0)) + >>> v0 = (numpy.random.rand(4, 5) - 0.5) * 20.0 + >>> v0[3] = 1.0 + >>> v1 = numpy.dot(P, v0) + >>> numpy.allclose(v1[1], v0[1]) + True + >>> numpy.allclose(v1[0], 3.0-v1[1]) + True + + """ + M = numpy.identity(4) + point = numpy.array(point[:3], dtype=numpy.float64, copy=False) + normal = unit_vector(normal[:3]) + if perspective is not None: + # perspective projection + perspective = numpy.array(perspective[:3], dtype=numpy.float64, + copy=False) + M[0, 0] = M[1, 1] = M[2, 2] = numpy.dot(perspective-point, normal) + M[:3, :3] -= numpy.outer(perspective, normal) + if pseudo: + # preserve relative depth + M[:3, :3] -= numpy.outer(normal, normal) + M[:3, 3] = numpy.dot(point, normal) * (perspective+normal) + else: + M[:3, 3] = numpy.dot(point, normal) * perspective + M[3, :3] = -normal + M[3, 3] = numpy.dot(perspective, normal) + elif direction is not None: + # parallel projection + direction = numpy.array(direction[:3], dtype=numpy.float64, copy=False) + scale = numpy.dot(direction, normal) + M[:3, :3] -= numpy.outer(direction, normal) / scale + M[:3, 3] = direction * (numpy.dot(point, normal) / scale) + else: + # orthogonal projection + M[:3, :3] -= numpy.outer(normal, normal) + M[:3, 3] = numpy.dot(point, normal) * normal + return M + + +def projection_from_matrix(matrix, pseudo=False): + """Return projection plane and perspective point from projection matrix. + + Return values are same as arguments for projection_matrix function: + point, normal, direction, perspective, and pseudo. + + >>> point = numpy.random.random(3) - 0.5 + >>> normal = numpy.random.random(3) - 0.5 + >>> direct = numpy.random.random(3) - 0.5 + >>> persp = numpy.random.random(3) - 0.5 + >>> P0 = projection_matrix(point, normal) + >>> result = projection_from_matrix(P0) + >>> P1 = projection_matrix(*result) + >>> is_same_transform(P0, P1) + True + >>> P0 = projection_matrix(point, normal, direct) + >>> result = projection_from_matrix(P0) + >>> P1 = projection_matrix(*result) + >>> is_same_transform(P0, P1) + True + >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=False) + >>> result = projection_from_matrix(P0, pseudo=False) + >>> P1 = projection_matrix(*result) + >>> is_same_transform(P0, P1) + True + >>> P0 = projection_matrix(point, normal, perspective=persp, pseudo=True) + >>> result = projection_from_matrix(P0, pseudo=True) + >>> P1 = projection_matrix(*result) + >>> is_same_transform(P0, P1) + True + + """ + M = numpy.array(matrix, dtype=numpy.float64, copy=False) + M33 = M[:3, :3] + l, V = numpy.linalg.eig(M) + i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] + if not pseudo and len(i): + # point: any eigenvector corresponding to eigenvalue 1 + point = numpy.real(V[:, i[-1]]).squeeze() + point /= point[3] + # direction: unit eigenvector corresponding to eigenvalue 0 + l, V = numpy.linalg.eig(M33) + i = numpy.where(abs(numpy.real(l)) < 1e-8)[0] + if not len(i): + raise ValueError("no eigenvector corresponding to eigenvalue 0") + direction = numpy.real(V[:, i[0]]).squeeze() + direction /= vector_norm(direction) + # normal: unit eigenvector of M33.T corresponding to eigenvalue 0 + l, V = numpy.linalg.eig(M33.T) + i = numpy.where(abs(numpy.real(l)) < 1e-8)[0] + if len(i): + # parallel projection + normal = numpy.real(V[:, i[0]]).squeeze() + normal /= vector_norm(normal) + return point, normal, direction, None, False + else: + # orthogonal projection, where normal equals direction vector + return point, direction, None, None, False + else: + # perspective projection + i = numpy.where(abs(numpy.real(l)) > 1e-8)[0] + if not len(i): + raise ValueError( + "no eigenvector not corresponding to eigenvalue 0") + point = numpy.real(V[:, i[-1]]).squeeze() + point /= point[3] + normal = - M[3, :3] + perspective = M[:3, 3] / numpy.dot(point[:3], normal) + if pseudo: + perspective -= normal + return point, normal, None, perspective, pseudo + + +def clip_matrix(left, right, bottom, top, near, far, perspective=False): + """Return matrix to obtain normalized device coordinates from frustrum. + + The frustrum bounds are axis-aligned along x (left, right), + y (bottom, top) and z (near, far). + + Normalized device coordinates are in range [-1, 1] if coordinates are + inside the frustrum. + + If perspective is True the frustrum is a truncated pyramid with the + perspective point at origin and direction along z axis, otherwise an + orthographic canonical view volume (a box). + + Homogeneous coordinates transformed by the perspective clip matrix + need to be dehomogenized (devided by w coordinate). + + >>> frustrum = numpy.random.rand(6) + >>> frustrum[1] += frustrum[0] + >>> frustrum[3] += frustrum[2] + >>> frustrum[5] += frustrum[4] + >>> M = clip_matrix(*frustrum, perspective=False) + >>> numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0]) + array([-1., -1., -1., 1.]) + >>> numpy.dot(M, [frustrum[1], frustrum[3], frustrum[5], 1.0]) + array([ 1., 1., 1., 1.]) + >>> M = clip_matrix(*frustrum, perspective=True) + >>> v = numpy.dot(M, [frustrum[0], frustrum[2], frustrum[4], 1.0]) + >>> v / v[3] + array([-1., -1., -1., 1.]) + >>> v = numpy.dot(M, [frustrum[1], frustrum[3], frustrum[4], 1.0]) + >>> v / v[3] + array([ 1., 1., -1., 1.]) + + """ + if left >= right or bottom >= top or near >= far: + raise ValueError("invalid frustrum") + if perspective: + if near <= _EPS: + raise ValueError("invalid frustrum: near <= 0") + t = 2.0 * near + M = ((-t/(right-left), 0.0, (right+left)/(right-left), 0.0), + (0.0, -t/(top-bottom), (top+bottom)/(top-bottom), 0.0), + (0.0, 0.0, -(far+near)/(far-near), t*far/(far-near)), + (0.0, 0.0, -1.0, 0.0)) + else: + M = ((2.0/(right-left), 0.0, 0.0, (right+left)/(left-right)), + (0.0, 2.0/(top-bottom), 0.0, (top+bottom)/(bottom-top)), + (0.0, 0.0, 2.0/(far-near), (far+near)/(near-far)), + (0.0, 0.0, 0.0, 1.0)) + return numpy.array(M, dtype=numpy.float64) + + +def shear_matrix(angle, direction, point, normal): + """Return matrix to shear by angle along direction vector on shear plane. + + The shear plane is defined by a point and normal vector. The direction + vector must be orthogonal to the plane's normal vector. + + A point P is transformed by the shear matrix into P" such that + the vector P-P" is parallel to the direction vector and its extent is + given by the angle of P-P'-P", where P' is the orthogonal projection + of P onto the shear plane. + + >>> angle = (random.random() - 0.5) * 4*math.pi + >>> direct = numpy.random.random(3) - 0.5 + >>> point = numpy.random.random(3) - 0.5 + >>> normal = numpy.cross(direct, numpy.random.random(3)) + >>> S = shear_matrix(angle, direct, point, normal) + >>> numpy.allclose(1.0, numpy.linalg.det(S)) + True + + """ + normal = unit_vector(normal[:3]) + direction = unit_vector(direction[:3]) + if abs(numpy.dot(normal, direction)) > 1e-6: + raise ValueError("direction and normal vectors are not orthogonal") + angle = math.tan(angle) + M = numpy.identity(4) + M[:3, :3] += angle * numpy.outer(direction, normal) + M[:3, 3] = -angle * numpy.dot(point[:3], normal) * direction + return M + + +def shear_from_matrix(matrix): + """Return shear angle, direction and plane from shear matrix. + + >>> angle = (random.random() - 0.5) * 4*math.pi + >>> direct = numpy.random.random(3) - 0.5 + >>> point = numpy.random.random(3) - 0.5 + >>> normal = numpy.cross(direct, numpy.random.random(3)) + >>> S0 = shear_matrix(angle, direct, point, normal) + >>> angle, direct, point, normal = shear_from_matrix(S0) + >>> S1 = shear_matrix(angle, direct, point, normal) + >>> is_same_transform(S0, S1) + True + + """ + M = numpy.array(matrix, dtype=numpy.float64, copy=False) + M33 = M[:3, :3] + # normal: cross independent eigenvectors corresponding to the eigenvalue 1 + l, V = numpy.linalg.eig(M33) + i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-4)[0] + if len(i) < 2: + raise ValueError("No two linear independent eigenvectors found {}".format(l)) + V = numpy.real(V[:, i]).squeeze().T + lenorm = -1.0 + for i0, i1 in ((0, 1), (0, 2), (1, 2)): + n = numpy.cross(V[i0], V[i1]) + l = vector_norm(n) + if l > lenorm: + lenorm = l + normal = n + normal /= lenorm + # direction and angle + direction = numpy.dot(M33 - numpy.identity(3), normal) + angle = vector_norm(direction) + direction /= angle + angle = math.atan(angle) + # point: eigenvector corresponding to eigenvalue 1 + l, V = numpy.linalg.eig(M) + i = numpy.where(abs(numpy.real(l) - 1.0) < 1e-8)[0] + if not len(i): + raise ValueError("no eigenvector corresponding to eigenvalue 1") + point = numpy.real(V[:, i[-1]]).squeeze() + point /= point[3] + return angle, direction, point, normal + + +def decompose_matrix(matrix): + """Return sequence of transformations from transformation matrix. + + matrix : array_like + Non-degenerative homogeneous transformation matrix + + Return tuple of: + scale : vector of 3 scaling factors + shear : list of shear factors for x-y, x-z, y-z axes + angles : list of Euler angles about static x, y, z axes + translate : translation vector along x, y, z axes + perspective : perspective partition of matrix + + Raise ValueError if matrix is of wrong type or degenerative. + + >>> T0 = translation_matrix((1, 2, 3)) + >>> scale, shear, angles, trans, persp = decompose_matrix(T0) + >>> T1 = translation_matrix(trans) + >>> numpy.allclose(T0, T1) + True + >>> S = scale_matrix(0.123) + >>> scale, shear, angles, trans, persp = decompose_matrix(S) + >>> scale[0] + 0.123 + >>> R0 = euler_matrix(1, 2, 3) + >>> scale, shear, angles, trans, persp = decompose_matrix(R0) + >>> R1 = euler_matrix(*angles) + >>> numpy.allclose(R0, R1) + True + + """ + M = numpy.array(matrix, dtype=numpy.float64, copy=True).T + if abs(M[3, 3]) < _EPS: + raise ValueError("M[3, 3] is zero") + M /= M[3, 3] + P = M.copy() + P[:, 3] = 0, 0, 0, 1 + if not numpy.linalg.det(P): + raise ValueError("Matrix is singular") + + scale = numpy.zeros((3, ), dtype=numpy.float64) + shear = [0, 0, 0] + angles = [0, 0, 0] + + if any(abs(M[:3, 3]) > _EPS): + perspective = numpy.dot(M[:, 3], numpy.linalg.inv(P.T)) + M[:, 3] = 0, 0, 0, 1 + else: + perspective = numpy.array((0, 0, 0, 1), dtype=numpy.float64) + + translate = M[3, :3].copy() + M[3, :3] = 0 + + row = M[:3, :3].copy() + scale[0] = vector_norm(row[0]) + row[0] /= scale[0] + shear[0] = numpy.dot(row[0], row[1]) + row[1] -= row[0] * shear[0] + scale[1] = vector_norm(row[1]) + row[1] /= scale[1] + shear[0] /= scale[1] + shear[1] = numpy.dot(row[0], row[2]) + row[2] -= row[0] * shear[1] + shear[2] = numpy.dot(row[1], row[2]) + row[2] -= row[1] * shear[2] + scale[2] = vector_norm(row[2]) + row[2] /= scale[2] + shear[1:] /= scale[2] + + if numpy.dot(row[0], numpy.cross(row[1], row[2])) < 0: + scale *= -1 + row *= -1 + + angles[1] = math.asin(-row[0, 2]) + if math.cos(angles[1]): + angles[0] = math.atan2(row[1, 2], row[2, 2]) + angles[2] = math.atan2(row[0, 1], row[0, 0]) + else: + #angles[0] = math.atan2(row[1, 0], row[1, 1]) + angles[0] = math.atan2(-row[2, 1], row[1, 1]) + angles[2] = 0.0 + + return scale, shear, angles, translate, perspective + + +def compose_matrix(scale=None, shear=None, angles=None, translate=None, + perspective=None): + """Return transformation matrix from sequence of transformations. + + This is the inverse of the decompose_matrix function. + + Sequence of transformations: + scale : vector of 3 scaling factors + shear : list of shear factors for x-y, x-z, y-z axes + angles : list of Euler angles about static x, y, z axes + translate : translation vector along x, y, z axes + perspective : perspective partition of matrix + + >>> scale = numpy.random.random(3) - 0.5 + >>> shear = numpy.random.random(3) - 0.5 + >>> angles = (numpy.random.random(3) - 0.5) * (2*math.pi) + >>> trans = numpy.random.random(3) - 0.5 + >>> persp = numpy.random.random(4) - 0.5 + >>> M0 = compose_matrix(scale, shear, angles, trans, persp) + >>> result = decompose_matrix(M0) + >>> M1 = compose_matrix(*result) + >>> is_same_transform(M0, M1) + True + + """ + M = numpy.identity(4) + if perspective is not None: + P = numpy.identity(4) + P[3, :] = perspective[:4] + M = numpy.dot(M, P) + if translate is not None: + T = numpy.identity(4) + T[:3, 3] = translate[:3] + M = numpy.dot(M, T) + if angles is not None: + R = euler_matrix(angles[0], angles[1], angles[2], 'sxyz') + M = numpy.dot(M, R) + if shear is not None: + Z = numpy.identity(4) + Z[1, 2] = shear[2] + Z[0, 2] = shear[1] + Z[0, 1] = shear[0] + M = numpy.dot(M, Z) + if scale is not None: + S = numpy.identity(4) + S[0, 0] = scale[0] + S[1, 1] = scale[1] + S[2, 2] = scale[2] + M = numpy.dot(M, S) + M /= M[3, 3] + return M + + +def orthogonalization_matrix(lengths, angles): + """Return orthogonalization matrix for crystallographic cell coordinates. + + Angles are expected in degrees. + + The de-orthogonalization matrix is the inverse. + + >>> O = orthogonalization_matrix((10., 10., 10.), (90., 90., 90.)) + >>> numpy.allclose(O[:3, :3], numpy.identity(3, float) * 10) + True + >>> O = orthogonalization_matrix([9.8, 12.0, 15.5], [87.2, 80.7, 69.7]) + >>> numpy.allclose(numpy.sum(O), 43.063229) + True + + """ + a, b, c = lengths + angles = numpy.radians(angles) + sina, sinb, _ = numpy.sin(angles) + cosa, cosb, cosg = numpy.cos(angles) + co = (cosa * cosb - cosg) / (sina * sinb) + return numpy.array(( + ( a*sinb*math.sqrt(1.0-co*co), 0.0, 0.0, 0.0), + (-a*sinb*co, b*sina, 0.0, 0.0), + ( a*cosb, b*cosa, c, 0.0), + ( 0.0, 0.0, 0.0, 1.0)), + dtype=numpy.float64) + + +def superimposition_matrix(v0, v1, scaling=False, usesvd=True): + """Return matrix to transform given vector set into second vector set. + + v0 and v1 are shape (3, \*) or (4, \*) arrays of at least 3 vectors. + + If usesvd is True, the weighted sum of squared deviations (RMSD) is + minimized according to the algorithm by W. Kabsch [8]. Otherwise the + quaternion based algorithm by B. Horn [9] is used (slower when using + this Python implementation). + + The returned matrix performs rotation, translation and uniform scaling + (if specified). + + >>> v0 = numpy.random.rand(3, 10) + >>> M = superimposition_matrix(v0, v0) + >>> numpy.allclose(M, numpy.identity(4)) + True + >>> R = random_rotation_matrix(numpy.random.random(3)) + >>> v0 = ((1,0,0), (0,1,0), (0,0,1), (1,1,1)) + >>> v1 = numpy.dot(R, v0) + >>> M = superimposition_matrix(v0, v1) + >>> numpy.allclose(v1, numpy.dot(M, v0)) + True + >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20.0 + >>> v0[3] = 1.0 + >>> v1 = numpy.dot(R, v0) + >>> M = superimposition_matrix(v0, v1) + >>> numpy.allclose(v1, numpy.dot(M, v0)) + True + >>> S = scale_matrix(random.random()) + >>> T = translation_matrix(numpy.random.random(3)-0.5) + >>> M = concatenate_matrices(T, R, S) + >>> v1 = numpy.dot(M, v0) + >>> v0[:3] += numpy.random.normal(0.0, 1e-9, 300).reshape(3, -1) + >>> M = superimposition_matrix(v0, v1, scaling=True) + >>> numpy.allclose(v1, numpy.dot(M, v0)) + True + >>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False) + >>> numpy.allclose(v1, numpy.dot(M, v0)) + True + >>> v = numpy.empty((4, 100, 3), dtype=numpy.float64) + >>> v[:, :, 0] = v0 + >>> M = superimposition_matrix(v0, v1, scaling=True, usesvd=False) + >>> numpy.allclose(v1, numpy.dot(M, v[:, :, 0])) + True + + """ + v0 = numpy.array(v0, dtype=numpy.float64, copy=False)[:3] + v1 = numpy.array(v1, dtype=numpy.float64, copy=False)[:3] + + if v0.shape != v1.shape or v0.shape[1] < 3: + raise ValueError("Vector sets are of wrong shape or type.") + + # move centroids to origin + t0 = numpy.mean(v0, axis=1) + t1 = numpy.mean(v1, axis=1) + v0 = v0 - t0.reshape(3, 1) + v1 = v1 - t1.reshape(3, 1) + + if usesvd: + # Singular Value Decomposition of covariance matrix + u, s, vh = numpy.linalg.svd(numpy.dot(v1, v0.T)) + # rotation matrix from SVD orthonormal bases + R = numpy.dot(u, vh) + if numpy.linalg.det(R) < 0.0: + # R does not constitute right handed system + R -= numpy.outer(u[:, 2], vh[2, :]*2.0) + s[-1] *= -1.0 + # homogeneous transformation matrix + M = numpy.identity(4) + M[:3, :3] = R + else: + # compute symmetric matrix N + xx, yy, zz = numpy.sum(v0 * v1, axis=1) + xy, yz, zx = numpy.sum(v0 * numpy.roll(v1, -1, axis=0), axis=1) + xz, yx, zy = numpy.sum(v0 * numpy.roll(v1, -2, axis=0), axis=1) + N = ((xx+yy+zz, yz-zy, zx-xz, xy-yx), + (yz-zy, xx-yy-zz, xy+yx, zx+xz), + (zx-xz, xy+yx, -xx+yy-zz, yz+zy), + (xy-yx, zx+xz, yz+zy, -xx-yy+zz)) + # quaternion: eigenvector corresponding to most positive eigenvalue + l, V = numpy.linalg.eig(N) + q = V[:, numpy.argmax(l)] + q /= vector_norm(q) # unit quaternion + q = numpy.roll(q, -1) # move w component to end + # homogeneous transformation matrix + M = quaternion_matrix(q) + + # scale: ratio of rms deviations from centroid + if scaling: + v0 *= v0 + v1 *= v1 + M[:3, :3] *= math.sqrt(numpy.sum(v1) / numpy.sum(v0)) + + # translation + M[:3, 3] = t1 + T = numpy.identity(4) + T[:3, 3] = -t0 + M = numpy.dot(M, T) + return M + + +def euler_matrix(ai, aj, ak, axes='sxyz'): + """Return homogeneous rotation matrix from Euler angles and axis sequence. + + ai, aj, ak : Euler's roll, pitch and yaw angles + axes : One of 24 axis sequences as string or encoded tuple + + >>> R = euler_matrix(1, 2, 3, 'syxz') + >>> numpy.allclose(numpy.sum(R[0]), -1.34786452) + True + >>> R = euler_matrix(1, 2, 3, (0, 1, 0, 1)) + >>> numpy.allclose(numpy.sum(R[0]), -0.383436184) + True + >>> ai, aj, ak = (4.0*math.pi) * (numpy.random.random(3) - 0.5) + >>> for axes in _AXES2TUPLE.keys(): + ... R = euler_matrix(ai, aj, ak, axes) + >>> for axes in _TUPLE2AXES.keys(): + ... R = euler_matrix(ai, aj, ak, axes) + + """ + try: + firstaxis, parity, repetition, frame = _AXES2TUPLE[axes] + except (AttributeError, KeyError): + _ = _TUPLE2AXES[axes] + firstaxis, parity, repetition, frame = axes + + i = firstaxis + j = _NEXT_AXIS[i+parity] + k = _NEXT_AXIS[i-parity+1] + + if frame: + ai, ak = ak, ai + if parity: + ai, aj, ak = -ai, -aj, -ak + + si, sj, sk = math.sin(ai), math.sin(aj), math.sin(ak) + ci, cj, ck = math.cos(ai), math.cos(aj), math.cos(ak) + cc, cs = ci*ck, ci*sk + sc, ss = si*ck, si*sk + + M = numpy.identity(4) + if repetition: + M[i, i] = cj + M[i, j] = sj*si + M[i, k] = sj*ci + M[j, i] = sj*sk + M[j, j] = -cj*ss+cc + M[j, k] = -cj*cs-sc + M[k, i] = -sj*ck + M[k, j] = cj*sc+cs + M[k, k] = cj*cc-ss + else: + M[i, i] = cj*ck + M[i, j] = sj*sc-cs + M[i, k] = sj*cc+ss + M[j, i] = cj*sk + M[j, j] = sj*ss+cc + M[j, k] = sj*cs-sc + M[k, i] = -sj + M[k, j] = cj*si + M[k, k] = cj*ci + return M + + +def euler_from_matrix(matrix, axes='sxyz'): + """Return Euler angles from rotation matrix for specified axis sequence. + + axes : One of 24 axis sequences as string or encoded tuple + + Note that many Euler angle triplets can describe one matrix. + + >>> R0 = euler_matrix(1, 2, 3, 'syxz') + >>> al, be, ga = euler_from_matrix(R0, 'syxz') + >>> R1 = euler_matrix(al, be, ga, 'syxz') + >>> numpy.allclose(R0, R1) + True + >>> angles = (4.0*math.pi) * (numpy.random.random(3) - 0.5) + >>> for axes in _AXES2TUPLE.keys(): + ... R0 = euler_matrix(axes=axes, *angles) + ... R1 = euler_matrix(axes=axes, *euler_from_matrix(R0, axes)) + ... if not numpy.allclose(R0, R1): print axes, "failed" + + """ + try: + firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()] + except (AttributeError, KeyError): + _ = _TUPLE2AXES[axes] + firstaxis, parity, repetition, frame = axes + + i = firstaxis + j = _NEXT_AXIS[i+parity] + k = _NEXT_AXIS[i-parity+1] + + M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:3, :3] + if repetition: + sy = math.sqrt(M[i, j]*M[i, j] + M[i, k]*M[i, k]) + if sy > _EPS: + ax = math.atan2( M[i, j], M[i, k]) + ay = math.atan2( sy, M[i, i]) + az = math.atan2( M[j, i], -M[k, i]) + else: + ax = math.atan2(-M[j, k], M[j, j]) + ay = math.atan2( sy, M[i, i]) + az = 0.0 + else: + cy = math.sqrt(M[i, i]*M[i, i] + M[j, i]*M[j, i]) + if cy > _EPS: + ax = math.atan2( M[k, j], M[k, k]) + ay = math.atan2(-M[k, i], cy) + az = math.atan2( M[j, i], M[i, i]) + else: + ax = math.atan2(-M[j, k], M[j, j]) + ay = math.atan2(-M[k, i], cy) + az = 0.0 + + if parity: + ax, ay, az = -ax, -ay, -az + if frame: + ax, az = az, ax + return ax, ay, az + + +def euler_from_quaternion(quaternion, axes='sxyz'): + """Return Euler angles from quaternion for specified axis sequence. + + >>> angles = euler_from_quaternion([0.06146124, 0, 0, 0.99810947]) + >>> numpy.allclose(angles, [0.123, 0, 0]) + True + + """ + return euler_from_matrix(quaternion_matrix(quaternion), axes) + + +def quaternion_from_euler(ai, aj, ak, axes='sxyz'): + """Return quaternion from Euler angles and axis sequence. + + ai, aj, ak : Euler's roll, pitch and yaw angles + axes : One of 24 axis sequences as string or encoded tuple + + >>> q = quaternion_from_euler(1, 2, 3, 'ryxz') + >>> numpy.allclose(q, [0.310622, -0.718287, 0.444435, 0.435953]) + True + + """ + try: + firstaxis, parity, repetition, frame = _AXES2TUPLE[axes.lower()] + except (AttributeError, KeyError): + _ = _TUPLE2AXES[axes] + firstaxis, parity, repetition, frame = axes + + i = firstaxis + j = _NEXT_AXIS[i+parity] + k = _NEXT_AXIS[i-parity+1] + + if frame: + ai, ak = ak, ai + if parity: + aj = -aj + + ai = ai / 2.0 + aj = aj / 2.0 + ak = ak / 2.0 + ci = math.cos(ai) + si = math.sin(ai) + cj = math.cos(aj) + sj = math.sin(aj) + ck = math.cos(ak) + sk = math.sin(ak) + cc = ci*ck + cs = ci*sk + sc = si*ck + ss = si*sk + + quaternion = numpy.empty((4, ), dtype=numpy.float64) + if repetition: + quaternion[i] = cj*(cs + sc) + quaternion[j] = sj*(cc + ss) + quaternion[k] = sj*(cs - sc) + quaternion[3] = cj*(cc - ss) + else: + quaternion[i] = cj*sc - sj*cs + quaternion[j] = cj*ss + sj*cc + quaternion[k] = cj*cs - sj*sc + quaternion[3] = cj*cc + sj*ss + if parity: + quaternion[j] *= -1 + + return quaternion + + +def quaternion_about_axis(angle, axis): + """Return quaternion for rotation about axis. + + >>> q = quaternion_about_axis(0.123, (1, 0, 0)) + >>> numpy.allclose(q, [0.06146124, 0, 0, 0.99810947]) + True + + """ + quaternion = numpy.zeros((4, ), dtype=numpy.float64) + quaternion[:3] = axis[:3] + qlen = vector_norm(quaternion) + if qlen > _EPS: + quaternion *= math.sin(angle/2.0) / qlen + quaternion[3] = math.cos(angle/2.0) + return quaternion + + +def quaternion_matrix(quaternion): + """Return homogeneous rotation matrix from quaternion. + + >>> R = quaternion_matrix([0.06146124, 0, 0, 0.99810947]) + >>> numpy.allclose(R, rotation_matrix(0.123, (1, 0, 0))) + True + + """ + q = numpy.array(quaternion[:4], dtype=numpy.float64, copy=True) + nq = numpy.dot(q, q) + if nq < _EPS: + return numpy.identity(4) + q *= math.sqrt(2.0 / nq) + q = numpy.outer(q, q) + return numpy.array(( + (1.0-q[1, 1]-q[2, 2], q[0, 1]-q[2, 3], q[0, 2]+q[1, 3], 0.0), + ( q[0, 1]+q[2, 3], 1.0-q[0, 0]-q[2, 2], q[1, 2]-q[0, 3], 0.0), + ( q[0, 2]-q[1, 3], q[1, 2]+q[0, 3], 1.0-q[0, 0]-q[1, 1], 0.0), + ( 0.0, 0.0, 0.0, 1.0) + ), dtype=numpy.float64) + + +def quaternion_from_matrix(matrix): + """Return quaternion from rotation matrix. + + >>> R = rotation_matrix(0.123, (1, 2, 3)) + >>> q = quaternion_from_matrix(R) + >>> numpy.allclose(q, [0.0164262, 0.0328524, 0.0492786, 0.9981095]) + True + + """ + q = numpy.empty((4, ), dtype=numpy.float64) + M = numpy.array(matrix, dtype=numpy.float64, copy=False)[:4, :4] + t = numpy.trace(M) + if t > M[3, 3]: + q[3] = t + q[2] = M[1, 0] - M[0, 1] + q[1] = M[0, 2] - M[2, 0] + q[0] = M[2, 1] - M[1, 2] + else: + i, j, k = 0, 1, 2 + if M[1, 1] > M[0, 0]: + i, j, k = 1, 2, 0 + if M[2, 2] > M[i, i]: + i, j, k = 2, 0, 1 + t = M[i, i] - (M[j, j] + M[k, k]) + M[3, 3] + q[i] = t + q[j] = M[i, j] + M[j, i] + q[k] = M[k, i] + M[i, k] + q[3] = M[k, j] - M[j, k] + q *= 0.5 / math.sqrt(t * M[3, 3]) + return q + + +def quaternion_multiply(quaternion1, quaternion0): + """Return multiplication of two quaternions. + + >>> q = quaternion_multiply([1, -2, 3, 4], [-5, 6, 7, 8]) + >>> numpy.allclose(q, [-44, -14, 48, 28]) + True + + """ + x0, y0, z0, w0 = quaternion0 + x1, y1, z1, w1 = quaternion1 + return numpy.array(( + x1*w0 + y1*z0 - z1*y0 + w1*x0, + -x1*z0 + y1*w0 + z1*x0 + w1*y0, + x1*y0 - y1*x0 + z1*w0 + w1*z0, + -x1*x0 - y1*y0 - z1*z0 + w1*w0), dtype=numpy.float64) + + +def quaternion_conjugate(quaternion): + """Return conjugate of quaternion. + + >>> q0 = random_quaternion() + >>> q1 = quaternion_conjugate(q0) + >>> q1[3] == q0[3] and all(q1[:3] == -q0[:3]) + True + + """ + return numpy.array((-quaternion[0], -quaternion[1], + -quaternion[2], quaternion[3]), dtype=numpy.float64) + + +def quaternion_inverse(quaternion): + """Return inverse of quaternion. + + >>> q0 = random_quaternion() + >>> q1 = quaternion_inverse(q0) + >>> numpy.allclose(quaternion_multiply(q0, q1), [0, 0, 0, 1]) + True + + """ + return quaternion_conjugate(quaternion) / numpy.dot(quaternion, quaternion) + + +def quaternion_slerp(quat0, quat1, fraction, spin=0, shortestpath=True): + """Return spherical linear interpolation between two quaternions. + + >>> q0 = random_quaternion() + >>> q1 = random_quaternion() + >>> q = quaternion_slerp(q0, q1, 0.0) + >>> numpy.allclose(q, q0) + True + >>> q = quaternion_slerp(q0, q1, 1.0, 1) + >>> numpy.allclose(q, q1) + True + >>> q = quaternion_slerp(q0, q1, 0.5) + >>> angle = math.acos(numpy.dot(q0, q)) + >>> numpy.allclose(2.0, math.acos(numpy.dot(q0, q1)) / angle) or \ + numpy.allclose(2.0, math.acos(-numpy.dot(q0, q1)) / angle) + True + + """ + q0 = unit_vector(quat0[:4]) + q1 = unit_vector(quat1[:4]) + if fraction == 0.0: + return q0 + elif fraction == 1.0: + return q1 + d = numpy.dot(q0, q1) + if abs(abs(d) - 1.0) < _EPS: + return q0 + if shortestpath and d < 0.0: + # invert rotation + d = -d + q1 *= -1.0 + angle = math.acos(d) + spin * math.pi + if abs(angle) < _EPS: + return q0 + isin = 1.0 / math.sin(angle) + q0 *= math.sin((1.0 - fraction) * angle) * isin + q1 *= math.sin(fraction * angle) * isin + q0 += q1 + return q0 + + +def random_quaternion(rand=None): + """Return uniform random unit quaternion. + + rand: array like or None + Three independent random variables that are uniformly distributed + between 0 and 1. + + >>> q = random_quaternion() + >>> numpy.allclose(1.0, vector_norm(q)) + True + >>> q = random_quaternion(numpy.random.random(3)) + >>> q.shape + (4,) + + """ + if rand is None: + rand = numpy.random.rand(3) + else: + assert len(rand) == 3 + r1 = numpy.sqrt(1.0 - rand[0]) + r2 = numpy.sqrt(rand[0]) + pi2 = math.pi * 2.0 + t1 = pi2 * rand[1] + t2 = pi2 * rand[2] + return numpy.array((numpy.sin(t1)*r1, + numpy.cos(t1)*r1, + numpy.sin(t2)*r2, + numpy.cos(t2)*r2), dtype=numpy.float64) + + +def random_rotation_matrix(rand=None): + """Return uniform random rotation matrix. + + rnd: array like + Three independent random variables that are uniformly distributed + between 0 and 1 for each returned quaternion. + + >>> R = random_rotation_matrix() + >>> numpy.allclose(numpy.dot(R.T, R), numpy.identity(4)) + True + + """ + return quaternion_matrix(random_quaternion(rand)) + + +class Arcball(object): + """Virtual Trackball Control. + + >>> ball = Arcball() + >>> ball = Arcball(initial=numpy.identity(4)) + >>> ball.place([320, 320], 320) + >>> ball.down([500, 250]) + >>> ball.drag([475, 275]) + >>> R = ball.matrix() + >>> numpy.allclose(numpy.sum(R), 3.90583455) + True + >>> ball = Arcball(initial=[0, 0, 0, 1]) + >>> ball.place([320, 320], 320) + >>> ball.setaxes([1,1,0], [-1, 1, 0]) + >>> ball.setconstrain(True) + >>> ball.down([400, 200]) + >>> ball.drag([200, 400]) + >>> R = ball.matrix() + >>> numpy.allclose(numpy.sum(R), 0.2055924) + True + >>> ball.next() + + """ + + def __init__(self, initial=None): + """Initialize virtual trackball control. + + initial : quaternion or rotation matrix + + """ + self._axis = None + self._axes = None + self._radius = 1.0 + self._center = [0.0, 0.0] + self._vdown = numpy.array([0, 0, 1], dtype=numpy.float64) + self._constrain = False + + if initial is None: + self._qdown = numpy.array([0, 0, 0, 1], dtype=numpy.float64) + else: + initial = numpy.array(initial, dtype=numpy.float64) + if initial.shape == (4, 4): + self._qdown = quaternion_from_matrix(initial) + elif initial.shape == (4, ): + initial /= vector_norm(initial) + self._qdown = initial + else: + raise ValueError("initial not a quaternion or matrix.") + + self._qnow = self._qpre = self._qdown + + def place(self, center, radius): + """Place Arcball, e.g. when window size changes. + + center : sequence[2] + Window coordinates of trackball center. + radius : float + Radius of trackball in window coordinates. + + """ + self._radius = float(radius) + self._center[0] = center[0] + self._center[1] = center[1] + + def setaxes(self, *axes): + """Set axes to constrain rotations.""" + if axes is None: + self._axes = None + else: + self._axes = [unit_vector(axis) for axis in axes] + + def setconstrain(self, constrain): + """Set state of constrain to axis mode.""" + self._constrain = constrain == True + + def getconstrain(self): + """Return state of constrain to axis mode.""" + return self._constrain + + def down(self, point): + """Set initial cursor window coordinates and pick constrain-axis.""" + self._vdown = arcball_map_to_sphere(point, self._center, self._radius) + self._qdown = self._qpre = self._qnow + + if self._constrain and self._axes is not None: + self._axis = arcball_nearest_axis(self._vdown, self._axes) + self._vdown = arcball_constrain_to_axis(self._vdown, self._axis) + else: + self._axis = None + + def drag(self, point): + """Update current cursor window coordinates.""" + vnow = arcball_map_to_sphere(point, self._center, self._radius) + + if self._axis is not None: + vnow = arcball_constrain_to_axis(vnow, self._axis) + + self._qpre = self._qnow + + t = numpy.cross(self._vdown, vnow) + if numpy.dot(t, t) < _EPS: + self._qnow = self._qdown + else: + q = [t[0], t[1], t[2], numpy.dot(self._vdown, vnow)] + self._qnow = quaternion_multiply(q, self._qdown) + + def next(self, acceleration=0.0): + """Continue rotation in direction of last drag.""" + q = quaternion_slerp(self._qpre, self._qnow, 2.0+acceleration, False) + self._qpre, self._qnow = self._qnow, q + + def matrix(self): + """Return homogeneous rotation matrix.""" + return quaternion_matrix(self._qnow) + + +def arcball_map_to_sphere(point, center, radius): + """Return unit sphere coordinates from window coordinates.""" + v = numpy.array(((point[0] - center[0]) / radius, + (center[1] - point[1]) / radius, + 0.0), dtype=numpy.float64) + n = v[0]*v[0] + v[1]*v[1] + if n > 1.0: + v /= math.sqrt(n) # position outside of sphere + else: + v[2] = math.sqrt(1.0 - n) + return v + + +def arcball_constrain_to_axis(point, axis): + """Return sphere point perpendicular to axis.""" + v = numpy.array(point, dtype=numpy.float64, copy=True) + a = numpy.array(axis, dtype=numpy.float64, copy=True) + v -= a * numpy.dot(a, v) # on plane + n = vector_norm(v) + if n > _EPS: + if v[2] < 0.0: + v *= -1.0 + v /= n + return v + if a[2] == 1.0: + return numpy.array([1, 0, 0], dtype=numpy.float64) + return unit_vector([-a[1], a[0], 0]) + + +def arcball_nearest_axis(point, axes): + """Return axis, which arc is nearest to point.""" + point = numpy.array(point, dtype=numpy.float64, copy=False) + nearest = None + mx = -1.0 + for axis in axes: + t = numpy.dot(arcball_constrain_to_axis(point, axis), point) + if t > mx: + nearest = axis + mx = t + return nearest + + +# epsilon for testing whether a number is close to zero +_EPS = numpy.finfo(float).eps * 4.0 + +# axis sequences for Euler angles +_NEXT_AXIS = [1, 2, 0, 1] + +# map axes strings to/from tuples of inner axis, parity, repetition, frame +_AXES2TUPLE = { + 'sxyz': (0, 0, 0, 0), 'sxyx': (0, 0, 1, 0), 'sxzy': (0, 1, 0, 0), + 'sxzx': (0, 1, 1, 0), 'syzx': (1, 0, 0, 0), 'syzy': (1, 0, 1, 0), + 'syxz': (1, 1, 0, 0), 'syxy': (1, 1, 1, 0), 'szxy': (2, 0, 0, 0), + 'szxz': (2, 0, 1, 0), 'szyx': (2, 1, 0, 0), 'szyz': (2, 1, 1, 0), + 'rzyx': (0, 0, 0, 1), 'rxyx': (0, 0, 1, 1), 'ryzx': (0, 1, 0, 1), + 'rxzx': (0, 1, 1, 1), 'rxzy': (1, 0, 0, 1), 'ryzy': (1, 0, 1, 1), + 'rzxy': (1, 1, 0, 1), 'ryxy': (1, 1, 1, 1), 'ryxz': (2, 0, 0, 1), + 'rzxz': (2, 0, 1, 1), 'rxyz': (2, 1, 0, 1), 'rzyz': (2, 1, 1, 1)} + +_TUPLE2AXES = dict((v, k) for k, v in _AXES2TUPLE.items()) + +# helper functions + +def vector_norm(data, axis=None, out=None): + """Return length, i.e. eucledian norm, of ndarray along axis. + + >>> v = numpy.random.random(3) + >>> n = vector_norm(v) + >>> numpy.allclose(n, numpy.linalg.norm(v)) + True + >>> v = numpy.random.rand(6, 5, 3) + >>> n = vector_norm(v, axis=-1) + >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=2))) + True + >>> n = vector_norm(v, axis=1) + >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1))) + True + >>> v = numpy.random.rand(5, 4, 3) + >>> n = numpy.empty((5, 3), dtype=numpy.float64) + >>> vector_norm(v, axis=1, out=n) + >>> numpy.allclose(n, numpy.sqrt(numpy.sum(v*v, axis=1))) + True + >>> vector_norm([]) + 0.0 + >>> vector_norm([1.0]) + 1.0 + + """ + data = numpy.array(data, dtype=numpy.float64, copy=True) + if out is None: + if data.ndim == 1: + return math.sqrt(numpy.dot(data, data)) + data *= data + out = numpy.atleast_1d(numpy.sum(data, axis=axis)) + numpy.sqrt(out, out) + return out + else: + data *= data + numpy.sum(data, axis=axis, out=out) + numpy.sqrt(out, out) + + +def unit_vector(data, axis=None, out=None): + """Return ndarray normalized by length, i.e. eucledian norm, along axis. + + >>> v0 = numpy.random.random(3) + >>> v1 = unit_vector(v0) + >>> numpy.allclose(v1, v0 / numpy.linalg.norm(v0)) + True + >>> v0 = numpy.random.rand(5, 4, 3) + >>> v1 = unit_vector(v0, axis=-1) + >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=2)), 2) + >>> numpy.allclose(v1, v2) + True + >>> v1 = unit_vector(v0, axis=1) + >>> v2 = v0 / numpy.expand_dims(numpy.sqrt(numpy.sum(v0*v0, axis=1)), 1) + >>> numpy.allclose(v1, v2) + True + >>> v1 = numpy.empty((5, 4, 3), dtype=numpy.float64) + >>> unit_vector(v0, axis=1, out=v1) + >>> numpy.allclose(v1, v2) + True + >>> list(unit_vector([])) + [] + >>> list(unit_vector([1.0])) + [1.0] + + """ + if out is None: + data = numpy.array(data, dtype=numpy.float64, copy=True) + if data.ndim == 1: + data /= math.sqrt(numpy.dot(data, data)) + return data + else: + if out is not data: + out[:] = numpy.array(data, copy=False) + data = out + length = numpy.atleast_1d(numpy.sum(data*data, axis)) + numpy.sqrt(length, length) + if axis is not None: + length = numpy.expand_dims(length, axis) + data /= length + if out is None: + return data + + +def random_vector(size): + """Return array of random doubles in the half-open interval [0.0, 1.0). + + >>> v = random_vector(10000) + >>> numpy.all(v >= 0.0) and numpy.all(v < 1.0) + True + >>> v0 = random_vector(10) + >>> v1 = random_vector(10) + >>> numpy.any(v0 == v1) + False + + """ + return numpy.random.random(size) + + +def inverse_matrix(matrix): + """Return inverse of square transformation matrix. + + >>> M0 = random_rotation_matrix() + >>> M1 = inverse_matrix(M0.T) + >>> numpy.allclose(M1, numpy.linalg.inv(M0.T)) + True + >>> for size in range(1, 7): + ... M0 = numpy.random.rand(size, size) + ... M1 = inverse_matrix(M0) + ... if not numpy.allclose(M1, numpy.linalg.inv(M0)): print size + + """ + return numpy.linalg.inv(matrix) + + +def concatenate_matrices(*matrices): + """Return concatenation of series of transformation matrices. + + >>> M = numpy.random.rand(16).reshape((4, 4)) - 0.5 + >>> numpy.allclose(M, concatenate_matrices(M)) + True + >>> numpy.allclose(numpy.dot(M, M.T), concatenate_matrices(M, M.T)) + True + + """ + M = numpy.identity(4) + for i in matrices: + M = numpy.dot(M, i) + return M + + +def is_same_transform(matrix0, matrix1): + """Return True if two matrices perform same transformation. + + >>> is_same_transform(numpy.identity(4), numpy.identity(4)) + True + >>> is_same_transform(numpy.identity(4), random_rotation_matrix()) + False + + """ + matrix0 = numpy.array(matrix0, dtype=numpy.float64, copy=True) + matrix0 /= matrix0[3, 3] + matrix1 = numpy.array(matrix1, dtype=numpy.float64, copy=True) + matrix1 /= matrix1[3, 3] + return numpy.allclose(matrix0, matrix1) + + +def _import_module(module_name, warn=True, prefix='_py_', ignore='_'): + """Try import all public attributes from module into global namespace. + + Existing attributes with name clashes are renamed with prefix. + Attributes starting with underscore are ignored by default. + + Return True on successful import. + + """ + try: + module = __import__(module_name) + except ImportError: + if warn: + warnings.warn("Failed to import module " + module_name) + else: + for attr in dir(module): + if ignore and attr.startswith(ignore): + continue + if prefix: + if attr in globals(): + globals()[prefix + attr] = globals()[attr] + elif warn: + warnings.warn("No Python implementation of " + attr) + globals()[attr] = getattr(module, attr) + return True \ No newline at end of file diff --git a/src/rktl_dependencies/setup.cfg b/src/rktl_dependencies/setup.cfg new file mode 100644 index 000000000..064c689bf --- /dev/null +++ b/src/rktl_dependencies/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rktl_dependencies +[install] +install_scripts=$base/lib/rktl_dependencies diff --git a/src/rktl_dependencies/setup.py b/src/rktl_dependencies/setup.py new file mode 100644 index 000000000..15152f480 --- /dev/null +++ b/src/rktl_dependencies/setup.py @@ -0,0 +1,28 @@ +from glob import glob +import os +from setuptools import find_packages, setup + +package_name = 'rktl_dependencies' + +setup( + name=package_name, + version='1.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('lib', package_name), glob(os.path.join(package_name, '*.py'))), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Adam Schmok', + maintainer_email='aschmok@purdue.edu', + description='Contains external dependencies required for other packages.', + license='BSD 3 Clause', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + ], + }, +) diff --git a/src/rktl_dependencies/test/test_copyright.py b/src/rktl_dependencies/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/src/rktl_dependencies/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/rktl_dependencies/test/test_flake8.py b/src/rktl_dependencies/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/src/rktl_dependencies/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/rktl_dependencies/test/test_pep257.py b/src/rktl_dependencies/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/src/rktl_dependencies/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/rktl_game/README.md b/src/rktl_game/README.md similarity index 100% rename from rktl_game/README.md rename to src/rktl_game/README.md diff --git a/rktl_game/launch/game.launch b/src/rktl_game/launch/game.launch similarity index 100% rename from rktl_game/launch/game.launch rename to src/rktl_game/launch/game.launch diff --git a/src/rktl_game/launch/game.launch.py b/src/rktl_game/launch/game.launch.py new file mode 100644 index 000000000..2e3bc2e29 --- /dev/null +++ b/src/rktl_game/launch/game.launch.py @@ -0,0 +1,28 @@ +import os +import launch +import launch_ros.actions +from launch.launch_description_sources import PythonLaunchDescriptionSource +from ament_index_python.packages import get_package_share_directory +from launch.launch_description_sources import get_launch_description_from_any_launch_file + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch_ros.actions.Node( + package='rktl_game', + executable='game_manager', + name='game_manager', + output='screen' + ), + launch.actions.IncludeLaunchDescription( + launch.launch_description_sources.AnyLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rosbridge_server'), 'launch/rosbridge_websocket_launch.xml') + ) + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() diff --git a/rktl_game/package.xml b/src/rktl_game/package.xml similarity index 54% rename from rktl_game/package.xml rename to src/rktl_game/package.xml index d85d280de..7e31e3eeb 100644 --- a/rktl_game/package.xml +++ b/src/rktl_game/package.xml @@ -1,5 +1,6 @@ - + + rktl_game 0.0.0 Game manager for ARC Rocket League project @@ -8,11 +9,19 @@ http://www.purduearc.com AJ Ernandes - catkin - roscpp - rospy + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + rclpy nodelet rosbridge_server nav_msgs rktl_msgs + ros2launch + + + ament_python + diff --git a/src/rktl_game/resource/rktl_game b/src/rktl_game/resource/rktl_game new file mode 100644 index 000000000..e69de29bb diff --git a/src/rktl_game/rktl_game/__init__.py b/src/rktl_game/rktl_game/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rktl_game/nodes/README.md b/src/rktl_game/rktl_game/nodes/README.md similarity index 100% rename from rktl_game/nodes/README.md rename to src/rktl_game/rktl_game/nodes/README.md diff --git a/rktl_game/nodes/game_manager b/src/rktl_game/rktl_game/nodes/game_manager similarity index 60% rename from rktl_game/nodes/game_manager rename to src/rktl_game/rktl_game/nodes/game_manager index e9acc660a..932e4f0e0 100755 --- a/rktl_game/nodes/game_manager +++ b/src/rktl_game/rktl_game/nodes/game_manager @@ -9,43 +9,46 @@ License: # ROS -import rospy +import rclpy +from rclpy.parameter import Parameter from std_msgs.msg import Bool, Header from nav_msgs.msg import Odometry from rktl_msgs.msg import Score, MatchStatus -from std_srvs.srv import Empty, EmptyResponse +from std_srvs.srv import Empty, Empty_Response class ScoreKeeper(): def __init__(self): - rospy.init_node('score_keeper') + rclpy.init() + global node + node = rclpy.create_node('score_keeper') self.orange_score = 0 self.blue_score = 0 - self.orange_goal = (rospy.get_param('/field/length', 1) - 0.15) / 2 - self.blue_goal = (rospy.get_param('/field/length', 1) - 0.15) / -2 + self.orange_goal = (node.get_parameter_or('/field/length', Parameter(1)).get_parameter_value().double_value - 0.15) / 2 + self.blue_goal = (node.get_parameter_or('/field/length', Parameter(1)).get_parameter_value().double_value - 0.15) / -2 self.enabled = False self.match_status = 0 - self.game_clock = rospy.get_param('/game_length', 90) - self.manager_rate = rospy.get_param('manager_rate', 10) + self.game_clock = node.get_parameter_or('/game_length', Parameter(90)) + self.manager_rate = node.get_parameter_or('manager_rate', Parameter(10)) # Publishers - self.match_status_pub = rospy.Publisher('match_status', MatchStatus, queue_size=1) - self.active_pub = rospy.Publisher('cars/enable', Bool, queue_size=1) + self.match_status_pub = node.create_publisher(MatchStatus, 'match_status', qos_profile=1) + self.active_pub = node.create_publisher(Bool, 'cars/enable', qos_profile=1) # Subscribers - rospy.Subscriber('ball/odom', Odometry, self.check_goal) - rospy.Subscriber('cars/enable', Bool, self.enable) + node.create_subscription(Odometry, 'ball/odom', self.check_goal, qos_profile=1) + node.create_subscription(Bool, 'cars/enable', self.enable, qos_profile=1) # Services - rospy.Service('reset_game', Empty, self.reset) - rospy.Service('unpause_game', Empty, self.unpause) - rospy.Service('pause_game', Empty, self.pause) + node.create_service(srv_type=Empty, srv_name='reset_game', callback=self.reset) + node.create_service(srv_type=Empty, srv_name='unpause_game', callback=self.unpause) + node.create_service(srv_type=Empty, srv_name='pause_game', callback=self.pause) # Counts loops and decrements game clock every second self.timer_ctr = 0 # main loop - rate = rospy.Rate(self.manager_rate) - while not rospy.is_shutdown(): + rate = node.create_rate(self.manager_rate.get_parameter_value().double_value) + while rclpy.ok(): if self.match_status == 1: self.timer_ctr += 1 if self.timer_ctr == self.manager_rate: @@ -62,32 +65,32 @@ class ScoreKeeper(): self.match_status = 4 score = Score(self.orange_score, self.blue_score) header = Header() - header.stamp = rospy.Time.now() + header.stamp = node.get_clock().now() self.match_status_pub.publish(MatchStatus(header, self.match_status, self.game_clock, score)) def pause(self, _): self.enabled = False self.match_status = 0 self.active_pub.publish(False) - return EmptyResponse() + return Empty_Response() def reset(self, _): self.orange_score = 0 self.blue_score = 0 self.enabled = False self.match_status = 0 - self.game_clock = rospy.get_param('/game_length', 90) + self.game_clock = node.get_parameter_or('/game_length', Parameter(90)).get_parameter_value().double_value self.active_pub.publish(False) header = Header() - header.stamp = rospy.Time.now() + header.stamp = node.get_clock().now() self.match_status_pub.publish(MatchStatus(header, 0, self.game_clock, Score(0, 0))) - return EmptyResponse() + return Empty_Response() def unpause(self, _): self.enabled = True self.match_status = 1 self.active_pub.publish(True) - return EmptyResponse() + return Empty_Response() def check_goal(self, ball_pose: Odometry): if self.enabled: diff --git a/src/rktl_game/setup.cfg b/src/rktl_game/setup.cfg new file mode 100644 index 000000000..1140ea26f --- /dev/null +++ b/src/rktl_game/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rktl_game +[install] +install_scripts=$base/lib/rktl_game diff --git a/src/rktl_game/setup.py b/src/rktl_game/setup.py new file mode 100644 index 000000000..db989a602 --- /dev/null +++ b/src/rktl_game/setup.py @@ -0,0 +1,28 @@ +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = 'rktl_game' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))), + (os.path.join('lib', package_name), glob(os.path.join(package_name, '*.py'))), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='jcrm1', + maintainer_email='52137472+jcrm1@users.noreply.github.com', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ ], + } +) diff --git a/src/rktl_game/test/test_copyright.py b/src/rktl_game/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/src/rktl_game/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/rktl_game/test/test_flake8.py b/src/rktl_game/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/src/rktl_game/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/rktl_game/test/test_pep257.py b/src/rktl_game/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/src/rktl_game/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/rktl_game/web/index.html b/src/rktl_game/web/index.html similarity index 100% rename from rktl_game/web/index.html rename to src/rktl_game/web/index.html diff --git a/rktl_game/web/index.js b/src/rktl_game/web/index.js similarity index 100% rename from rktl_game/web/index.js rename to src/rktl_game/web/index.js diff --git a/rktl_game/web/stylesheet.css b/src/rktl_game/web/stylesheet.css similarity index 100% rename from rktl_game/web/stylesheet.css rename to src/rktl_game/web/stylesheet.css diff --git a/rktl_launch/README.md b/src/rktl_launch/README.md similarity index 100% rename from rktl_launch/README.md rename to src/rktl_launch/README.md diff --git a/src/rktl_launch/config/global_params.yaml b/src/rktl_launch/config/global_params.yaml new file mode 100644 index 000000000..53986177e --- /dev/null +++ b/src/rktl_launch/config/global_params.yaml @@ -0,0 +1,33 @@ +# ALL UNITS IN SI + +# field dimensions +/**: + ros__parameters: + field: + width: 3.0 + # max: 3.5 + # min: 2.5 + length: 4.25 + # max: 5 + # min: 3.5 + wall_thickness: 0.25 + goal: + width: 1.0 + + # car dimensions and physical constants + ball: + radius: 0.0508 # (2 in) + + # car dimensions and physical constants + cars: + length: 0.12 # front to rear wheel center to center, meters + # min: 0.10 + # max: 0.30 + steering: + max_throw: 0.1826 # center to side, radians + # min: 0.16 + # max: 0.20 + rate: 0.9128 # rad/s + throttle: + max_speed: 2.3 # m/s + tau: 0.2 # time constant, seconds diff --git a/src/rktl_launch/config/test.yaml b/src/rktl_launch/config/test.yaml new file mode 100644 index 000000000..9073233e7 --- /dev/null +++ b/src/rktl_launch/config/test.yaml @@ -0,0 +1,12 @@ +/**: + ros__parameters: + field: + width: 3 + # max: 3.5 + # min: 2.5 + length: 4.25 + # max: 5 + # min: 3.5 + wall_thickness: 0.25 + goal: + width: 1 \ No newline at end of file diff --git a/rktl_launch/launch/README.md b/src/rktl_launch/launch/README.md similarity index 100% rename from rktl_launch/launch/README.md rename to src/rktl_launch/launch/README.md diff --git a/rktl_launch/launch/c3po.launch b/src/rktl_launch/launch/c3po.launch similarity index 100% rename from rktl_launch/launch/c3po.launch rename to src/rktl_launch/launch/c3po.launch diff --git a/src/rktl_launch/launch/c3po.launch.py b/src/rktl_launch/launch/c3po.launch.py new file mode 100644 index 000000000..9995e498e --- /dev/null +++ b/src/rktl_launch/launch/c3po.launch.py @@ -0,0 +1,32 @@ +import os +import launch +from launch.launch_description_sources import PythonLaunchDescriptionSource +from ament_index_python.packages import get_package_share_directory + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_perception'), 'launch/camera.launch.py') + ), + launch_arguments={ + 'camera_name': 'cam0' + }.items() + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_perception'), 'launch/camera.launch.py') + ), + launch_arguments={ + 'camera_name': 'cam1' + }.items() + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() diff --git a/rktl_launch/launch/r2d2.launch b/src/rktl_launch/launch/r2d2.launch similarity index 100% rename from rktl_launch/launch/r2d2.launch rename to src/rktl_launch/launch/r2d2.launch diff --git a/src/rktl_launch/launch/r2d2.launch.py b/src/rktl_launch/launch/r2d2.launch.py new file mode 100644 index 000000000..5848c81ed --- /dev/null +++ b/src/rktl_launch/launch/r2d2.launch.py @@ -0,0 +1,48 @@ +import os +import launch +from launch.launch_description_sources import PythonLaunchDescriptionSource +import launch_ros.actions +from ament_index_python.packages import get_package_share_directory + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch_ros.actions.Node( + package='rktl_control', + executable='pose_synchronizer', + name='pose_sync_node', + parameters=[ + get_package_share_directory( + 'rktl_control') + '/config/pose_synchronizer.yaml' + ] + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_perception'), 'launch/camera.launch.py') + ), + launch_arguments={ + 'camera_name': 'cam2' + }.items() + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_perception'), 'launch/camera.launch.py') + ), + launch_arguments={ + 'camera_name': 'cam3' + }.items() + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_launch'), 'launch/rocket_league.launch.py') + ) + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() diff --git a/rktl_launch/launch/rocket_league.launch b/src/rktl_launch/launch/rocket_league.launch similarity index 100% rename from rktl_launch/launch/rocket_league.launch rename to src/rktl_launch/launch/rocket_league.launch diff --git a/src/rktl_launch/launch/rocket_league.launch.py b/src/rktl_launch/launch/rocket_league.launch.py new file mode 100644 index 000000000..bf5260867 --- /dev/null +++ b/src/rktl_launch/launch/rocket_league.launch.py @@ -0,0 +1,105 @@ +import os +import launch +from launch.launch_description_sources import PythonLaunchDescriptionSource +from launch.substitutions import LaunchConfiguration, EqualsSubstitution +from launch.conditions import IfCondition +import launch_ros.actions +from ament_index_python.packages import get_package_share_directory + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='render', + default_value='true' + ), + launch.actions.DeclareLaunchArgument( + name='agent_type', # Either planner, autonomy, or patrol + default_value='patrol' + ), + launch.actions.DeclareLaunchArgument( + name='autonomy_weights', + default_value='model' + ), + launch_ros.actions.Node( + package='rqt_gui', + executable='rqt_gui', + name='rqt_gui', + arguments=['--perspective-file', os.path.join(get_package_share_directory( + 'rktl_launch'), 'rqt','rktl.perspective')] + ), + launch_ros.actions.SetParametersFromFile( + filename=os.path.join(get_package_share_directory( + 'rktl_launch'), 'config', 'global_params.yaml') + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_sim'), 'launch/visualizer.launch.py') + ), + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('render'), 'true')) + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_game'), 'launch/game.launch.py') + ) + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_control'), 'launch/ball.launch.py') + ) + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_control'), 'launch/car.launch.py') + ), + launch_arguments={ + 'car_name': 'car0' + }.items() + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_control'), 'launch/hardware_interface.launch.py') + ) + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_planner'), 'launch/simple_agent.launch.py') + ), + launch_arguments={ + 'agent_name': 'agent0', + 'car_name': 'car0' + }.items(), + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('agent_type'), 'planner')) + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_autonomy'), 'launch/rocket_league/rocket_league_agent.launch.py') + ), + launch_arguments={ + 'weights_name': launch.substitutions.LaunchConfiguration('autonomy_weights') + }.items(), + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('agent_type'), 'autonomy')) + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_planner'), 'launch/patrol_agent.launch.py') + ), + launch_arguments={ + 'car_name': 'car0' + }.items(), + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('agent_type'), 'patrol')) + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() diff --git a/rktl_launch/launch/rocket_league_sim.launch b/src/rktl_launch/launch/rocket_league_sim.launch similarity index 100% rename from rktl_launch/launch/rocket_league_sim.launch rename to src/rktl_launch/launch/rocket_league_sim.launch diff --git a/src/rktl_launch/launch/rocket_league_sim.launch.py b/src/rktl_launch/launch/rocket_league_sim.launch.py new file mode 100644 index 000000000..65d5d57d6 --- /dev/null +++ b/src/rktl_launch/launch/rocket_league_sim.launch.py @@ -0,0 +1,132 @@ +import os +import launch +import launch_ros.actions +from launch.conditions import IfCondition +from launch.substitutions import LaunchConfiguration, EqualsSubstitution +from ament_index_python.packages import get_package_share_directory +from launch.launch_description_sources import PythonLaunchDescriptionSource + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='render', + default_value='true' + ), + launch.actions.DeclareLaunchArgument( + name='sim_mode', + default_value='realistic' # Either realistic or ideal (check docs) + ), + launch.actions.DeclareLaunchArgument( + name='perception_delay', + default_value='0.15' + ), + launch.actions.DeclareLaunchArgument( + name='agent_type', + default_value='patrol' # Either planner, autonomy, or patrol + ), + launch.actions.DeclareLaunchArgument( + name='autonomy_weights', + default_value='model' + ), + launch.actions.GroupAction( + actions=[ + launch_ros.actions.PushRosNamespace("truth"), + launch_ros.actions.SetRemap( + src="/cars/car0/odom", + dst="/cars/car0/odom_truth", + ), + launch_ros.actions.SetRemap( + src="/ball/odom", + dst="/ball/odom_truth", + ), + launch.actions.GroupAction( + actions=[ + launch_ros.actions.PushRosNamespace("visualizer"), + launch_ros.actions.SetParameter( + name="window_name", + value="Ground Truth", + ) + ], + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('sim_mode'), 'realistic')) + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_sim'), 'launch', 'visualizer.launch.py') + ), + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('render'), 'realistic')) + ), + ], + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('sim_mode'), 'realistic')) + ), + launch_ros.actions.SetParametersFromFile( + filename=os.path.join(get_package_share_directory( + 'rktl_launch'), 'config', 'global_params.yaml') + ), + launch_ros.actions.Node( + namespace='ball', + package='rktl_control', + executable='topic_delay', + name='pose_delay', + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('sim_mode'), 'realistic')), + arguments=["pose_sync_early", "pose_sync", "geometry_msgs/PoseWithCovarianceStamped", launch.substitutions.LaunchConfiguration('perception_delay')] + ), + launch_ros.actions.Node( + namespace='cars/car0', + package='rktl_control', + executable='topic_delay', + name='pose_delay', + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('sim_mode'), 'realistic')), + arguments=["pose_sync_early", "pose_sync", "geometry_msgs/PoseWithCovarianceStamped", launch.substitutions.LaunchConfiguration('perception_delay')] + ), + launch_ros.actions.Node( + package='rqt_gui', + executable='rqt_gui', + name='rqt_gui', + condition=IfCondition(EqualsSubstitution(LaunchConfiguration('render'), 'true')), + arguments=['--perspective-file', os.path.join(get_package_share_directory( + 'rktl_launch'), 'rqt','rktl.perspective')] + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_sim'), 'launch/simulator.launch.py') + ), + launch_arguments={ + 'sim_mode': launch.substitutions.LaunchConfiguration('sim_mode') + }.items() + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_planner'), 'launch/simple_agent.launch.py') + ), + launch_arguments={ + 'agent_name': 'agent0', + 'car_name': 'car0' + }.items() + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_autonomy'), 'launch/rocket_league_agent.launch.py') + ), + launch_arguments={ + 'weights_name': launch.substitutions.LaunchConfiguration('autonomy_weights') + }.items() + ), + launch.actions.IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join(get_package_share_directory( + 'rktl_planner'), 'launch/patrol_agent.launch.py') + ), + launch_arguments={ + 'car_name': 'car0' + }.items() + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() diff --git a/src/rktl_launch/launch/test.launch.py b/src/rktl_launch/launch/test.launch.py new file mode 100644 index 000000000..53765eda1 --- /dev/null +++ b/src/rktl_launch/launch/test.launch.py @@ -0,0 +1,146 @@ +import os + +from ament_index_python.packages import get_package_share_directory + +from launch import LaunchDescription +from launch.actions import DeclareLaunchArgument, GroupAction, IncludeLaunchDescription +from launch_ros.actions import Node, SetRemap, PushROSNamespace, SetParameter +from launch.substitutions import LaunchConfiguration +from launch.launch_description_sources import PythonLaunchDescriptionSource + + +def generate_launch_description(): + render_launch_arg = DeclareLaunchArgument( + name='render', + default_value='true' + ) + + sim_mode_launch_arg = DeclareLaunchArgument( + name='sim_mode', + default_value='realistic' + ) + + perception_delay_launch_arg = DeclareLaunchArgument( + name='perception_delay', + default_value='0.15' + ) + + agent_type_launch_arg = DeclareLaunchArgument( + name='agent_type', + default_value='patrol' + ) + + autonomy_weights_launch_arg = DeclareLaunchArgument( + name='autonomy_weights', + default_value='model' + ) + + global_config = os.path.join( + get_package_share_directory('rktl_launch'), + 'config', + 'global_parms.yaml' + ) + + # START Group Action ------------------ + remap1 = SetRemap( + src="/cars/car0/odom", + dst="/cars/car0/odom_truth", + ) + + remap2 = SetRemap( + src="/ball/odom", + dst="/ball/odom_truth", + ) + + parameter_with_namespace = GroupAction( + actions=[ + PushROSNamespace("visualizer"), + SetParameter( + name="window_name", + value="Ground Truth", + ) + ], + ) + + visualizer_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join( + get_package_share_directory('rktl_sim'), + 'launch', + 'test_visualizer.launch.py' + ) + ), + ) + + group_action1 = GroupAction( + actions=[ + remap1, + remap2, + parameter_with_namespace, + visualizer_launch + ] + ) + # END Group Action ------------------ + + ball_node = Node( + name='ball', + package='rktl_control', + executable='topic_delay', + arguments=["pose_sync_early", + "pose_sync", + "geometry_msgs/PoseWithCovarianceStamped",LaunchConfiguration('perception_delay') + ] + ) + + car_node = Node( + name='car', + package='rktl_control', + executable='topic_delay', + arguments=["pose_sync_early", + "pose_sync", + "geometry_msgs/PoseWithCovarianceStamped",LaunchConfiguration('perception_delay') + ] + ) + + perspective_file = os.path.join( + get_package_share_directory('rktl_launch'), + 'rqt', + 'rktl.perspective' + ) + + rqt_gui_node = Node( + package='rqt_gui', + executable='rqt_gui', + name='rqt_gui', + arguments=['--perspective-file', + perspective_file + ] + + ) + + simulator_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource( + os.path.join( + get_package_share_directory('rktl_sim'), + 'launch', + 'test_simulator.launch.py' + ) + ) + ) + + return LaunchDescription([ + render_launch_arg, + sim_mode_launch_arg, + perception_delay_launch_arg, + agent_type_launch_arg, + autonomy_weights_launch_arg, + group_action1, + ball_node, + car_node, + rqt_gui_node, + simulator_launch + ]) + + +if __name__ == '__main__': + generate_launch_description() diff --git a/rktl_launch/package.xml b/src/rktl_launch/package.xml similarity index 60% rename from rktl_launch/package.xml rename to src/rktl_launch/package.xml index ee0b61a10..c8cebcec2 100644 --- a/rktl_launch/package.xml +++ b/src/rktl_launch/package.xml @@ -1,7 +1,8 @@ - + + rktl_launch - 0.0.0 + 1.0.0 Launch files for the ARC Rocket League project Autonomous Robotics Club of Purdue @@ -12,12 +13,19 @@ Arianna McNamara Vincent Vu - catkin - rosbridge_server - rktl_perception + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + rktl_game rktl_planner rktl_sim rktl_control rqt_gui + ros2launch + + + ament_python + diff --git a/src/rktl_launch/resource/rktl_launch b/src/rktl_launch/resource/rktl_launch new file mode 100644 index 000000000..e69de29bb diff --git a/src/rktl_launch/rktl_launch/__init__.py b/src/rktl_launch/rktl_launch/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rktl_launch/rqt/rktl.perspective b/src/rktl_launch/rqt/rktl.perspective similarity index 100% rename from rktl_launch/rqt/rktl.perspective rename to src/rktl_launch/rqt/rktl.perspective diff --git a/src/rktl_launch/setup.cfg b/src/rktl_launch/setup.cfg new file mode 100644 index 000000000..b7e96ba37 --- /dev/null +++ b/src/rktl_launch/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rktl_launch +[install] +install_scripts=$base/lib/rktl_launch diff --git a/src/rktl_launch/setup.py b/src/rktl_launch/setup.py new file mode 100644 index 000000000..d1f68dd2a --- /dev/null +++ b/src/rktl_launch/setup.py @@ -0,0 +1,31 @@ +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = 'rktl_launch' + +setup( + name=package_name, + version='1.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))), + (os.path.join('lib', package_name), glob(os.path.join(package_name, '*.py'))), + (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.yaml'))), + (os.path.join('share', package_name, 'rqt'), glob(os.path.join('rqt', '*.perspective'))), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Autonomous Robotics Club of Purdue', + maintainer_email='autonomy@purdue.edu', + description='Launch files for the ARC Rocket League project', + license='BSD 3 Clause', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + ], + }, +) diff --git a/src/rktl_launch/test/test_copyright.py b/src/rktl_launch/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/src/rktl_launch/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/rktl_launch/test/test_flake8.py b/src/rktl_launch/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/src/rktl_launch/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/rktl_launch/test/test_pep257.py b/src/rktl_launch/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/src/rktl_launch/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/rktl_msgs/CMakeLists.txt b/src/rktl_msgs/CMakeLists.txt new file mode 100644 index 000000000..076a22d44 --- /dev/null +++ b/src/rktl_msgs/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.8) +project(rktl_msgs) + +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) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +find_package(std_msgs REQUIRED) +find_package(builtin_interfaces REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(rosidl_default_generators REQUIRED) + +rosidl_generate_interfaces(${PROJECT_NAME} + "msg/BezierPath.msg" + "msg/BezierPathList.msg" + "msg/ControlCommand.msg" + "msg/ControlEffort.msg" + "msg/MatchStatus.msg" + "msg/Path.msg" + "msg/Score.msg" + "msg/Waypoint.msg" + "srv/CreateBezierPath.srv" + DEPENDENCIES geometry_msgs std_msgs builtin_interfaces # Add packages that above messages depend on, in this case geometry_msgs for Sphere.msg +) + +ament_package() diff --git a/rktl_msgs/README.md b/src/rktl_msgs/README.md similarity index 100% rename from rktl_msgs/README.md rename to src/rktl_msgs/README.md diff --git a/rktl_msgs/msg/BezierPath.msg b/src/rktl_msgs/msg/BezierPath.msg similarity index 56% rename from rktl_msgs/msg/BezierPath.msg rename to src/rktl_msgs/msg/BezierPath.msg index 4bad1e867..e04caa63f 100644 --- a/rktl_msgs/msg/BezierPath.msg +++ b/src/rktl_msgs/msg/BezierPath.msg @@ -1,3 +1,3 @@ int32 order geometry_msgs/Point[] control_points -std_msgs/Duration duration +builtin_interfaces/Duration duration diff --git a/rktl_msgs/msg/BezierPathList.msg b/src/rktl_msgs/msg/BezierPathList.msg similarity index 68% rename from rktl_msgs/msg/BezierPathList.msg rename to src/rktl_msgs/msg/BezierPathList.msg index fc04ee02e..b8d064965 100644 --- a/rktl_msgs/msg/BezierPathList.msg +++ b/src/rktl_msgs/msg/BezierPathList.msg @@ -1,3 +1,3 @@ -Header header +std_msgs/Header header string child_frame_id rktl_msgs/BezierPath[] paths \ No newline at end of file diff --git a/rktl_msgs/msg/ControlCommand.msg b/src/rktl_msgs/msg/ControlCommand.msg similarity index 60% rename from rktl_msgs/msg/ControlCommand.msg rename to src/rktl_msgs/msg/ControlCommand.msg index 7bbb885c1..e88049149 100644 --- a/rktl_msgs/msg/ControlCommand.msg +++ b/src/rktl_msgs/msg/ControlCommand.msg @@ -1,3 +1,3 @@ -Header header +std_msgs/Header header float64 velocity float64 curvature diff --git a/rktl_msgs/msg/ControlEffort.msg b/src/rktl_msgs/msg/ControlEffort.msg similarity index 59% rename from rktl_msgs/msg/ControlEffort.msg rename to src/rktl_msgs/msg/ControlEffort.msg index 39b76e1be..6cf958fd5 100644 --- a/rktl_msgs/msg/ControlEffort.msg +++ b/src/rktl_msgs/msg/ControlEffort.msg @@ -1,3 +1,3 @@ -Header header +std_msgs/Header header float64 throttle float64 steering diff --git a/rktl_msgs/msg/MatchStatus.msg b/src/rktl_msgs/msg/MatchStatus.msg similarity index 94% rename from rktl_msgs/msg/MatchStatus.msg rename to src/rktl_msgs/msg/MatchStatus.msg index 32a3a95ba..4023e2c79 100644 --- a/rktl_msgs/msg/MatchStatus.msg +++ b/src/rktl_msgs/msg/MatchStatus.msg @@ -1,4 +1,4 @@ -Header header +std_msgs/Header header int8 PAUSED=0 # before the match is started int8 ONGOING=1 # match is currently going on diff --git a/rktl_msgs/msg/Path.msg b/src/rktl_msgs/msg/Path.msg similarity index 75% rename from rktl_msgs/msg/Path.msg rename to src/rktl_msgs/msg/Path.msg index f37e16f4d..3ff593296 100644 --- a/rktl_msgs/msg/Path.msg +++ b/src/rktl_msgs/msg/Path.msg @@ -1,4 +1,4 @@ -Header header +std_msgs/Header header string child_frame_id float64 velocity rktl_msgs/Waypoint[] waypoints diff --git a/rktl_msgs/msg/Score.msg b/src/rktl_msgs/msg/Score.msg similarity index 100% rename from rktl_msgs/msg/Score.msg rename to src/rktl_msgs/msg/Score.msg diff --git a/rktl_msgs/msg/Waypoint.msg b/src/rktl_msgs/msg/Waypoint.msg similarity index 76% rename from rktl_msgs/msg/Waypoint.msg rename to src/rktl_msgs/msg/Waypoint.msg index a3aa4ff28..cced45368 100644 --- a/rktl_msgs/msg/Waypoint.msg +++ b/src/rktl_msgs/msg/Waypoint.msg @@ -1,4 +1,4 @@ -Header header +std_msgs/Header header std_msgs/Float32 delta_t geometry_msgs/Pose pose geometry_msgs/Twist twist \ No newline at end of file diff --git a/src/rktl_msgs/package.xml b/src/rktl_msgs/package.xml new file mode 100644 index 000000000..55761724d --- /dev/null +++ b/src/rktl_msgs/package.xml @@ -0,0 +1,28 @@ + + + + rktl_msgs + 0.0.0 + Custom messages for ARC Rocket League Project + Autonomous Robotics Club of Purdue + BSD 3 Clause + https://www.purduearc.com + Arianna McNamara + James Baxter + + ament_cmake + + ament_lint_auto + ament_lint_common + + + ament_cmake + + + geometry_msgs + std_msgs + builtin_interfaces + rosidl_default_generators + rosidl_default_runtime + rosidl_interface_packages + diff --git a/src/rktl_msgs/srv/CreateBezierPath.srv b/src/rktl_msgs/srv/CreateBezierPath.srv new file mode 100644 index 000000000..abc12e59d --- /dev/null +++ b/src/rktl_msgs/srv/CreateBezierPath.srv @@ -0,0 +1,8 @@ +float64 velocity +geometry_msgs/Pose[] target_poses +builtin_interfaces/Duration[] target_durations +builtin_interfaces/Duration bezier_segment_duration +builtin_interfaces/Duration linear_segment_duration +--- +rktl_msgs/BezierPath[] bezier_paths +rktl_msgs/Path linear_path diff --git a/src/rktl_perception/CMakeLists.txt b/src/rktl_perception/CMakeLists.txt new file mode 100644 index 000000000..8a1dd877d --- /dev/null +++ b/src/rktl_perception/CMakeLists.txt @@ -0,0 +1,147 @@ +cmake_minimum_required(VERSION 3.8) +project(rktl_perception) + +## Enable C++17 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) +endif() +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 dependencies added from previous +find_package(spinnaker_camera_driver REQUIRED) +find_package(image_proc REQUIRED) +find_package(tf2 REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(image_transport REQUIRED) +find_package(cv_bridge REQUIRED) +find_package(apriltag_ros REQUIRED) +find_package(rclcpp_components REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(image_geometry REQUIRED) +find_package(rosidl_default_generators REQUIRED) + +# System dependencies +find_package(OpenCV REQUIRED COMPONENTS core highgui) +find_package(Eigen3 REQUIRED) + +# Add include directories +include_directories( + include + ${OpenCV_INCLUDE_DIRS} + ${EIGEN3_INCLUDE_DIR} + ) + +# Build localizer node +add_executable(${PROJECT_NAME}_localizer_node + src/localizer_node.cpp + src/localizer.cpp + ) +set_target_properties(${PROJECT_NAME}_localizer_node PROPERTIES + OUTPUT_NAME localizer + PREFIX "" + ) +add_dependencies(${PROJECT_NAME}_localizer_node + ${${PROJECT_NAME}_EXPORTED_TARGETS} + ) +target_link_libraries(${PROJECT_NAME}_localizer_node + Eigen3::Eigen + ) +ament_target_dependencies(${PROJECT_NAME}_localizer_node + rclcpp + std_msgs + ) + +# Build ball_detection node +add_executable(${PROJECT_NAME}_ball_detection_node + src/ball_detection.cpp + src/ball_detection_node.cpp + ) +set_target_properties(${PROJECT_NAME}_ball_detection_node PROPERTIES + OUTPUT_NAME ball_detection + PREFIX "" + ) +add_dependencies(${PROJECT_NAME}_ball_detection_node + ${${PROJECT_NAME}_EXPORTED_TARGETS} + ${OpenCV_EXPORTED_TARGETS} + ) +target_link_libraries(${PROJECT_NAME}_ball_detection_node + ${OpenCV_LIBRARIES} + ) +ament_target_dependencies(${PROJECT_NAME}_ball_detection_node + rclcpp + std_msgs + ) + +# Build ball_color node +add_executable(${PROJECT_NAME}_ball_color_node + src/ball_color.cpp + ) +set_target_properties(${PROJECT_NAME}_ball_color_node PROPERTIES + OUTPUT_NAME ball_color + PREFIX "" + ) +add_dependencies(${PROJECT_NAME}_ball_color_node + ${${PROJECT_NAME}_EXPORTED_TARGETS} + ${OpenCV_EXPORTED_TARGETS} + ) + target_link_libraries(${PROJECT_NAME}_ball_color_node + ${OpenCV_LIBRARIES} + ) + +ament_target_dependencies(${PROJECT_NAME}_ball_color_node + rclcpp + std_msgs + ) + +# vis node +add_executable(${PROJECT_NAME}_focus_vis_node + src/focus_vis.cpp + src/focus_vis_node.cpp + ) +set_target_properties(${PROJECT_NAME}_focus_vis_node PROPERTIES + OUTPUT_NAME focus_vis + PREFIX "" + ) +add_dependencies(${PROJECT_NAME}_focus_vis_node + ${${PROJECT_NAME}_EXPORTED_TARGETS} + ${OpenCV_EXPORTED_TARGETS} + ) +target_link_libraries(${PROJECT_NAME}_focus_vis_node + ${OpenCV_LIBRARIES} + ) +ament_target_dependencies(${PROJECT_NAME}_focus_vis_node + rclcpp + std_msgs + ) + + if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() + endif() + +ament_export_dependencies(rclcpp + pointgrey_camera_driver + image_proc + tf2 + tf2_ros + image_transport + cv_bridge + apriltag_ros + OpenCV + ) + +ament_export_include_directories(include) +ament_package() \ No newline at end of file diff --git a/src/rktl_perception/config/apriltag_settings.yaml b/src/rktl_perception/config/apriltag_settings.yaml new file mode 100644 index 000000000..327719df8 --- /dev/null +++ b/src/rktl_perception/config/apriltag_settings.yaml @@ -0,0 +1,12 @@ +/**: + ros__parameters: + tag_family: 'tag25h9' + tag_border: 1 + tag_threads: 4 + tag_decimate: 1.0 + tag_blur: 0.0 + tag_refine_edges: 1 + tag_refine_decode: 0 + tag_refine_pose: 0 + tag_debug: 0 + publish_tf: false \ No newline at end of file diff --git a/src/rktl_perception/config/ball_settings.yaml b/src/rktl_perception/config/ball_settings.yaml new file mode 100644 index 000000000..c9cc53263 --- /dev/null +++ b/src/rktl_perception/config/ball_settings.yaml @@ -0,0 +1,12 @@ +/**: + ros__parameters: + publishThresh: true + min_hue: 50 + max_hue: 100 + min_sat: 70 + max_sat: 180 + min_vib: 40 + max_vib: 100 + min_size: 250 + erode: 2 + dilate: 8 \ No newline at end of file diff --git a/src/rktl_perception/config/cam0/calibration.yaml b/src/rktl_perception/config/cam0/calibration.yaml new file mode 100644 index 000000000..05d5bdf71 --- /dev/null +++ b/src/rktl_perception/config/cam0/calibration.yaml @@ -0,0 +1,22 @@ +/**: + ros__parameters: + image_width: 1288 + image_height: 964 + camera_name: 15356310 + camera_matrix: + rows: 3 + cols: 3 + data: [610.4617133035794, 0, 648.8400790764666, 0, 612.8636563372359, 509.10981446149, 0, 0, 1] + distortion_model: plumb_bob + distortion_coefficients: + rows: 1 + cols: 5 + data: [-0.2432499228354474, 0.04650574966262223, -0.001403592205737197, -0.001846787629070493, 0] + rectification_matrix: + rows: 3 + cols: 3 + data: [1, 0, 0, 0, 1, 0, 0, 0, 1] + projection_matrix: + rows: 3 + cols: 4 + data: [437.3668518066406, 0, 642.4606144639438, 0, 0, 497.3519287109375, 519.6716765197198, 0, 0, 0, 1, 0] \ No newline at end of file diff --git a/src/rktl_perception/config/cam0/pointgrey.yaml b/src/rktl_perception/config/cam0/pointgrey.yaml new file mode 100644 index 000000000..60b9a8270 --- /dev/null +++ b/src/rktl_perception/config/cam0/pointgrey.yaml @@ -0,0 +1,8 @@ +/**: + ros__parameters: + serial: 15356310 + frame_rate: 30 + auto_shutter: false + shutter_speed: 0.001 + auto_gain: false + gain: 25.0 diff --git a/src/rktl_perception/config/cam1/calibration.yaml b/src/rktl_perception/config/cam1/calibration.yaml new file mode 100644 index 000000000..67ec8d7ef --- /dev/null +++ b/src/rktl_perception/config/cam1/calibration.yaml @@ -0,0 +1,22 @@ +/**: + ros__parameters: + image_width: 1288 + image_height: 964 + camera_name: 15374749 + camera_matrix: + rows: 3 + cols: 3 + data: [614.57136917199, 0, 655.3078549706212, 0, 615.1667182505952, 527.5952225219263, 0, 0, 1] + distortion_model: plumb_bob + distortion_coefficients: + rows: 1 + cols: 5 + data: [-0.2139026302415871, 0.03446647780631319, 0.0003785624850231047, 0.001450676024114855, 0] + rectification_matrix: + rows: 3 + cols: 3 + data: [1, 0, 0, 0, 1, 0, 0, 0, 1] + projection_matrix: + rows: 3 + cols: 4 + data: [429.591796875, 0, 663.7421479902696, 0, 0, 510.9947814941406, 548.7979183761527, 0, 0, 0, 1, 0] \ No newline at end of file diff --git a/src/rktl_perception/config/cam1/pointgrey.yaml b/src/rktl_perception/config/cam1/pointgrey.yaml new file mode 100644 index 000000000..81989f871 --- /dev/null +++ b/src/rktl_perception/config/cam1/pointgrey.yaml @@ -0,0 +1,8 @@ +/**: + ros__parameters: + serial: 15374749 + frame_rate: 30 + auto_shutter: false + shutter_speed: 0.001 + auto_gain: false + gain: 25.0 diff --git a/src/rktl_perception/config/cam2/calibration.yaml b/src/rktl_perception/config/cam2/calibration.yaml new file mode 100644 index 000000000..2e072d08d --- /dev/null +++ b/src/rktl_perception/config/cam2/calibration.yaml @@ -0,0 +1,22 @@ +/**: + ros__parameters: + image_width: 1288 + image_height: 964 + camera_name: 15374754 + camera_matrix: + rows: 3 + cols: 3 + data: [1126.08574591452, 0, 616.7375520125502, 0, 1116.255414571801, 525.7495338331287, 0, 0, 1] + distortion_model: plumb_bob + distortion_coefficients: + rows: 1 + cols: 5 + data: [-0.7942828618736387, 0.5381554120245144, 0.00207013009474492, 0.01022713459887638, 0] + rectification_matrix: + rows: 3 + cols: 3 + data: [1, 0, 0, 0, 1, 0, 0, 0, 1] + projection_matrix: + rows: 3 + cols: 4 + data: [827.6439208984375, 0, 634.490142294926, 0, 0, 908.0331420898438, 549.0852317258759, 0, 0, 0, 1, 0] \ No newline at end of file diff --git a/src/rktl_perception/config/cam2/pointgrey.yaml b/src/rktl_perception/config/cam2/pointgrey.yaml new file mode 100644 index 000000000..53cdff196 --- /dev/null +++ b/src/rktl_perception/config/cam2/pointgrey.yaml @@ -0,0 +1,8 @@ +/**: + ros__parameters: + serial: 15374754 + frame_rate: 30 + auto_shutter: false + shutter_speed: 0.001 + auto_gain: false + gain: 25.0 diff --git a/src/rktl_perception/config/cam3/calibration.yaml b/src/rktl_perception/config/cam3/calibration.yaml new file mode 100644 index 000000000..39a3b8d9a --- /dev/null +++ b/src/rktl_perception/config/cam3/calibration.yaml @@ -0,0 +1,22 @@ +/**: + ros__parameters: + image_width: 1288 + image_height: 964 + camera_name: 15374755 + camera_matrix: + rows: 3 + cols: 3 + data: [691.8297171319369, 0, 616.8642266971589, 0, 697.0148612025321, 517.5329039891342, 0, 0, 1] + distortion_model: plumb_bob + distortion_coefficients: + rows: 1 + cols: 5 + data: [-0.2678012034146836, 0.05194194294770087, 0.002831174538127116, 0.001537062305525107, 0] + rectification_matrix: + rows: 3 + cols: 3 + data: [1, 0, 0, 0, 1, 0, 0, 0, 1] + projection_matrix: + rows: 3 + cols: 4 + data: [478.2955932617188, 0, 611.6567255038535, 0, 0, 581.3926391601562, 538.9608447667997, 0, 0, 0, 1, 0] \ No newline at end of file diff --git a/src/rktl_perception/config/cam3/pointgrey.yaml b/src/rktl_perception/config/cam3/pointgrey.yaml new file mode 100644 index 000000000..31d8f340b --- /dev/null +++ b/src/rktl_perception/config/cam3/pointgrey.yaml @@ -0,0 +1,8 @@ +/**: + ros__parameters: + serial: 15374755 + frame_rate: 30 + auto_shutter: false + shutter_speed: 0.001 + auto_gain: false + gain: 25.0 diff --git a/src/rktl_perception/config/tags.yaml b/src/rktl_perception/config/tags.yaml new file mode 100644 index 000000000..a006a5c64 --- /dev/null +++ b/src/rktl_perception/config/tags.yaml @@ -0,0 +1,26 @@ +/**: + ros__parameters: + standalone_tags: + [ + { id: 10, size: 0.07 }, + { id: 11, size: 0.07 }, + { id: 12, size: 0.07 }, + { id: 13, size: 0.07 }, + ] + tag_bundles: + [ + { + name: 'field_origin', + layout: + [ + {id: 0, size: 0.2, x: -1.8081, y: 1.1742, z: -0.0308, qw: 0.9997, qx: 0.0099, qy: 0.0046, qz: 0.0197}, + {id: 1, size: 0.2, x: 0.0701, y: 1.3045, z: -0.0291, qw: 0.9998, qx: -0.0116, qy: -0.0007, qz: 0.0161}, + {id: 2, size: 0.2, x: 1.7377, y: 1.2590, z: -0.0244, qw: 0.9998, qx: 0.0085, qy: -0.0038, qz: 0.0152}, + {id: 3, size: 0.2, x: -1.1869, y: 0.0000, z: 0.0000, qw: 1.0000, qx: 0.0000, qy: 0.0000, qz: 0.0000}, + {id: 4, size: 0.2, x: 1.1869, y: 0.0138, z: -0.0036, qw: 0.9998, qx: 0.0047, qy: -0.0161, qz: 0.0089}, + {id: 5, size: 0.2, x: -1.7244, y: -1.2670, z: 0.0111, qw: 0.9998, qx: 0.0008, qy: -0.0024, qz: 0.0208}, + {id: 6, size: 0.2, x: 0.1233, y: -1.2090, z: 0.0012, qw: 0.9998, qx: 0.0023, qy: 0.0177, qz: 0.0000}, + {id: 7, size: 0.2, x: 1.8482, y: -1.0850, z: 0.0030, qw: 0.9999, qx: -0.0131, qy: -0.0009, qz: -0.0015} + ] + } + ] \ No newline at end of file diff --git a/rktl_perception/include/rktl_perception/ball_detection.h b/src/rktl_perception/include/rktl_perception/ball_detection.h similarity index 85% rename from rktl_perception/include/rktl_perception/ball_detection.h rename to src/rktl_perception/include/rktl_perception/ball_detection.h index 38f753fb7..03d42f4e9 100644 --- a/rktl_perception/include/rktl_perception/ball_detection.h +++ b/src/rktl_perception/include/rktl_perception/ball_detection.h @@ -9,8 +9,8 @@ #define __RKTL_PERCEPTION_BALL_DETECTION_H__ /* ros */ -#include -#include +#include + /* image */ #include @@ -31,25 +31,25 @@ /* math & vectors */ #include #include +#include namespace rktl_perception { class BallDetection { public: - BallDetection(const ros::NodeHandle& nh, const ros::NodeHandle& pnh); + BallDetection(const std::shared_ptr& node); ~BallDetection() = default; private: void ballCallback(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr& info); - ros::NodeHandle _nh; - ros::NodeHandle _pnh; + std::shared_ptr _node; image_transport::ImageTransport _it; image_transport::CameraSubscriber _cameraSub; image_transport::Publisher _imgPub; - ros::Publisher _vecPub; - ros::Subscriber _detectionSub; + rclcpp::Publisher _vecPub; + rclcpp::Subscribtion _detectionSub; int _minHue, _minSat, _minVib, _maxHue, _maxSat, _maxVib, _minSize, _erodeAmnt, _dilateAmnt; double _originX, _originY; bool _publishThresh; diff --git a/rktl_perception/include/rktl_perception/focus_vis.h b/src/rktl_perception/include/rktl_perception/focus_vis.h similarity index 85% rename from rktl_perception/include/rktl_perception/focus_vis.h rename to src/rktl_perception/include/rktl_perception/focus_vis.h index 5d3dba164..956c7f4dc 100644 --- a/rktl_perception/include/rktl_perception/focus_vis.h +++ b/src/rktl_perception/include/rktl_perception/focus_vis.h @@ -9,7 +9,7 @@ #define __RKTL_PERCEPTION_FOCUS_VIS_H__ /* ros */ -#include +#include /* image */ #include @@ -30,23 +30,25 @@ #include #include +/* memory */ +# include + namespace rktl_perception { class FocusVis { public: - FocusVis(const ros::NodeHandle& nh); + FocusVis(const std::shared_ptr node); ~FocusVis() = default; private: void focusCallback(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr& info); - ros::NodeHandle _nh; - ros::NodeHandle _pnh; + std::shared_ptr _node; image_transport::ImageTransport _it; image_transport::CameraSubscriber _cameraSub; image_transport::Publisher _imgPub; - ros::Subscriber _detectionSub; + rclcpp::Subscribtion _detectionSub; image_geometry::PinholeCameraModel _camera; }; diff --git a/rktl_perception/include/rktl_perception/localizer.h b/src/rktl_perception/include/rktl_perception/localizer.h similarity index 75% rename from rktl_perception/include/rktl_perception/localizer.h rename to src/rktl_perception/include/rktl_perception/localizer.h index 8648ca639..08949ec65 100644 --- a/rktl_perception/include/rktl_perception/localizer.h +++ b/src/rktl_perception/include/rktl_perception/localizer.h @@ -8,7 +8,7 @@ #ifndef __RKTL_PERCEPTION_LOCALIZER_H__ #define __RKTL_PERCEPTION_LOCALIZER_H__ -#include +#include #include #include #include @@ -17,7 +17,7 @@ namespace rktl_perception { class Localizer { public: - Localizer(const ros::NodeHandle& nh, const ros::NodeHandle& pnh); + Localizer(const std::shared_ptr& node); ~Localizer() = default; static std::string idsToString(std::vector ids); @@ -27,17 +27,16 @@ class Localizer { void ballCallback(geometry_msgs::Vector3StampedConstPtr msg); Eigen::Matrix4d combineMatrices(const Eigen::Matrix3d& rot, const Eigen::Vector3d& pos); geometry_msgs::PoseWithCovarianceStamped toMsg(const Eigen::Matrix4d& transform, - ros::Time stamp, + rclcpp::Time stamp, const std::string& frameId = "map"); - ros::NodeHandle _nh; - ros::NodeHandle _pnh; - ros::Subscriber _sub; + std::shared_ptr _node; + rclcpp::Subscribtion _sub; std::string _originId; - ros::Publisher _pub; - std::map _pubs; - ros::Subscriber _ballSub; - ros::Publisher _ballPub; + rclcpp::Publisher _pub; + std::map _pubs; + rclcpp::Subscribtion _ballSub; + rclcpp::Publisher _ballPub; int _bufferSize; int _bufferPos; double _ballRadius; diff --git a/src/rktl_perception/launch/cal_launch.py b/src/rktl_perception/launch/cal_launch.py new file mode 100644 index 000000000..aa8ba0bbd --- /dev/null +++ b/src/rktl_perception/launch/cal_launch.py @@ -0,0 +1,43 @@ +import os +import sys + +import launch +import launch_ros.actions + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch_ros.actions.Node( + package='camera_calibration', + executable='cameracalibrator.py', + name='calib', + namespace = 'cams/cam0', + arguments=["--size 8x6 --square 0.026 image:=/cams/cam3/image_raw camera:=/cams/cam0"] + ), + launch_ros.actions.Node( + package='camera_calibration', + executable='cameracalibrator.py', + name='calib', + namespace = 'cams/cam1', + arguments=["--size 8x6 --square 0.026 image:=/cams/cam3/image_raw camera:=/cams/cam1"] + ), + launch_ros.actions.Node( + package='camera_calibration', + executable='cameracalibrator.py', + name='calib', + namespace = 'cams/cam2', + arguments=["--size 8x6 --square 0.026 image:=/cams/cam3/image_raw camera:=/cams/cam2"] + ), + launch_ros.actions.Node( + package='camera_calibration', + executable='cameracalibrator.py', + name='calib', + namespace = 'cams/cam3', + arguments=["--size 8x6 --square 0.026 image:=/cams/cam3/image_raw camera:=/cams/cam3"] + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() \ No newline at end of file diff --git a/src/rktl_perception/launch/camera_launch.py b/src/rktl_perception/launch/camera_launch.py new file mode 100644 index 000000000..cf0831fba --- /dev/null +++ b/src/rktl_perception/launch/camera_launch.py @@ -0,0 +1,92 @@ +from ament_index_python import get_package_share_directory +import launch +from launch_ros.actions import Node, SetParametersFromFile +from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription +from launch.launch_description_sources import PythonLaunchDescriptionSource +import os + +def generate_launch_description(): + load_manager = DeclareLaunchArgument('load_manager', default_value='true') + manager_name = DeclareLaunchArgument('manager_name', default_value='camera_manager') + manager_threads = DeclareLaunchArgument('manager_threads', default_value='4') + camera_name = DeclareLaunchArgument('camera_name', default_value='cam0') + + ns_group = launch.actions.GroupAction( + actions=[ + Node( + package='nodelet', + executable='nodelet', + name=[launch.substitutions.LaunchConfiguration('manager_name')], + namespace=['cams/', launch.substitutions.LaunchConfiguration('camera_name')], + arguments='manager', + parameters=[{'num_worker_threads': launch.substitutions.LaunchConfiguration('manager_threads')}], + condition=launch.conditions.IfCondition([launch.substitutions.LaunchConfiguration('load_manager')]) + ), + Node( + package='nodelet', + executable='nodelet', + name=[launch.substitutions.LaunchConfiguration('camera_name')], + namespace=['cams/', launch.substitutions.LaunchConfiguration('camera_name')], + arguments=['load', 'pointgrey_camera_driver/PointGreyCameraNodelet', launch.substitutions.LaunchConfiguration('manager_name')], + parameters=[ + {'frame_id': launch.substitutions.LaunchConfiguration('camera_name')}, + {'camera_info_url': 'package://rktl_perception/config/' + launch.substitutions.LaunchConfiguration('camera_name') + '/calibration.yaml'} + ] + ), + IncludeLaunchDescription( + PythonLaunchDescriptionSource(os.path.join(get_package_share_directory('image_proc'), 'launch', 'image_proc.launch')), + launch_arguments={'manager': launch.substitutions.LaunchConfiguration('manager_name')}.items(), + ), + Node( + package='apriltag_ros', + executable='apriltag_ros_continuous_node', + name='apriltag', + namespace=['cams/', launch.substitutions.LaunchConfiguration('camera_name')], + parameters=[ + {'camera_frame': launch.substitutions.LaunchConfiguration('camera_name')}, + {'publish_tag_detections_image': True}, + os.path.join(get_package_share_directory('rktl_perception'), 'config', 'apriltag_settings.yaml'), + os.path.join(get_package_share_directory('rktl_perception'), 'config', 'tags.yaml') + ] + ), + SetParametersFromFile(filename=os.path.join(get_package_share_directory('rktl_perception'), 'config', launch.substitutions.LaunchConfiguration('camera_name'), 'pointgrey.yaml')), + + Node( + package='rktl_perception', + executable='localizer', + name='localizer', + namespace=['cams/', launch.substitutions.LaunchConfiguration('camera_name')], + parameters=[ + {'origin_id': '0,1,2,3,4,5,6,7'}, + {'buffer_size': 300}, + {'ball_sub_topic': 'ball_vec'}, + {'ball_pub_topic': '/ball/pose'}, + {'ball_radius': 0.03}, + {'pub_topic': 'pose'}, + {'pub_topics': { + '0,1,2,3,4,5,6,7': '/origin/pose', + '10': '/cars/car0/pose' + }} + ] + ), + Node( + package='rktl_perception', + executable='pose_to_tf', + name='pose_to_tf', + namespace=['cams/', launch.substitutions.LaunchConfiguration('camera_name')], + parameters=[ + {'cam_frame_id': launch.substitutions.LaunchConfiguration('camera_name')} + ] + ), + Node( + package='rktl_perception', + executable='ball_detection', + name='ball_detection', + namespace=['cams/', launch.substitutions.LaunchConfiguration('camera_name')], + parameters = os.path.join(get_package_share_directory('rktl_perception'), 'config', launch.substitutions.LaunchConfiguration('camera_name'), 'ball_settings.yaml') + ) + ], + namespace='cams/' + launch.substitutions.LaunchConfiguration('camera_name') + ) + + return launch.LaunchDescription([load_manager, manager_name, manager_threads, camera_name, ns_group]) \ No newline at end of file diff --git a/src/rktl_perception/launch/color_picker_launch.py b/src/rktl_perception/launch/color_picker_launch.py new file mode 100644 index 000000000..3021866b7 --- /dev/null +++ b/src/rktl_perception/launch/color_picker_launch.py @@ -0,0 +1,42 @@ +import os +import sys + +import launch +import launch_ros.actions + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch_ros.actions.Node( + package='rktl_perception', + executable='ball_color', + name='ball_color', + namespace = 'cams/cam0', + output = 'screen' + ) + #, + # launch_ros.actions.Node( + # package='rktl_perception', + # executable='ball_color', + # name='ball_color', + # namespace = 'cams/cam1', + # output = 'screen' + # ), + # launch_ros.actions.Node( + # package='rktl_perception', + # executable='ball_color', + # name='ball_color', + # namespace = 'cams/cam2', + # output = 'screen' + # ), + # launch_ros.actions.Node( + # package='rktl_perception', + # executable='ball_color', + # name='ball_color', + # namespace = 'cams/cam3', + # output = 'screen' + # ), + ]) + return ld + +if __name__ == '__main__': + generate_launch_description() \ No newline at end of file diff --git a/src/rktl_perception/launch/field_view_launch.py b/src/rktl_perception/launch/field_view_launch.py new file mode 100644 index 000000000..5f7aad83b --- /dev/null +++ b/src/rktl_perception/launch/field_view_launch.py @@ -0,0 +1,45 @@ +import os +import sys + +import launch +from launch_ros.actions import Node + +def generate_launch_description(): + nodes = [ + Node( + package="rktl_perception", + executable="projector", + namespace="cams/cam0", + name="projector", + parameters=[{"ground_height": 0.00}] + ), + Node( + package="rktl_perception", + executable="projector", + namespace="cams/cam1", + name="projector", + parameters=[{"ground_height": -0.02}] + ), + Node( + package="rktl_perception", + executable="projector", + namespace="cams/cam2", + name="projector", + parameters=[{"ground_height": -0.01}] + ), + Node( + package="rktl_perception", + executable="projector", + namespace="cams/cam3", + name="projector", + parameters=[{"ground_height": -0.03}] + ), + Node( + package="rviz", + executable="rviz", + name="rviz", + arguments= "-d " + os.path.join(get_package_share_directory('rktl_perception'), 'rviz', 'field.rviz') + ) + ] + + return launch.LaunchDescription(nodes) \ No newline at end of file diff --git a/src/rktl_perception/launch/focus_assist_launch.py b/src/rktl_perception/launch/focus_assist_launch.py new file mode 100644 index 000000000..8c9c7f804 --- /dev/null +++ b/src/rktl_perception/launch/focus_assist_launch.py @@ -0,0 +1,35 @@ +import os +import sys + +import launch +from launch_ros.actions import Node + +def generate_launch_description(): + nodes = [ + Node( + package="rktl_perception", + executable="focus_vis", + namespace="cams/cam0", + name="focus_vis" + ), + Node( + package="rktl_perception", + executable="focus_vis", + namespace="cams/cam1", + name="focus_vis" + ), + Node( + package="rktl_perception", + executable="focus_vis", + namespace="cams/cam2", + name="focus_vis" + ), + Node( + package="rktl_perception", + executable="focus_vis", + namespace="cams/cam3", + name="focus_vis" + ) + ] + + return launch.LaunchDescription(nodes) \ No newline at end of file diff --git a/rktl_perception/nodes/pose_to_tf b/src/rktl_perception/nodes/pose_to_tf similarity index 63% rename from rktl_perception/nodes/pose_to_tf rename to src/rktl_perception/nodes/pose_to_tf index 94737211b..ebf9f59ed 100755 --- a/rktl_perception/nodes/pose_to_tf +++ b/src/rktl_perception/nodes/pose_to_tf @@ -7,22 +7,25 @@ License: """ # ROS -import rospy +import rclpy from geometry_msgs.msg import PoseWithCovarianceStamped, TransformStamped from tf2_ros import TransformBroadcaster -class Publisher(object): +class Publisher(rclpy.create_node): """Workaround class until TF is implemented""" def __init__(self): - rospy.init_node('pose_to_tf') + #rospy.init_node('pose_to_tf') + rclpy.init_node('pose_to_tf') self.broadcaster = TransformBroadcaster() - self.FRAME = rospy.get_param('~cam_frame_id', 'cam0') + #self.FRAME = rospy.get_param('~cam_frame_id', 'cam0') + self.FRAME = self.get_parameter_or('cam_frame_id', 'cam0').get_parameter_value().string_value - rospy.Subscriber("pose", PoseWithCovarianceStamped, self.pose_cb) - rospy.spin() + #rospy.Subscriber("pose", PoseWithCovarianceStamped, self.pose_cb) + self.subscription = self.create_subscription(PoseWithCovarianceStamped, "pose", self.pose_cb, 1) + #rospy.spin() -> Moved into main function def pose_cb(self, pose_msg): """Publish a pose corresponding to the TF.""" @@ -38,6 +41,12 @@ class Publisher(object): tf.transform.rotation.w = pose_msg.pose.pose.orientation.w self.broadcaster.sendTransform(tf) +def main(args=None): + rclpy.init(arg = args) + publisher = Publisher() + rclpy.spin(publisher) + publisher.destroy_node() + rclpy.shutdown() if __name__ == "__main__": - Publisher() + main() diff --git a/rktl_perception/nodes/projector b/src/rktl_perception/nodes/projector similarity index 74% rename from rktl_perception/nodes/projector rename to src/rktl_perception/nodes/projector index ba861deb8..7df3b070f 100755 --- a/rktl_perception/nodes/projector +++ b/src/rktl_perception/nodes/projector @@ -7,24 +7,28 @@ License: """ # ROS -import rospy +import rclpy from sensor_msgs.msg import Image, CameraInfo from tf2_ros import Buffer, TransformListener -from tf.transformations import translation_matrix, quaternion_matrix +from rktl_dependencies.transformations import translation_matrix, quaternion_matrix import numpy as np -class Projector(object): +class Projector(rclpy.create_node): """Class to synchronize and buffer all poses.""" def __init__(self): - rospy.init_node('projector') - self.depth_pub = rospy.Publisher('depth_rect', Image, queue_size=1) - rospy.Subscriber('camera_info', CameraInfo, self.camera_info_cb) + #self.init_node('projector') + super().init_node('projector') + #self.depth_pub = rospy.Publisher('depth_rect', Image, queue_size=1) + self.depth_pub = self.create_publisher(Image, 'depth_rect', queue_size=1) + #rospy.Subscriber('camera_info', CameraInfo, self.camera_info_cb) + self.subscription = self.create_subscription(CameraInfo, 'camera_info', self.camera_info_cb, 1) # constants - self.GROUND_HEIGHT = rospy.get_param('~ground_height', 0.0) + #self.GROUND_HEIGHT = rospy.get_param('~ground_height', 0.0) + self.GROUND_HEIGHT = self.get_parameter_or('ground_height', 0.0).get_parameter_value().float_value # used to access TF tree buffer = Buffer() @@ -41,19 +45,25 @@ class Projector(object): self.img_msg = None # rate at which to re-compute depth map - rate = rospy.Rate(1.0/rospy.get_param('~update_period', 1)) + #rate = rospy.Rate(1.0/rospy.get_param('~update_period', 1)) + #rate = rclpy.Rate(1.0/rclpy.get_parameter_or('update_period', 1).get_parameter_value().integer_value) + self._loop_rate = self.create_rate(1.0/self.get_parameter_or('update_period', 1).get_parameter_value().integer_value, self.get_clock()) # compute depth map in main loop and cache since it is slow changing # publish latest in callback so that the stamps match (for rviz) - while not rospy.is_shutdown(): + while not rclpy.ok(): # check if camera_info callback has been run if not self.initialized: continue # get camera location via TF tree + #try: + # map_to_cam_msg = buffer.lookup_transform( + # self.frame_id, 'map', rospy.Time()) + #except: try: map_to_cam_msg = buffer.lookup_transform( - self.frame_id, 'map', rospy.Time()) + self.frame_id, 'map', self.get_clock().now().to_msg()) except: continue @@ -115,7 +125,8 @@ class Projector(object): # delay since making the depth map is expensive try: - rate.sleep() + self._loop_rate.sleep() + #rate.sleep() except: pass @@ -134,6 +145,11 @@ class Projector(object): self.img_msg.header.stamp = info_msg.header.stamp self.depth_pub.publish(self.img_msg) +def main(args=None): + rclpy.init(args=args) + projector = Projector() + rclpy.spin(projector) + rclpy.shutdown() if __name__ == "__main__": - Projector() + main() diff --git a/src/rktl_perception/package.xml b/src/rktl_perception/package.xml new file mode 100644 index 000000000..1ead5abdc --- /dev/null +++ b/src/rktl_perception/package.xml @@ -0,0 +1,42 @@ + + + + rktl_perception + 0.0.0 + TODO: Package description + DinoSage + TODO: License declaration + + ament_cmake + + rclcpp + + pointgrey_camera_driver + image_proc + apriltag_ros + tf2 + tf2_ros + image_transport + cv_bridge + nodelet + + OpenCV + Eigen3 + geometry_msgs + image_geometry + + + camera_calibration + rviz + ros2launch + + + + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/src/rktl_perception/resource/rktl_perception b/src/rktl_perception/resource/rktl_perception new file mode 100644 index 000000000..e69de29bb diff --git a/src/rktl_perception/rktl_perception/__init__.py b/src/rktl_perception/rktl_perception/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rktl_perception/rviz/field.rviz b/src/rktl_perception/rviz/field.rviz similarity index 100% rename from rktl_perception/rviz/field.rviz rename to src/rktl_perception/rviz/field.rviz diff --git a/rktl_perception/scripts/calibrate_bundle.m b/src/rktl_perception/scripts/calibrate_bundle.m similarity index 100% rename from rktl_perception/scripts/calibrate_bundle.m rename to src/rktl_perception/scripts/calibrate_bundle.m diff --git a/src/rktl_perception/setup.cfg b/src/rktl_perception/setup.cfg new file mode 100644 index 000000000..d2a7845d8 --- /dev/null +++ b/src/rktl_perception/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rktl_perception +[install] +install_scripts=$base/lib/rktl_perception diff --git a/src/rktl_perception/setup.py b/src/rktl_perception/setup.py new file mode 100644 index 000000000..98825b80e --- /dev/null +++ b/src/rktl_perception/setup.py @@ -0,0 +1,30 @@ +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = 'rktl_perception' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))), + (os.path.join('lib', package_name), glob(os.path.join(package_name, '*.py'))), + (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.yaml'))), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='zakolano', + maintainer_email='zakolano@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + ], + }, +) diff --git a/rktl_perception/src/ball_color.cpp b/src/rktl_perception/src/ball_color.cpp similarity index 90% rename from rktl_perception/src/ball_color.cpp rename to src/rktl_perception/src/ball_color.cpp index 193199ead..81e2720fa 100644 --- a/rktl_perception/src/ball_color.cpp +++ b/src/rktl_perception/src/ball_color.cpp @@ -6,8 +6,8 @@ */ /* ros */ -#include -#include +#include +#include /* image */ #include @@ -23,6 +23,9 @@ /* messages */ #include +#include +#include + const int max_value_H = 360 / 2; const int max_value = 255; const std::string window_capture_name = "Video Capture"; @@ -89,13 +92,17 @@ void imageCallback(const sensor_msgs::ImageConstPtr& msg) { cv::imshow(window_detection_name, frame_threshold); cv::waitKey(30); } catch (cv_bridge::Exception& e) { - ROS_ERROR("Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str()); + //ROS_ERROR("Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str()); + RCLCPP_ERROR(node->get_logger(), "Could not convert from '%s' to 'bgr8'.", msg->encoding.c_str()); } } int main(int argc, char* argv[]) { - ros::init(argc, argv, "image_listener"); - ros::NodeHandle nh; + //ros::init(argc, argv, "image_listener"); + //ros::NodeHandle nh; + rclcpp::init(argc, argv); + auto node = rclcpp:Node::make_shared("image_listener"); + cv::namedWindow(window_detection_name); // Trackbars to set thresholds for HSV values @@ -117,7 +124,7 @@ int main(int argc, char* argv[]) { image_transport::ImageTransport it(nh); image_transport::Subscriber sub = it.subscribe("image_rect_color", 10, imageCallback); - ros::spin(); + rclcpp::spin(); cv::destroyWindow(window_detection_name); return 0; diff --git a/rktl_perception/src/ball_detection.cpp b/src/rktl_perception/src/ball_detection.cpp similarity index 75% rename from rktl_perception/src/ball_detection.cpp rename to src/rktl_perception/src/ball_detection.cpp index 12faba200..488688940 100644 --- a/rktl_perception/src/ball_detection.cpp +++ b/src/rktl_perception/src/ball_detection.cpp @@ -5,28 +5,29 @@ * All rights reserved. */ -#include +#include "rktl_perception/ball_detection.h" namespace rktl_perception { int getMaxAreaContourId(std::vector> contours); -BallDetection::BallDetection(const ros::NodeHandle& nh, const ros::NodeHandle& pnh) - : _nh(nh), _pnh(pnh), _it(nh) { - _vecPub = _nh.advertise("ball_vec", 10); + +BallDetection::BallDetection(const std::shared_ptr& node) : _node(node), _it(node) { + _vecPub = _node->create_publisher("ball_vec", 10); _imgPub = _it.advertise("threshold_img", 1); - _cameraSub = _it.subscribeCamera("image_rect_color", 10, &BallDetection::ballCallback, this); + _cameraSub = _it.subscribe_camera("image_rect_color", 10, std::bind(&BallDetection::ballCallback, this, std::placeholders::_1, std::placeholders::_2)); + // future plan for this node is to make all of these adjustable with dynamic_reconfigure - _publishThresh = _pnh.param("publishThresh", false); - _minHue = _pnh.param("min_hue", 050); - _maxHue = _pnh.param("max_hue", 100); - _minSat = _pnh.param("min_sat", 075); - _maxSat = _pnh.param("max_sat", 180); - _minVib = _pnh.param("min_vib", 040); - _maxVib = _pnh.param("max_vib", 100); - _minSize = _pnh.param("min_size", 50); - _erodeAmnt = _pnh.param("erode", 4); - _dilateAmnt = _pnh.param("dilate", 5); + _publishThresh = _node->declare_parameter("publishThresh", false); + _minHue = _node->declare_parameter("min_hue", 50); + _maxHue = _node->declare_parameter("max_hue", 100); + _minSat = _node->declare_parameter("min_sat", 75); + _maxSat = _node->declare_parameter("max_sat", 180); + _minVib = _node->declare_parameter("min_vib", 40); + _maxVib = _node->declare_parameter("max_vib", 100); + _minSize = _node->declare_parameter("min_size", 50); + _erodeAmnt = _node->declare_parameter("erode", 4); + _dilateAmnt = _node->declare_parameter("dilate", 5); } void BallDetection::ballCallback(const sensor_msgs::ImageConstPtr& msg, @@ -88,7 +89,8 @@ void BallDetection::ballCallback(const sensor_msgs::ImageConstPtr& msg, _vecPub.publish(vec); /* debug the size of the countour */ - ROS_INFO("Size of the largest ball: %.0f\n", largestContourArea); + //ROS_INFO("Size of the largest ball: %.0f\n", largestContourArea); + RCLCPP_INFO(node->get_logger(), "Size of the largest ball: %.0f\n", largestContourArea); /* publishes the threshold image */ if (_publishThresh) { @@ -103,7 +105,9 @@ void BallDetection::ballCallback(const sensor_msgs::ImageConstPtr& msg, } } } catch (cv_bridge::Exception& e) { - ROS_ERROR("There was some error, likely with image conversion"); + + //ROS_ERROR("There was some error, likely with image conversion"); + RCLCPP_ERROR(node->get_logger(), "There was some error, likely with image conversion"); } } diff --git a/src/rktl_perception/src/ball_detection_node.cpp b/src/rktl_perception/src/ball_detection_node.cpp new file mode 100644 index 000000000..5ea110c71 --- /dev/null +++ b/src/rktl_perception/src/ball_detection_node.cpp @@ -0,0 +1,20 @@ +/* Node to detect the ball's position on the field. + * License: + * BSD 3-Clause License + * Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + * All rights reserved. + */ + +#include "rclcpp/rclcpp.hpp" +#include "rktl_perception/ball_detection.h" + +int main(int argc, char* argv[]) { + rclcpp::init(argc, argv); + auto node = std::make_shared("ball_detection"); + + auto ballDetection = std::make_shared(node); + + rclcpp::spin(node); + rclcpp::shutdown(); + return 0; +} \ No newline at end of file diff --git a/rktl_perception/src/focus_vis.cpp b/src/rktl_perception/src/focus_vis.cpp similarity index 90% rename from rktl_perception/src/focus_vis.cpp rename to src/rktl_perception/src/focus_vis.cpp index 511f57285..d2d536296 100644 --- a/rktl_perception/src/focus_vis.cpp +++ b/src/rktl_perception/src/focus_vis.cpp @@ -7,9 +7,11 @@ #include +rclcpp::init(argc, argv); + namespace rktl_perception { -FocusVis::FocusVis(const ros::NodeHandle& nh) : _nh(nh), _it(_nh) { +FocusVis::FocusVis(const std::shared_ptr& node) : _node(node), _it(_node) { _imgPub = _it.advertise("edge_dectection", 1); _cameraSub = _it.subscribeCamera("image_rect_color", 10, &FocusVis::focusCallback, this); } @@ -33,7 +35,7 @@ void FocusVis::focusCallback(const sensor_msgs::ImageConstPtr& msg, sensor_msgs::Image edgeImg; std_msgs::Header header; cv_bridge::CvImage img_bridge; - header.stamp = ros::Time::now(); + header.stamp = rclcpp::Time::now(); img_bridge = cv_bridge::CvImage(header, sensor_msgs::image_encodings::MONO8, frame_edge); img_bridge.toImageMsg(edgeImg); _imgPub.publish(edgeImg); diff --git a/rktl_perception/src/focus_vis_node.cpp b/src/rktl_perception/src/focus_vis_node.cpp similarity index 62% rename from rktl_perception/src/focus_vis_node.cpp rename to src/rktl_perception/src/focus_vis_node.cpp index dd8ad11e3..e1e2e7d82 100644 --- a/rktl_perception/src/focus_vis_node.cpp +++ b/src/rktl_perception/src/focus_vis_node.cpp @@ -8,11 +8,10 @@ #include int main(int argc, char* argv[]) { - ros::init(argc, argv, "focus_vis"); + rclcpp::init(argc, argv); + auto node = rclcpp:Node::make_shared("focus_vis"); + rktl_perception::FocusVis focusVis(node); - ros::NodeHandle nh; - rktl_perception::FocusVis focusVis(nh); - - ros::spin(); + rclcpp::spin(); return 0; } diff --git a/rktl_perception/src/localizer.cpp b/src/rktl_perception/src/localizer.cpp similarity index 80% rename from rktl_perception/src/localizer.cpp rename to src/rktl_perception/src/localizer.cpp index 45e7df904..388c12a47 100644 --- a/rktl_perception/src/localizer.cpp +++ b/src/rktl_perception/src/localizer.cpp @@ -9,7 +9,7 @@ namespace rktl_perception { -Localizer::Localizer(const ros::NodeHandle& nh, const ros::NodeHandle& pnh) : _nh(nh), _pnh(pnh) { +Localizer::Localizer() : _node(node) { std::string detectionTopic; std::string pubTopic; std::map pubTopics; @@ -17,23 +17,23 @@ Localizer::Localizer(const ros::NodeHandle& nh, const ros::NodeHandle& pnh) : _n std::string ballPubTopic; int queueSize; - _pnh.param("dectection_topic", detectionTopic, "tag_detections"); - _pnh.param("origin_id", _originId, "0"); - _pnh.getParam("pub_topic", pubTopic); - _pnh.getParam("pub_topics", pubTopics); - _pnh.param("ball_sub_topic", ballSubTopic, "ball_vector"); - _pnh.param("ball_pub_topic", ballPubTopic, "ball_pos"); - _pnh.param("ball_radius", _ballRadius, 0.05); - _pnh.param("buffer_size", _bufferSize, 10); - _pnh.param("queue_size", queueSize, 100); - - _sub = _nh.subscribe(detectionTopic, queueSize, &Localizer::apriltagCallback, this); - _pub = _nh.advertise(pubTopic, queueSize); - _ballSub = _nh.subscribe(ballSubTopic, queueSize, &Localizer::ballCallback, this); - _ballPub = _nh.advertise(ballPubTopic, queueSize); + _node->param("dectection_topic", detectionTopic, "tag_detections"); + _node->param("origin_id", _originId, "0"); + _node->getParam("pub_topic", pubTopic); + _node->getParam("pub_topics", pubTopics); + _node->param("ball_sub_topic", ballSubTopic, "ball_vector"); + _node->param("ball_pub_topic", ballPubTopic, "ball_pos"); + _node->param("ball_radius", _ballRadius, 0.05); + _node->param("buffer_size", _bufferSize, 10); + _node->param("queue_size", queueSize, 100); + + _sub = _node->subscribe(detectionTopic, queueSize, &Localizer::apriltagCallback, this); + _pub = _node->advertise(pubTopic, queueSize); + _ballSub = _node->subscribe(ballSubTopic, queueSize, &Localizer::ballCallback, this); + _ballPub = _node->advertise(ballPubTopic, queueSize); for (auto& kv : pubTopics) _pubs[kv.first] = - _nh.advertise(kv.second, queueSize); + _node->advertise(kv.second, queueSize); _bufferPos = 0; _buffer.reserve(_bufferSize); @@ -134,7 +134,7 @@ Eigen::Matrix4d Localizer::combineMatrices(const Eigen::Matrix3d& rot, const Eig } geometry_msgs::PoseWithCovarianceStamped Localizer::toMsg(const Eigen::Matrix4d& transform, - ros::Time stamp, + rclcpp::Time stamp, const std::string& frameId) { Eigen::Quaterniond rot(transform.topLeftCorner<3, 3>()); Eigen::Vector3d pos(transform.col(3).head<3>()); diff --git a/rktl_perception/src/localizer_node.cpp b/src/rktl_perception/src/localizer_node.cpp similarity index 52% rename from rktl_perception/src/localizer_node.cpp rename to src/rktl_perception/src/localizer_node.cpp index 5ee10e69d..d1d1a3c78 100644 --- a/rktl_perception/src/localizer_node.cpp +++ b/src/rktl_perception/src/localizer_node.cpp @@ -8,11 +8,14 @@ #include int main(int argc, char* argv[]) { - ros::init(argc, argv, "localizer"); + // ros::init(argc, argv, "localizer"); + // ros::NodeHandle nh; + // ros::NodeHandle pnh("~"); - ros::NodeHandle nh; - ros::NodeHandle pnh("~"); - rktl_perception::Localizer localizer(nh, pnh); + rclcpp::init(argc, argv); + auto node = rclcpp:Node::make_shared("localizer"); + + rktl_perception::Localizer localizer(node); ros::spin(); return 0; diff --git a/src/rktl_perception/test/test_copyright.py b/src/rktl_perception/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/src/rktl_perception/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/rktl_perception/test/test_flake8.py b/src/rktl_perception/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/src/rktl_perception/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/rktl_perception/test/test_pep257.py b/src/rktl_perception/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/src/rktl_perception/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/rktl_planner/README.md b/src/rktl_planner/README.md similarity index 100% rename from rktl_planner/README.md rename to src/rktl_planner/README.md diff --git a/src/rktl_planner/config/path_follower.yaml b/src/rktl_planner/config/path_follower.yaml new file mode 100644 index 000000000..6f4fa87e0 --- /dev/null +++ b/src/rktl_planner/config/path_follower.yaml @@ -0,0 +1,5 @@ +/**: + ros__parameters: + lookahead_dist: 0.25 + lookahead_gain: 0.1 + lookahead_pnts: -1 diff --git a/src/rktl_planner/config/path_planner.yaml b/src/rktl_planner/config/path_planner.yaml new file mode 100644 index 000000000..f5c1fe661 --- /dev/null +++ b/src/rktl_planner/config/path_planner.yaml @@ -0,0 +1,3 @@ +/**: + ros__parameters: + planner_type: 'complex' \ No newline at end of file diff --git a/src/rktl_planner/config/patrol_planner.yaml b/src/rktl_planner/config/patrol_planner.yaml new file mode 100644 index 000000000..a5ff6fe3b --- /dev/null +++ b/src/rktl_planner/config/patrol_planner.yaml @@ -0,0 +1,27 @@ +/**: + ros__parameters: + # tuned params for no prediction at 0.15 delay + wall_avoidance: + reverse_time: 0.125 + distance_margin: 0.375 + heading_margin: 1.57 + + patrol_wall_dist: 0.75 + + curvature_gain: + kp: 2.0 + kd: 0.05 + falloff: 0.25 + + scoring: + car_lookahead_dist: 0.75 + goal_depth_target: 0.05 + + # X coordinate to switch to defense + defensive_line: -0.5 + + # Y coordinate to switch to reverse + reverse_line: 0.0 + + speed: 1.75 + attempt_timeout: 4.0 diff --git a/rktl_planner/launch/README.md b/src/rktl_planner/launch/README.md similarity index 100% rename from rktl_planner/launch/README.md rename to src/rktl_planner/launch/README.md diff --git a/rktl_planner/launch/patrol_agent.launch b/src/rktl_planner/launch/patrol_agent.launch similarity index 100% rename from rktl_planner/launch/patrol_agent.launch rename to src/rktl_planner/launch/patrol_agent.launch diff --git a/src/rktl_planner/launch/patrol_agent.launch.py b/src/rktl_planner/launch/patrol_agent.launch.py new file mode 100644 index 000000000..0f32b210f --- /dev/null +++ b/src/rktl_planner/launch/patrol_agent.launch.py @@ -0,0 +1,31 @@ +import launch +import launch_ros.actions +from launch.substitutions import PathJoinSubstitution +from ament_index_python.packages import get_package_share_directory +import os + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='car_name', + default_value='car0' + ), + launch_ros.actions.Node( + package='rktl_planner', + namespace=PathJoinSubstitution(["cars", launch.substitutions.LaunchConfiguration("car_name")]), + executable='patrol_planner', + name='patrol_planner', + output='screen', + parameters=[ + get_package_share_directory('rktl_planner') + '/config/patrol_planner.yaml', + os.path.join(get_package_share_directory( + 'rktl_launch'), 'config', 'global_params.yaml') + ] + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() diff --git a/rktl_planner/launch/simple_agent.launch b/src/rktl_planner/launch/simple_agent.launch similarity index 100% rename from rktl_planner/launch/simple_agent.launch rename to src/rktl_planner/launch/simple_agent.launch diff --git a/src/rktl_planner/launch/simple_agent.launch.py b/src/rktl_planner/launch/simple_agent.launch.py new file mode 100644 index 000000000..da3dfaa73 --- /dev/null +++ b/src/rktl_planner/launch/simple_agent.launch.py @@ -0,0 +1,59 @@ +import launch +import launch_ros.actions +from ament_index_python.packages import get_package_share_directory +import os + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='agent_name', + default_value='agent0' + ), + launch.actions.DeclareLaunchArgument( + name='car_name', + default_value='car0' + ), + launch.actions.GroupAction( + actions=[ + launch_ros.actions.PushRosNamespace("agents/agent0"), + launch_ros.actions.Node( + package="rktl_planner", + executable="path_follower", + name="path_follower", + output="screen", + parameters=[ + { + os.path.join(get_package_share_directory('rktl_planner'), 'config', 'path_follower.yaml') + }, + { + "car_name": launch.substitutions.LaunchConfiguration("car_name") + } + ] + ), + launch_ros.actions.Node( + package="rktl_planner", + executable="bezier_path_server", + name="bezier_path_server", + output="screen" + ), + launch_ros.actions.Node( + package="rktl_planner", + executable="path_planner", + name="path_planner", + output="screen", + parameters=[ + { + os.path.join(get_package_share_directory('rktl_planner'), 'config', 'path_planner.yaml') + }, + { + "car_name": launch.substitutions.LaunchConfiguration("car_name") + } + ] + ) + ] + ) + ]) + return ld + +if __name__ == '__main__': + generate_launch_description() diff --git a/rktl_planner/package.xml b/src/rktl_planner/package.xml similarity index 68% rename from rktl_planner/package.xml rename to src/rktl_planner/package.xml index 736543b83..4718f723d 100644 --- a/rktl_planner/package.xml +++ b/src/rktl_planner/package.xml @@ -1,9 +1,9 @@ - + + rktl_planner 1.0.0 Handles localized actions to plan and follow trajectories. - Autonomous Robotics Club of Purdue BSD 3 Clause https://www.purduearc.com @@ -13,9 +13,12 @@ Adrian Teigen Daniel Gerblick - catkin + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest geometry_msgs - rospy + rclpy std_msgs nav_msgs geometry_msgs @@ -23,7 +26,14 @@ tf python-numpy rktl_msgs + rktl_dependencies rktl_msgs message_generation message_runtime - \ No newline at end of file + ros2launch + + + ament_python + + + diff --git a/src/rktl_planner/resource/rktl_planner b/src/rktl_planner/resource/rktl_planner new file mode 100644 index 000000000..e69de29bb diff --git a/src/rktl_planner/rktl_planner/__init__.py b/src/rktl_planner/rktl_planner/__init__.py new file mode 100644 index 000000000..e4fac00e0 --- /dev/null +++ b/src/rktl_planner/rktl_planner/__init__.py @@ -0,0 +1,14 @@ +""" +License: + BSD 3-Clause License + Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + All rights reserved. +""" + +from .bezier_curve import BezierCurve +from .bezier_path import BezierPath + +__all__ = [ + "BezierCurve", + "BezierPath" +] \ No newline at end of file diff --git a/rktl_planner/src/rktl_planner/bezier_curve.py b/src/rktl_planner/rktl_planner/bezier_curve.py similarity index 100% rename from rktl_planner/src/rktl_planner/bezier_curve.py rename to src/rktl_planner/rktl_planner/bezier_curve.py diff --git a/rktl_planner/src/rktl_planner/bezier_path.py b/src/rktl_planner/rktl_planner/bezier_path.py similarity index 98% rename from rktl_planner/src/rktl_planner/bezier_path.py rename to src/rktl_planner/rktl_planner/bezier_path.py index dd15615db..f99a45151 100644 --- a/rktl_planner/src/rktl_planner/bezier_path.py +++ b/src/rktl_planner/rktl_planner/bezier_path.py @@ -10,9 +10,8 @@ import math from rktl_planner import BezierCurve from rktl_msgs.msg import BezierPath as BezierPathMsg -from rospy import Duration +from rclpy.duration import Duration from geometry_msgs.msg import Vector3 -from std_msgs.msg import Duration as DurationMsg class BezierPath: @@ -127,7 +126,7 @@ def angular_vel_at(self, secs): def to_msg(self): """Returns a `rktl_msg/BezierPathMsg` object representing the `BezierPath` object.""" - duration_msg = DurationMsg(self.duration) + duration_msg = Duration(self.duration).to_msg() msg = BezierPathMsg( order=self.bezier_curve.order, control_points=self.bezier_curve.control_points, diff --git a/rktl_planner/src/rktl_planner/convert.py b/src/rktl_planner/rktl_planner/convert.py similarity index 100% rename from rktl_planner/src/rktl_planner/convert.py rename to src/rktl_planner/rktl_planner/convert.py diff --git a/rktl_planner/nodes/README.md b/src/rktl_planner/rktl_planner/nodes/README.md similarity index 100% rename from rktl_planner/nodes/README.md rename to src/rktl_planner/rktl_planner/nodes/README.md diff --git a/src/rktl_planner/rktl_planner/nodes/__init__.py b/src/rktl_planner/rktl_planner/nodes/__init__.py new file mode 100644 index 000000000..fe211ef4a --- /dev/null +++ b/src/rktl_planner/rktl_planner/nodes/__init__.py @@ -0,0 +1,16 @@ +""" +License: + BSD 3-Clause License + Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + All rights reserved. +""" + +from .path_follower import PathFollower +from .path_planner import PathPlanner +from .patrol_planner import PatrolPlanner + +__all__ = [ + "PathFollower", + "PatrolPlanner", + "PathPlanner" +] \ No newline at end of file diff --git a/rktl_planner/nodes/bezier_path_server b/src/rktl_planner/rktl_planner/nodes/bezier_path_server.py similarity index 80% rename from rktl_planner/nodes/bezier_path_server rename to src/rktl_planner/rktl_planner/nodes/bezier_path_server.py index 2f3b95d79..7099711d3 100755 --- a/rktl_planner/nodes/bezier_path_server +++ b/src/rktl_planner/rktl_planner/nodes/bezier_path_server.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -import rospy +#import rospy +import rclpy import math -from tf.transformations import euler_from_quaternion, quaternion_from_euler -from rktl_planner.srv import CreateBezierPath, CreateBezierPathRequest, CreateBezierPathResponse +from rktl_dependencies.transformations import euler_from_quaternion, quaternion_from_euler +from rktl_msgs.srv import CreateBezierPath from rktl_planner import BezierPath from rktl_msgs.msg import Path as PathMsg, Waypoint as WaypointMsg from geometry_msgs.msg import Pose, Point, Vector3 @@ -30,7 +31,8 @@ def line(p: Point, v: Vector3, t: float): def create_segment(start_pose: Pose, end_pose: Pose, velocity: float, - duration: rospy.Duration, segment_duration: rospy.Duration): + # duration: rospy.Duration, segment_duration: rospy.Duration): + duration: rclpy.duration.Duration, segment_duration: rclpy.duration.Duration): control_points = [start_pose.position, end_pose.position] start_vel, start_head = vel(start_pose, velocity) end_vel, end_head = vel(end_pose, velocity) @@ -38,8 +40,7 @@ def create_segment(start_pose: Pose, end_pose: Pose, velocity: float, t_val = duration.to_sec() / 3 control_points.insert(1, line(control_points[0], start_vel, t_val)) - control_points.insert(len(control_points) - 1, - line(control_points[-1], end_vel, -t_val)) + control_points.insert(len(control_points) - 1, line(control_points[-1], end_vel, -t_val)) path = BezierPath(control_points, duration.to_sec()) num_segments = math.ceil(duration.to_sec() / segment_duration.to_sec()) segment_length = duration.to_sec() / num_segments @@ -52,7 +53,7 @@ def create_segment(start_pose: Pose, end_pose: Pose, velocity: float, return segments -def bezier_path_server(req: CreateBezierPathRequest): +def bezier_path_server(req: CreateBezierPath.Request): velocity = req.velocity bezier_segments = [] durations = [] @@ -69,7 +70,7 @@ def bezier_path_server(req: CreateBezierPathRequest): else: durations.append(segment.duration.to_sec()) - res = CreateBezierPathResponse() + res = CreateBezierPath.Response() res.bezier_paths = [x.to_msg() for x in bezier_segments] res.linear_path = PathMsg() res.linear_path.velocity = velocity @@ -95,8 +96,11 @@ def bezier_path_server(req: CreateBezierPathRequest): idx += 1 return res +def main(): + rclpy.init() + node = rclpy.create_node('bezier_path_server') + service = node.create_service(CreateBezierPath, 'create_bezier_path', bezier_path_server) + rclpy.spin(node) + if __name__ == '__main__': - rospy.init_node('bezier_path_server') - service = rospy.Service('create_bezier_path', - CreateBezierPath, bezier_path_server) - rospy.spin() + main() diff --git a/rktl_planner/nodes/path_follower b/src/rktl_planner/rktl_planner/nodes/path_follower.py similarity index 67% rename from rktl_planner/nodes/path_follower rename to src/rktl_planner/rktl_planner/nodes/path_follower.py index 3a2fae578..565288879 100755 --- a/rktl_planner/nodes/path_follower +++ b/src/rktl_planner/rktl_planner/nodes/path_follower.py @@ -7,7 +7,8 @@ """ # 3rd party modules -import rospy +import rclpy +#import rospy import numpy as np import math import time @@ -23,7 +24,9 @@ class PathFollower(object): """A node to ensure the car follows a given trajectory.""" def __init__(self): - rospy.init_node('path_follower') + rclpy.init() + global node + node = rclpy.create_node('path_follower') self.path_start_time = None self.path = None @@ -31,30 +34,37 @@ def __init__(self): self.start_time = None self.max_speed = None - self.frame_id = rospy.get_param('~frame_id', 'map') + node.declare_parameters(namespace='', parameters=[ + ('frame_id', "map"), + ('max_speed', 0.1), + ('lookahead_dist', 0.15), + ('lookahead_gain', 0.055), + ('lookahead_pnts', -1), + ('car_name', "car0"), + ]) + + self.frame_id = node.get_parameter('frame_id').get_parameter_value().string_value # Max speed to travel path - self.max_speed = rospy.get_param('~max_speed', 0.1) + self.max_speed = node.get_parameter('max_speed').get_parameter_value().double_value # Radius to search for intersections - self.lookahead_dist = rospy.get_param('~lookahead_dist', 0.15) + self.lookahead_dist = node.get_parameter('lookahead_dist').get_parameter_value().double_value # Coeffient to adjust lookahead distance by speed - self.lookahead_gain = rospy.get_param('~lookahead_gain', 0.035) + self.lookahead_gain = node.get_parameter('lookahead_gain').get_parameter_value().double_value # Number of waypoints to search per pass (-1 is full path) - self.lookahead_pnts = rospy.get_param('~lookahead_pnts', -1) + self.lookahead_pnts = node.get_parameter('lookahead_pnts').get_parameter_value().integer_value # Publishers - car_name = rospy.get_param('~car_name') - self.bot_velocity_cmd = rospy.Publisher(f'/cars/{car_name}/command', - ControlCommand, queue_size=1) - + car_name = node.get_parameter('car_name').get_parameter_value().string_value + # Subscribers - rospy.Subscriber(f'/cars/{car_name}/odom', Odometry, self.odom_cb) - rospy.Subscriber('linear_path', Path, self.path_cb) + node.create_subscription(Odometry, f'/cars/{car_name}/odom', self.odom_cb, qos_profile=1) + node.create_subscription(Path, 'linear_path', self.path_cb, qos_profile=1) - rospy.spin() + rclpy.spin(node) def path_cb(self, path_msg: Path): """Creates path using waypoints in Path message.""" @@ -76,15 +86,14 @@ def odom_cb(self, odom_msg: Odometry): odom_msg.twist) # Set lookahead dist by lookahead gain and current speed - lookahead_boost = np.linalg.norm(bot_linear) \ - * self.lookahead_gain - lookahead_dist = self.lookahead_dist + lookahead_boost - + lookahead_boost = np.linalg.norm(bot_linear) * self.lookahead_gain.get_parameter_value().double_value + lookahead_dist = rclpy.Parameter('~lookahead_dist', rclpy.Parameter.Type.DOUBLE, self.lookahead_dist.get_parameter_value().double_value + lookahead_boost) + # Set number of waypoints to check - if self.lookahead_pnts == -1: + if self.lookahead_pnts.get_parameter_value().integer_value == -1: lookahead_pnts = len(self.path) else: - lookahead_pnts = self.lookahead_pnts + lookahead_pnts = self.lookahead_pnts.get_parameter_value().integer_value # Find next valid intersection along path intersect = None @@ -122,7 +131,8 @@ def odom_cb(self, odom_msg: Odometry): # If no intersection found, stop moving if intersect is None: self.bot_velocity_cmd.publish(ControlCommand()) - rospy.logwarn("No intersection could be found.") + node.get_logger().warn("No intersection could be found.") + #rospy.logwarn("No intersection could be found.") return # Calculate curvature @@ -135,6 +145,8 @@ def odom_cb(self, odom_msg: Odometry): cmd.curvature = -(1.0 / turn_rad) self.bot_velocity_cmd.publish(cmd) +def main(): + PathFollower() if __name__ == "__main__": - PathFollower() + main() diff --git a/rktl_planner/nodes/path_planner b/src/rktl_planner/rktl_planner/nodes/path_planner.py similarity index 67% rename from rktl_planner/nodes/path_planner rename to src/rktl_planner/rktl_planner/nodes/path_planner.py index 07cd6b9dc..34b90dc89 100755 --- a/rktl_planner/nodes/path_planner +++ b/src/rktl_planner/rktl_planner/nodes/path_planner.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 import math -import rospy +import rclpy import numpy as np -from tf.transformations import quaternion_from_euler, euler_from_quaternion -from std_msgs.msg import Duration +from rktl_dependencies.transformations import quaternion_from_euler, euler_from_quaternion +from rclpy.duration import Duration from nav_msgs.msg import Odometry from geometry_msgs.msg import Pose from rktl_msgs.msg import BezierPathList, Path -from std_srvs.srv import Empty, EmptyRequest, EmptyResponse -from rktl_planner.srv import CreateBezierPath, CreateBezierPathRequest +from std_srvs.srv import Empty, Empty_Request, Empty_Response +from rktl_msgs.srv import CreateBezierPath def create_simple_path_req(car_odom, ball_odom, goal_pos): - req = CreateBezierPathRequest() + req = CreateBezierPath.Request() req.velocity = 0.5 - req.bezier_segment_duration.data = rospy.Duration(0.5) - req.linear_segment_duration.data = rospy.Duration(0.01) + req.bezier_segment_duration.data = node.create_rate(0.5) + req.linear_segment_duration.data = node.create_rate(0.01) # Target 0 (car pos) pose0 = Pose() @@ -25,7 +25,7 @@ def create_simple_path_req(car_odom, ball_odom, goal_pos): pose0.position.z = 0.0 pose0.orientation = car_odom.pose.pose.orientation req.target_poses.append(pose0) - req.target_durations.append(Duration(data=rospy.Duration(5.0))) + req.target_durations.append(Duration(data=node.create_rate(5.0)).to_msg()) # Target 1 (ball pos) final_vec_x = goal_pos[0] - ball_odom.pose.pose.position.x @@ -42,7 +42,7 @@ def create_simple_path_req(car_odom, ball_odom, goal_pos): pose1.orientation.z = final_quat[2] pose1.orientation.w = final_quat[3] req.target_poses.append(pose1) - req.target_durations.append(Duration(data=rospy.Duration(1.0))) + req.target_durations.append(Duration(data=node.create_rate(1.0)).to_msg()) # Target 2 (stop) pose2 = Pose() @@ -57,8 +57,8 @@ def create_simple_path_req(car_odom, ball_odom, goal_pos): def create_backup_path_req(car_odom, ball_odom, goal_pos): req = CreateBezierPathRequest() req.velocity = -0.5 - req.bezier_segment_duration.data = rospy.Duration(0.5) - req.linear_segment_duration.data = rospy.Duration(0.01) + req.bezier_segment_duration.data = node.create_rate(0.5) + req.linear_segment_duration.data = node.create_rate(0.01) # Target 0 (car pos) pose0 = Pose() @@ -67,7 +67,7 @@ def create_backup_path_req(car_odom, ball_odom, goal_pos): pose0.position.z = 0.0 pose0.orientation = car_odom.pose.pose.orientation req.target_poses.append(pose0) - req.target_durations.append(Duration(data=rospy.Duration(1.0))) + req.target_durations.append(Duration(data=node.create_rate(1.0)).to_msg()) # Target 1 (in front of ball) final_vec_x = goal_pos[0] - ball_odom.pose.pose.position.x @@ -99,37 +99,38 @@ def create_complex_path_req(car_odom, ball_odom, goal_pos): class PathPlanner(object): def __init__(self): - rospy.init_node('path_planner') - - planner_type = rospy.get_param('~planner_type', 'simple') + rclpy.init() + global node + node = rclpy.create_node('path_planner') + node.declare_parameter('planner_type', 'simple') + planner_type = node.get_parameter('planner_type').get_parameter_value().string_value if planner_type == 'simple': self.path_req = create_simple_path_req elif planner_type == 'complex': self.path_req = create_complex_path_req else: - raise NotImplementedError(f'unrecognized planner type: {rospy.get_param("~planner_type")}') + raise NotImplementedError(f'unrecognized planner type: {planner_type}') # Subscribers - car_name = rospy.get_param('~car_name') - rospy.Subscriber(f'/cars/{car_name}/odom', Odometry, self.car_odom_cb) - rospy.Subscriber('/ball/odom', Odometry, self.ball_odom_cb) + node.declare_parameter('car_name') + car_name = node.get_parameter('car_name').get_parameter_value().string_value + node.create_subscription(Odometry, f'/cars/{car_name}/odom', self.car_odom_cb, 1) + node.create_subscription(Odometry, '/ball/odom', self.ball_odom_cb, 1) # Services - self.reset_server = rospy.Service('reset_planner', Empty, self.reset) - self.path_client = rospy.ServiceProxy('create_bezier_path', CreateBezierPath) + self.reset_server = node.create_service(Empty, 'reset_planner', self.reset) + self.path_client = node.create_client(CreateBezierPath, 'create_bezier_path') # Publishers - self.linear_path_pub = rospy.Publisher('linear_path', - Path, queue_size=1, latch=True) - self.bezier_path_pub = rospy.Publisher('bezier_path', - BezierPathList, queue_size=1, latch=True) + self.linear_path_pub = node.create_publisher(Path, 'linear_path', 1) + self.bezier_path_pub = node.create_publisher(BezierPathList, 'bezier_path', 1) self.car_odom = Odometry() self.ball_odom = Odometry() + node.declare_parameter('/field/length', 1.0) + self.goal_pos = (node.get_parameter('/field/length').get_parameter_value().double_value / 2.0, 0.) - self.goal_pos = (rospy.get_param('/field/length', 1) / 2, 0.) - - rospy.spin() + rclpy.spin(node) def car_odom_cb(self, data: Odometry): self.car_odom = data @@ -137,7 +138,7 @@ def car_odom_cb(self, data: Odometry): def ball_odom_cb(self, data: Odometry): self.ball_odom = data - def reset(self, _: EmptyRequest): + def reset(self, _: Empty_Request): req = self.path_req(self.car_odom, self.ball_odom, self.goal_pos) res = self.path_client(req) if self.linear_path_pub: @@ -146,7 +147,10 @@ def reset(self, _: EmptyRequest): bezier_path_list = BezierPathList() bezier_path_list.paths = res.bezier_paths self.bezier_path_pub.publish(bezier_path_list) - return EmptyResponse() + return Empty_Response() -if __name__ == '__main__': +def main(): PathPlanner() + +if __name__ == '__main__': + main() diff --git a/rktl_planner/nodes/patrol_planner b/src/rktl_planner/rktl_planner/nodes/patrol_planner.py similarity index 71% rename from rktl_planner/nodes/patrol_planner rename to src/rktl_planner/rktl_planner/nodes/patrol_planner.py index 10c08a2a1..8386fa771 100755 --- a/rktl_planner/nodes/patrol_planner +++ b/src/rktl_planner/rktl_planner/nodes/patrol_planner.py @@ -6,52 +6,75 @@ All rights reserved. """ -import rospy -from tf.transformations import euler_from_quaternion +import rclpy +from rktl_dependencies.transformations import euler_from_quaternion from nav_msgs.msg import Odometry from rktl_msgs.msg import ControlCommand import math, time -from angles import shortest_angular_distance as sad +from rktl_dependencies.angles import shortest_angular_distance as sad class PatrolPlanner(object): """A very simple strategy for rktl. Patrol around the field in a circle and make a beeline at the ball if it seems like it would result in a goal.""" def __init__(self): - rospy.init_node('path_follower') + rclpy.init() + global node + node = rclpy.create_node('path_follower') + node.declare_parameter('/field/width') + node.declare_parameter('/field/height') + node.declare_parameter('/cars/steering/max_throw') + node.declare_parameter('/cars/length', 0.12) + node.declare_parameter('speed') + node.declare_parameter('curvature_gain/kp') + node.declare_parameter('curvature_gain/kd') + node.declare_parameter('curvature_gain/falloff') + node.declare_parameter('patrol_wall_dist') + node.declare_parameter('wall_avoidance/reverse_time') + node.declare_parameter('wall_avoidance/distance_margin') + node.declare_parameter('wall_avoidance/heading_margin') + node.declare_parameter('attempt_timeout') + node.declare_parameter('defensive_line') + node.declare_parameter('reverse_line') + node.declare_parameter('scoring/heading_margin') + node.declare_parameter('scoring/car_lookahead_dist') + node.declare_parameter('scoring/ball_lookahead_time') + node.declare_parameter('scoring/goal_depth_target') + node.declare_parameter('defense/reverse_time_gain') # physical constants (global) - self.FIELD_WIDTH = rospy.get_param('/field/width') - self.FIELD_HEIGHT = rospy.get_param('/field/length') - self.MAX_CURVATURE = math.tan(rospy.get_param('/cars/steering/max_throw')) / rospy.get_param('/cars/length') + self.FIELD_WIDTH = node.get_parameter('/field/width').get_parameter_value().double_value + self.FIELD_HEIGHT = node.get_parameter('/field/height').get_parameter_value().double_value + self.MAX_CURVATURE = math.tan(node.get_parameter('/cars/steering/max_throw').get_parameter_value().double_value) / node.get_parameter('/cars/length').get_parameter_value().double_value # constants # general stability parameters - self.SPEED = rospy.get_param('~speed', 1.0) - self.CURVATURE_P_GAIN = rospy.get_param('~curvature_gain/kp', 1.0) - self.CURVATURE_D_GAIN = rospy.get_param('~curvature_gain/kd', 0.0) - self.CURVATURE_GAIN_FALLOFF_SQ = pow(rospy.get_param('~curvature_gain/falloff', 1e-9), 2) - self.PATROL_DIST = rospy.get_param('~patrol_wall_dist', 0.5) + self.SPEED = node.get_parameter('speed').get_parameter_value().double_value + self.CURVATURE_P_GAIN = node.get_parameter('curvature_gain/kp').get_parameter_value().double_value + self.CURVATURE_D_GAIN = node.get_parameter('curvature_gain/kd').get_parameter_value().double_value + self.CURVATURE_GAIN_FALLOFF_SQ = pow(node.get_parameter('curvature_gain/falloff').get_parameter_value().double_value, 2) + self.PATROL_DIST = node.get_parameter('patrol_wall_dist').get_parameter_value().double_value # wall avoidance parameters - self.WALL_REVERSE_TIME = rospy.get_param('~wall_avoidance/reverse_time', 0.25) - self.WALL_DIST_MARGIN = rospy.get_param('~wall_avoidance/distance_margin', 0.5) - self.WALL_HEADING_MARGIN = rospy.get_param('~wall_avoidance/heading_margin', math.pi/4) + self.WALL_REVERSE_TIME = node.get_parameter('wall_avoidance/reverse_time').get_parameter_value().double_value + self.WALL_DIST_MARGIN = node.get_parameter('wall_avoidance/distance_margin').get_parameter_value().double_value + self.WALL_HEADING_MARGIN = node.get_parameter('wall_avoidance/heading_margin').get_parameter_value().double_value # offense & defense parameters - self.ATTEMPT_TIMEOUT = rospy.get_param('~attempt_timeout', 5.0) - self.DEFENSIVE_LINE = rospy.get_param('~defensive_line', 0.0) - self.REVERSE_LINE = rospy.get_param('~reverse_line', 0.0) + self.ATTEMPT_TIMEOUT = node.get_parameter('attempt_timeout').get_parameter_value().double_value + self.DEFENSIVE_LINE = node.get_parameter('defensive_line').get_parameter_value().double_value + self.REVERSE_LINE = node.get_parameter('reverse_line').get_parameter_value().double_value # offensive related parameters - self.SCORING_MARGIN = rospy.get_param('~scoring/heading_margin', math.pi/8.0) - self.LOOKAHEAD_DIST = rospy.get_param('~scoring/car_lookahead_dist', 1.0) - self.LOOKAHEAD_TIME = rospy.get_param('~scoring/ball_lookahead_time', 0.0) - self.GOAL_DEPTH_TARGET = rospy.get_param('~scoring/goal_depth_target', 0.0) + + self.SCORING_MARGIN = node.get_parameter('scoring/heading_margin').get_parameter_value().double_value + self.LOOKAHEAD_DIST = node.get_parameter('scoring/car_lookahead_dist').get_parameter_value().double_value + self.LOOKAHEAD_TIME = node.get_parameter('scoring/ball_lookahead_time').get_parameter_value().double_value + self.GOAL_DEPTH_TARGET = node.get_parameter('scoring/goal_depth_target').get_parameter_value().double_value # defense related parameters - self.DEFENSE_TIME_GAIN = rospy.get_param('~defense/reverse_time_gain', 0.5) + self.DEFENSE_TIME_GAIN = node.get_parameter('defense/reverse_time_gain').get_parameter_value().double_value # variables self.ball_position = None @@ -60,13 +83,13 @@ def __init__(self): self.prev_error = 0.0 # Publishers - self.cmd_pub = rospy.Publisher('command', ControlCommand, queue_size=1) + self.cmd_pub = node.create_publisher(ControlCommand, 'command', 1) # Subscribers - rospy.Subscriber('odom', Odometry, self.car_odom_cb) - rospy.Subscriber('/ball/odom', Odometry, self.ball_odom_cb) + node.create_subscription(Odometry, 'odom', self.car_odom_cb, 1) + node.create_subscription(Odometry, '/ball/odom', self.ball_odom_cb, 1) - rospy.spin() + rclpy.spin(node) def ball_odom_cb(self, odom_msg): """Callback for ball odometry.""" @@ -98,7 +121,7 @@ def car_odom_cb(self, odom_msg): # check that ball position is known if self.ball_position is None or self.ball_velocity is None: self.cmd_pub.publish(cmd_msg) - rospy.logwarn("ball position unknown") + node.get_logger().warn("ball position unknown") return # calculate cardinal distances / errors @@ -138,9 +161,9 @@ def car_odom_cb(self, odom_msg): ball_goal_heading = math.atan2(gy - by, gx - bx) # check to see if an existing attempt has timed out - if self.attempt_start_time is not None and (rospy.Time.now() - self.attempt_start_time).to_sec() > self.ATTEMPT_TIMEOUT: + if self.attempt_start_time is not None and (node.get_clock().now() - self.attempt_start_time).to_sec() > self.ATTEMPT_TIMEOUT: self.attempt_start_time = None - rospy.loginfo("attempt timeout") + node.get_logger().info("attempt timeout") # check if we should be offensive or defensive if bx > self.DEFENSIVE_LINE: # offensive @@ -163,9 +186,9 @@ def car_odom_cb(self, odom_msg): # mark attempt start if self.attempt_start_time is None: - self.attempt_start_time = rospy.Time.now() + self.attempt_start_time = node.get_clock().now() self.prev_error = 0.0 - rospy.loginfo("beginning scoring attempt") + node.get_logger().info("beginning scoring attempt") return else: # defensive @@ -200,9 +223,9 @@ def car_odom_cb(self, odom_msg): self.cmd_pub.publish(cmd_msg) # mark attempt start - self.attempt_start_time = rospy.Time.now() + self.attempt_start_time = node.get_clock().now() self.prev_error = 0.0 - rospy.loginfo("beginning defensive attempt") + node.get_logger().info("beginning defensive attempt") # reverse for longer, if the ball is nearer to the middle time.sleep((self.FIELD_WIDTH/2.0 - abs(by)) * self.DEFENSE_TIME_GAIN) @@ -283,5 +306,8 @@ def control_curvature(self, error): self.prev_error = error return curvature -if __name__ == "__main__": +def main(): PatrolPlanner() + +if __name__ == "__main__": + main() diff --git a/rktl_planner/src/rktl_planner/pure_pursuit.py b/src/rktl_planner/rktl_planner/pure_pursuit.py similarity index 97% rename from rktl_planner/src/rktl_planner/pure_pursuit.py rename to src/rktl_planner/rktl_planner/pure_pursuit.py index 07bfd4a82..8270886df 100755 --- a/rktl_planner/src/rktl_planner/pure_pursuit.py +++ b/src/rktl_planner/rktl_planner/pure_pursuit.py @@ -8,7 +8,7 @@ # 3rd party modules import numpy as np import math -from tf.transformations import euler_from_quaternion +from rktl_dependencies.transformations import euler_from_quaternion def find_intersection(path_seg, bot_path, lookahead_dist): diff --git a/src/rktl_planner/setup.cfg b/src/rktl_planner/setup.cfg new file mode 100644 index 000000000..2d1e4c1ef --- /dev/null +++ b/src/rktl_planner/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rktl_planner +[install] +install_scripts=$base/lib/rktl_planner diff --git a/src/rktl_planner/setup.py b/src/rktl_planner/setup.py new file mode 100644 index 000000000..d3cfdbae4 --- /dev/null +++ b/src/rktl_planner/setup.py @@ -0,0 +1,35 @@ +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = 'rktl_planner' + +setup( + name=package_name, + version='1.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))), + (os.path.join('lib', package_name), glob(os.path.join(package_name, '*.py'))), + (os.path.join('lib', package_name, 'nodes'), glob(os.path.join(package_name, 'nodes', '*.py'))), + (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.yaml'))), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Autonomous Robotics Club of Purdue', + maintainer_email='autonomy@purdue.edu', + description='Handles localized actions to plan and follow trajectories.', + license='BSD 3 Clause', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'bezier_path_server = rktl_planner.nodes.bezier_path_server:main', + 'path_follower = rktl_planner.nodes.path_follower:main', + 'path_planner = rktl_planner.nodes.path_planner:main', + 'patrol_planner = rktl_planner.nodes.patrol_planner:main' + ], + }, +) diff --git a/src/rktl_planner/test/test_copyright.py b/src/rktl_planner/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/src/rktl_planner/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/rktl_planner/test/test_flake8.py b/src/rktl_planner/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/src/rktl_planner/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/rktl_planner/test/test_pep257.py b/src/rktl_planner/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/src/rktl_planner/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/rktl_sim/README.md b/src/rktl_sim/README.md similarity index 85% rename from rktl_sim/README.md rename to src/rktl_sim/README.md index 12b1c51ca..b673a2748 100644 --- a/rktl_sim/README.md +++ b/src/rktl_sim/README.md @@ -3,4 +3,4 @@ This package provides a tools for simulating a physical environment for testing and training, as well as a visualizer for viewing the state of the environment. Test cases are also included and can be performed by running -`catkin test rktl_sim` inside of Docker. +`catkin test rktl_sim` inside of Docker. \ No newline at end of file diff --git a/src/rktl_sim/config/simulation.yaml b/src/rktl_sim/config/simulation.yaml new file mode 100644 index 000000000..b15c34922 --- /dev/null +++ b/src/rktl_sim/config/simulation.yaml @@ -0,0 +1,105 @@ +/**: + ros__parameters: + rate: 10 + spawn_height: 0.06 + + car: + # init_pose: + # pos: [0.5, 0.0, 0.06] + sensor_noise: + pos: [0.01, 0.01, 0.00] + orient: [0.0, 0.0, 0.09] + dropout: 0.15 + + ball: + init_speed: 0.0 + init_pose: + pos: [0.0, 0.5, 0.06] + orient: [0.0, 0.0, 0.0] + sensor_noise: + pos: [0.01, 0.01, 0.0] + orient: [0.0, 0.0, 0.0] + dropout: 0.1 + + # + # Simulation settings + # + + rate: 10 + spawn_height: 0.06 + + # Directly export to setPhysicsEngineParameters() + engine: + fixedTimeStep: 0.001 + restitutionVelocityThreshold: 0.0001 + + # Directly exported to changeDynamics() + dynamics: + ball: + mass: 0.2 + lateralFriction: 0.4 + restitution: 0.7 + rollingFriction: 0.0001 + spinningFriction: 0.001 + contactDamping: 0.0 + contactStiffness: 0.0 + collisionMargin: 0.0 + car: + mass: 0.0 + lateralFriction: 0.0 + restitution: 0.0 + rollingFriction: 0.0 + spinningFriction: 0.0 + contactDamping: 0.0 + contactStiffness: 0.0 + collisionMargin: 0.0 + walls: + mass: 0.0 + lateralFriction: 0.0 + restitution: 0.0 + rollingFriction: 0.0 + spinningFriction: 0.0 + contactDamping: 0.0 + contactStiffness: 0.0 + collisionMargin: 0.0 + floor: + mass: 0.0 + lateralFriction: 0.0 + restitution: 0.0 + rollingFriction: 0.0 + spinningFriction: 0.0 + contactDamping: 0.0 + contactStiffness: 0.0 + collisionMargin: 0.0 + + # + # Object instances + # + + sensor_noise: + car: + pos: [0.01, 0.01, 0.00] + orient: [0.0, 0.0, 0.09] + dropout: 0.0 + ball: + pos: [0.01, 0.01, 0.0] + orient: [0.0, 0.0, 0.0] + dropout: 0.0 + + cars: + - name: "car0" + # init_pose: + # pos: [0.5, 0.0, 0.06] + + # - name: "car1" + # # init_pose: + # # pos: [0.5, 0.0, 0.06] + # sensor_noise: + # pos: [0.01, 0.01, 0.00] + # orient: [0.0, 0.0, 0.09] + + ball: + init_speed: 0.0 + # init_pose: + # pos: [0.0, 0.5, 0.06] + # orient: [0.0, 0.0, 0.0] \ No newline at end of file diff --git a/src/rktl_sim/config/visualization.yaml b/src/rktl_sim/config/visualization.yaml new file mode 100644 index 000000000..4decdf385 --- /dev/null +++ b/src/rktl_sim/config/visualization.yaml @@ -0,0 +1,6 @@ +/**: + ros__parameters: + rate: 10 + cars: + body_width: 0.0762 # (3 in) + body_length: 0.2032 # (8 in) diff --git a/rktl_sim/launch/README.md b/src/rktl_sim/launch/README.md similarity index 100% rename from rktl_sim/launch/README.md rename to src/rktl_sim/launch/README.md diff --git a/rktl_sim/launch/simulator.launch b/src/rktl_sim/launch/simulator.launch similarity index 100% rename from rktl_sim/launch/simulator.launch rename to src/rktl_sim/launch/simulator.launch diff --git a/src/rktl_sim/launch/simulator.launch.py b/src/rktl_sim/launch/simulator.launch.py new file mode 100644 index 000000000..dab7a3bba --- /dev/null +++ b/src/rktl_sim/launch/simulator.launch.py @@ -0,0 +1,59 @@ +import os +import sys + +import launch +import launch_ros.actions +from ament_index_python.packages import get_package_share_directory + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch.actions.DeclareLaunchArgument( + name='pybullet_render', + default_value='false' + ), + launch.actions.DeclareLaunchArgument( + name='sim_mode', + default_value='realistic' # Either realistic or ideal (check docs) + ), + launch_ros.actions.Node( + package='rktl_sim', + executable='simulator', + name='simulator', + output='screen', + on_exit=launch.actions.Shutdown(), + parameters=[ + { + 'mode': launch.substitutions.LaunchConfiguration('sim_mode') + }, + { + 'render': launch.substitutions.LaunchConfiguration('pybullet_render') + }, + { + 'urdf/ball': get_package_share_directory('rktl_sim') + '/urdf/ball.urdf' + }, + { + 'urdf/car': get_package_share_directory('rktl_sim') + '/urdf/car.urdf' + }, + { + 'urdf/goal': get_package_share_directory('rktl_sim') + '/urdf/goal.urdf' + }, + { + 'urdf/sidewall': get_package_share_directory('rktl_sim') + '/urdf/sidewall.urdf' + }, + { + 'urdf/backwall': get_package_share_directory('rktl_sim') + '/urdf/backwall.urdf' + }, + { + 'urdf/plane': get_package_share_directory('rktl_sim') + '/urdf/plane.urdf' + }, + get_package_share_directory( + 'rktl_sim') + '/config/simulation.yaml' + ] + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() diff --git a/src/rktl_sim/launch/test_simulator.launch.py b/src/rktl_sim/launch/test_simulator.launch.py new file mode 100644 index 000000000..48d6edd08 --- /dev/null +++ b/src/rktl_sim/launch/test_simulator.launch.py @@ -0,0 +1,95 @@ +import os +import sys + +import launch +import launch_ros.actions +from launch import LaunchDescription +from launch_ros.actions import Node +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration + +from ament_index_python.packages import get_package_share_directory + +def generate_launch_description(): + + pybullet_render_launch_arg = DeclareLaunchArgument( + name='pybullet_render', + default_value='false' + ) + + sim_mode_launch_arg = DeclareLaunchArgument( + name='sim_mode', + default_value='realistic' + ) + + simulation_config = os.path.join( + get_package_share_directory('rktl_sim'), + 'config', + 'simulation.yaml' + ) + + urdf_ball_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'urdf', + 'ball.urdf' + ) + + urdf_car_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'urdf', + 'car.urdf' + ) + + urdf_goal_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'urdf', + 'goal.urdf' + ) + + urdf_sidewall_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'urdf', + 'sidewall.urdf' + ) + + urdf_backwall_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'urdf', + 'backwall.urdf' + ) + + urdf_plane_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'urdf', + 'plane.urdf' + ) + + simulator_node = Node( + package='rktl_sim', + executable='simulator', + name='simulator', + parameters=[ + simulation_config, + { + 'mode' : LaunchConfiguration('sim_mode'), + 'render' : LaunchConfiguration('pybullet_render'), + 'urdf/ball' : urdf_ball_param, + 'urdf/car' : urdf_car_param, + 'urdf/goal' : urdf_goal_param, + 'urdf/sidewall' : urdf_sidewall_param, + 'urdf/backwall' : urdf_backwall_param, + 'urdf/plane' : urdf_plane_param + } + ] + + ) + + + return LaunchDescription([ + pybullet_render_launch_arg, + sim_mode_launch_arg, + simulator_node + ]) + +if __name__ == '__main__': + generate_launch_description() diff --git a/src/rktl_sim/launch/test_visualizer.launch.py b/src/rktl_sim/launch/test_visualizer.launch.py new file mode 100644 index 000000000..cdcb09c46 --- /dev/null +++ b/src/rktl_sim/launch/test_visualizer.launch.py @@ -0,0 +1,75 @@ +import os +import sys + +import launch +import launch_ros.actions +from launch import LaunchDescription +from launch_ros.actions import Node +from launch.actions import DeclareLaunchArgument +from launch.substitutions import LaunchConfiguration + +from ament_index_python.packages import get_package_share_directory + + +def generate_launch_description(): + + global_config = os.path.join( + get_package_share_directory('rktl_launch'), + 'config', + 'global_params.yaml' + ) + + visualizer_config = os.path.join( + get_package_share_directory('rktl_sim'), + 'config', + 'vizualization.yaml' + ) + + media_ball_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'media', + 'ball.png' + ) + + media_car_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'media', + 'car.png' + ) + + media_goal_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'media', + 'goal.png' + ) + + media_field_param = os.path.join( + get_package_share_directory('rktl_sim'), + 'media', + 'field.png' + ) + + visualizer_node = Node( + package='rktl_sim', + executable='visualizer', + namespace='test', + name='visualizer', + output='screen', + parameters=[ + global_config, + visualizer_config, + { + 'media/ball' : media_ball_param, + 'media/car' : media_car_param, + 'media/goal' : media_goal_param, + 'media/field' : media_field_param + } + ] + ) + + return LaunchDescription([ + visualizer_node + ]) + +if __name__ == '__main__': + generate_launch_description() diff --git a/rktl_sim/launch/visualizer.launch b/src/rktl_sim/launch/visualizer.launch similarity index 100% rename from rktl_sim/launch/visualizer.launch rename to src/rktl_sim/launch/visualizer.launch diff --git a/src/rktl_sim/launch/visualizer.launch.py b/src/rktl_sim/launch/visualizer.launch.py new file mode 100644 index 000000000..ab953410c --- /dev/null +++ b/src/rktl_sim/launch/visualizer.launch.py @@ -0,0 +1,38 @@ +import os +import sys + +import launch +import launch_ros.actions +from ament_index_python.packages import get_package_share_directory + + +def generate_launch_description(): + ld = launch.LaunchDescription([ + launch_ros.actions.Node( + package='rktl_sim', + executable='visualizer', + name='visualizer', + output='screen', + parameters=[ + { + 'media/ball': get_package_share_directory('rktl_sim') + '/media/ball.png' + }, + { + 'media/car': get_package_share_directory('rktl_sim') + '/media/car.png' + }, + { + 'media/goal': get_package_share_directory('rktl_sim') + '/media/goal.png' + }, + { + 'media/field': get_package_share_directory('rktl_sim') + '/media/field.jpg' + }, + get_package_share_directory( + 'rktl_sim') + '/config/visualization.yaml' + ] + ) + ]) + return ld + + +if __name__ == '__main__': + generate_launch_description() diff --git a/rktl_sim/media/ball.png b/src/rktl_sim/media/ball.png similarity index 100% rename from rktl_sim/media/ball.png rename to src/rktl_sim/media/ball.png diff --git a/rktl_sim/media/car.png b/src/rktl_sim/media/car.png similarity index 100% rename from rktl_sim/media/car.png rename to src/rktl_sim/media/car.png diff --git a/rktl_sim/media/field.jpg b/src/rktl_sim/media/field.jpg similarity index 100% rename from rktl_sim/media/field.jpg rename to src/rktl_sim/media/field.jpg diff --git a/rktl_sim/media/goal.png b/src/rktl_sim/media/goal.png similarity index 100% rename from rktl_sim/media/goal.png rename to src/rktl_sim/media/goal.png diff --git a/rktl_sim/nodes/README.md b/src/rktl_sim/nodes/README.md similarity index 100% rename from rktl_sim/nodes/README.md rename to src/rktl_sim/nodes/README.md diff --git a/rktl_sim/package.xml b/src/rktl_sim/package.xml similarity index 64% rename from rktl_sim/package.xml rename to src/rktl_sim/package.xml index a7b203579..08baea41d 100644 --- a/rktl_sim/package.xml +++ b/src/rktl_sim/package.xml @@ -1,5 +1,6 @@ - + + rktl_sim 1.0.0 Simulator and Visualizer for ARC Rocket League project @@ -10,13 +11,19 @@ Dhiro Sarkar Jake Tockerman - catkin - rosunit - rospy + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest pygame geometry_msgs nav_msgs std_srvs PyBullet rktl_msgs + ros2launch + + + ament_python + diff --git a/src/rktl_sim/resource/rktl_sim b/src/rktl_sim/resource/rktl_sim new file mode 100644 index 000000000..e69de29bb diff --git a/src/rktl_sim/rktl_sim/__init__.py b/src/rktl_sim/rktl_sim/__init__.py new file mode 100644 index 000000000..2e7fdf3a9 --- /dev/null +++ b/src/rktl_sim/rktl_sim/__init__.py @@ -0,0 +1,23 @@ +""" +License: + BSD 3-Clause License + Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + All rights reserved. +""" + +from sim_simulator import Simulator +from vis_visualizer import VisualizerROS as Vizualizer +# from .cartpole_interface import CartpoleInterface +# from .cartpole_direct_interface import CartpoleDirectInterface +# from .snake_interface import SnakeInterface +#from .rocket_league_interface import RocketLeagueInterface +# from .env_counter import EnvCounter + +__all__ = [ + "Simulator", + # "CartpoleInterface", + # "CartpoleDirectInterface", + # "SnakeInterface", + "Visualizer" + # "EnvCounter" +] \ No newline at end of file diff --git a/rktl_sim/src/visualizer/asset.py b/src/rktl_sim/rktl_sim/asset.py similarity index 100% rename from rktl_sim/src/visualizer/asset.py rename to src/rktl_sim/rktl_sim/asset.py diff --git a/src/rktl_sim/rktl_sim/nodes/README.md b/src/rktl_sim/rktl_sim/nodes/README.md new file mode 100644 index 000000000..f2c7358c3 --- /dev/null +++ b/src/rktl_sim/rktl_sim/nodes/README.md @@ -0,0 +1,151 @@ +# ROS Nodes + +These are the [ROS Nodes](http://wiki.ros.org/Nodes) provided by this package. +Most likely, you will use a launch file to run these. You can also run them +by using `rosrun`: + +```bash +rosrun rktl_sim +``` + +```{contents} ROS Nodes in the package +:depth: 2 +:backlinks: top +:local: true +``` + +--- + +## simulator + +This node wraps around a physics simulator and allows controlling a car +in a simulated environment. The ROS parameter server is used to configure the +simulator, and publishes sensor data and control commands as ROS messages. +The simulator is implemented in `simulator.py` in the `src` directory. It can be +run in `'realistic'` or `'ideal`' mode by changing the `~mode` parameter. If the +mode is `'ideal'`, the simulator will use perfect sensor readings and ignore +any control effort messages that it receives. If the mode is `'realistic'`, +the simulator will add noise to sensor readings and simulate the effects of +control effort on the car's motion. Many parameters are set in +`rktl_launch/config.global_params.yaml` or `rktl_sim/config/simulator.yaml`. + +### Subscribed Topics + +- `/cars/car0/effort` ([rktl_msgs/ControlEffort](/rktl_msgs/html/msg/ControlEffort.html#http://)): + The car's desired movement, in terms of throttle and steering amount. Only + subscribed to if the `~mode` parameter is set to `'realistic'`. +- `/cars/car0/command` ([rktl_msgs/ControlCommand](/rktl_msgs/html/msg/ControlCommand.html#http://)): + The car's desired movement, in terms of velocity and steering angle. Only + subscribed to if the `~mode` parameter is set to `'ideal'`. + +### Published Topics + +- `/ball/pose_sync_early` ([geometry_msgs/PoseWithCovarianceStamped](/geometry_msgs/html/msg/PoseWithCovarianceStamped.html#http://)): + Pose of the ball with a timestamp and added noise. Only published if the + `~mode` parameter is set to `'realistic'`. +- `/ball/odom_truth` ([nav_msgs/Odometry](/nav_msgs/html/msg/Odometry.html#http://)): + Odometry of the ball with no added noise. Only published if the `~mode` + parameter is set to `'realistic'`. +- `/ball/odom` ([nav_msgs/Odometry](/nav_msgs/html/msg/Odometry.html#http://)): + Odometry of the ball with no added noise. Only published if the `~mode` + parameter is set to `'ideal'`. +- `/cars/car0/pose_sync_early` ([geometry_msgs/PoseWithCovarianceStamped](/geometry_msgs/html/msg/PoseWithCovarianceStamped.html#http://)): + Pose of the car with a timestamp and added noise. Only published if the + `~mode` parameter is set to `'realistic'`. +- `/cars/car0/odom_truth` ([nav_msgs/Odometry](/nav_msgs/html/msg/Odometry.html#http://)): + Odometry of the car with no added noise. Only published if the `~mode` + parameter is set to `'realistic'`. +- `/cars/car0/odom` ([nav_msgs/Odometry](/nav_msgs/html/msg/Odometry.html#http://)): + Odometry of the car with no added noise. Only published if the `~mode` + parameter is set to `'ideal'`. +- `match_status` ([rktl_msgs/MatchStatus](/rktl_msgs/html/msg/MatchStatus.html#http://)): + Status of the match, whether the match is ongoing or if a goal has been + scored, ending the match (and which goal it was scored on). + +### Parameters + +- `~mode` (string): Mode of the simulator (either '`ideal'` or `'realistic'`). +- `~render` (bool, default: False): Whether or not to enable rendering of the + simulation. +- `~rate` (int, default: 30): Rate at which to run the simulation. +- `~frame_id` (string, default: 'map'): Frame ID to use for published ROS + messages. +- `~timeout` (int, default: 10): Timeout in seconds for certain simulator + functions. +- `/field/width` (float): Width of the playing field, in meters. +- `/field/length` (float): Length of the playing field, in meters. +- `/field/wall_thickness` (float): Thickness of the walls around the playing + field, in meters. +- `/field/goal/width` (float): Width of the goals, in meters. +- `~spawn_height` (float, default: 0.06): Height at which to spawn the ball and + car. +- `~urdf` (dict): Dictionary of URDF file paths for each robot. Can be set by + setting `urdf/*` to whatever is needed. +- `~ball/init_pose` (list): List of 3 floats representing the initial position + of the ball. +- `~ball/init_speed` (list): List of 3 floats representing the initial speed of + the ball. +- `~ball/sensor_noise` (dict): Dictionary of noise parameters for the ball's + sensors. +- `/cars/length` (float): Length of the car, front to rear wheel center to + center, in meters. +- `/cars/throttle/max_speed` (float): Maximum speed of the car, in meters per + second. +- `/cars/throttle/tau` (float): The throttle time constant, in seconds. +- `/cars/steering/max_throw` (float): The maximum steering throw, in radians. +- `/cars/steering/rate` (float): The maxmimum steering rate, in radians per + second. +- `~car/init_pose` (list): List of 3 floats representing the initial position + of the car. +- `~car/sensor_noise` (dict): Dictionary of noise parameters for the car's + sensors. + +--- + +## visualizer + +This node provides a visualizer that uses Pygame to show the position of a car +and a ball on a playing field. The script subscribes to topics that provide +information about the position of the car and the ball, and updates their +position in the visualizer accordingly. Additionally, the script can display a +planned path for the car using a linear path or a bezier curve. Many parameters +are set in `rktl_launch/config.global_params.yaml` or +`rktl_sim/config/visualizer.yaml`. + +### Subscribed Topics + +- `/ball/odom` ([nav_msgs/Odometry](/nav_msgs/html/msg/Odometry.html#http://)): + The ball's odometry information containing its position, orientation, and velocities. +- `/cars/car0/odom` ([nav_msgs/Odometry](/nav_msgs/html/msg/Odometry.html#http://)): + The car's odometry information containing its position, orientation, and velocities. +- `/cars/car0/path` ([rktl_msgs/Path](/rktl_msgs/html/msg/Path.html#http://)): + Used for visualizing the path the car should follow as a series of waypoints. +- `/cars/car0/lookahead_pnt` ([std_msgs/Float32](/std_msgs/html/msg/Float32.html#http://)): + Used for visualizing the path the lookahead point for a pure pursuit algorithm. +- `/agents/agent0/bezier_path` ([rktl_msgs/BezierPathList](/rktl_msgs/html/msg/BezierPathList.html#http://)): + Used for visualizing the path the car should follow as a bezier curve. + +### Parameters + +- `/field/width` (float): Width of the playing field, in meters. +- `/field/length` (float): Length of the playing field, in meters. +- `/field/wall_thickness` (float): Thickness of the walls around the playing + field, in meters. +- `/field/goal/width` (float): Width of the goals, in meters. +- `/ball/radius` (float): Radius of the ball, in meters. +- `window_name` (string, default: 'Rocket League Visualizer'): Name of the + window +- `~frame_id` (string, default: 'map'): Frame ID to use for published ROS + messages. +- `~timeout` (int, default: 10): Timeout in seconds for certain simulator + functions. +- `~rate` (int, default: 30): Rate at which to run the simulation. +- `~cars/body_width` (float): Rendered width of the car in the visualizer, in + meters. +- `~cars/body_length` (float): Rendered length of the car in the visualizer, + in meters. +- `~media/field` (string): Path to the image representing the field. +- `~media/car` (string): Path to the image representing the car. +- `~media/ball` (string): Path to the image representing the ball. + +--- diff --git a/rktl_sim/nodes/simulator b/src/rktl_sim/rktl_sim/nodes/simulator similarity index 67% rename from rktl_sim/nodes/simulator rename to src/rktl_sim/rktl_sim/nodes/simulator index 3a206a363..cac5f39df 100755 --- a/rktl_sim/nodes/simulator +++ b/src/rktl_sim/rktl_sim/nodes/simulator @@ -7,17 +7,23 @@ License: """ # 3rd party modules +import rclpy #import node import random +import sys from geometry_msgs.msg import PoseWithCovarianceStamped from nav_msgs.msg import Odometry import os -import rospy -from std_srvs.srv import Empty, EmptyResponse +import time + +#import rospy + +from std_srvs.srv import Empty, Empty_Response from threading import Lock from enum import Enum + # local libraries -import simulator +from rktl_sim.nodes import simulator from rktl_msgs.msg import MatchStatus, ControlCommand, ControlEffort @@ -55,117 +61,87 @@ class Simulator(object): self.car_properties = None # setting config parameters (stay constant for the whole simulator run) - rospy.init_node('simulator') + # rospy.init_node('simulator') + rclpy.init(args=sys.argv) + global node + node = rclpy.create_node("simulator") + + mode = self.get_sim_param('~mode') if mode == 'ideal': self.mode = SimulatorMode.IDEAL elif mode == 'realistic': self.mode = SimulatorMode.REALISTIC else: - rospy.signal_shutdown('unknown sim mode set "{}"'.format(mode)) + #rospy.signal_shutdown('unknown sim mode set "{}"'.format(mode)) + self.destroy_node('unknown sim mode set "{}"'.format(mode)) + self.cmd_lock = Lock() self.reset_lock = Lock() self.car_cmd = (0.0, 0.0) render_enabled = self.get_sim_param('~render', secondParam=False) - rate = rospy.Rate(self.get_sim_param('~rate', secondParam=30)) - self.frame_id = self.get_sim_param('~frame_id', secondParam='map') - self.timeout = self.get_sim_param('~timeout', secondParam=10) - - # setup urdf file paths: a universal way to describe kinematics and dynamics of robots - self.urdf_paths = self.get_sim_param('~urdf') - for path in self.urdf_paths.values(): - self.check_urdf(path) - - self.props = { - 'engine': self.get_sim_param('~engine', secondParam=None), - 'dynamics': self.get_sim_param('~dynamics', secondParam=None), - } - - # prep the simulator for a new run, setting all instance parameters for the sim - - self.spawn_bounds, self.field_setup = self.configure_field() - self.car_ids = {} - self.car_pose_pubs = {} - self.car_odom_pubs = {} - self.car_effort_subs = {} - self.car_cmd_subs = {} - # TODO: find a better way to not have duplicate code segment - self.car_properties = {'length': self.get_sim_param('/cars/length'), - 'max_speed': self.get_sim_param("/cars/throttle/max_speed"), - 'steering_throw': self.get_sim_param("/cars/steering/max_throw"), - 'throttle_tau': self.get_sim_param("/cars/throttle/tau"), - 'steering_rate': self.get_sim_param("/cars/steering/rate"), - 'simulate_effort': (self.mode == SimulatorMode.REALISTIC)} - - self.sim = simulator.Sim( - self.props, self.urdf_paths, self.spawn_bounds, self.field_setup, render_enabled) - - self.sim.create_ball('ball', init_pose=self.ball_init_pose, - init_speed=self.ball_init_speed, noise=self.ball_noise) - - self.update_all_cars() - - self.cmd_lock = Lock() - self.reset_lock = Lock() - self.last_time = None - - self.reset_cb(None) # janky reset call with mandatory none parameter - - # Publishers - self.status_pub = rospy.Publisher( - 'match_status', MatchStatus, queue_size=1) + #self.status_pub = rospy.Publisher('match_status', MatchStatus, queue_size=1) + self.status_pub = node.create_publisher(MatchStatus, 'match_status', 1) self.ball_pose_pub, self.ball_odom_pub = None, None if self.mode == SimulatorMode.REALISTIC: - self.ball_pose_pub = rospy.Publisher('/ball/pose_sync_early', - PoseWithCovarianceStamped, queue_size=1) - self.ball_odom_pub = rospy.Publisher('/ball/odom_truth', - Odometry, queue_size=1) + #self.ball_pose_pub = rospy.Publisher('/ball/pose_sync_early', PoseWithCovarianceStamped, queue_size=1) + self.ball_pose_pub = node.create_publisher(PoseWithCovarianceStamped, '/ball/pose_sync_early', 1) + #self.ball_odom_pub = rospy.Publisher('/ball/odom_truth', Odometry, queue_size=1) + self.ball_odom_pub = node.create_publisher(Odometry, '/ball/odom_truth', 1) elif self.mode == SimulatorMode.IDEAL: - self.ball_odom_pub = rospy.Publisher('/ball/odom', - Odometry, queue_size=1) + #self.ball_odom_pub = rospy.Publisher('/ball/odom', Odometry, queue_size=1) + self.ball_odom_pub = node.create_publisher(Odometry, '/ball/odom', 1) # Services - rospy.Service('sim_reset_car_and_ball', Empty, self.reset_cb) - rospy.Service('sim_reset_ball', Empty, self.reset_ball_cb) + # rospy.Service('sim_reset_car_and_ball', Empty, self.reset_cb) + # rospy.Service('sim_reset_ball', Empty, self.reset_ball_cb) + node.create_service(Empty, 'sim_reset_car_and_ball', self.reset_cb) + node.create_service(Empty, 'sim_reset_ball', self.reset_ball_cb) + - while not rospy.is_shutdown(): + while not rclpy.ok(): self.loop_once() try: rate.sleep() - except rospy.ROSInterruptException: + except rclpy.ROSInterruptException: pass + def check_urdf(self, urdf_path): """Validates that a URDF exists, then returns its file.""" if urdf_path is None: - rospy.signal_shutdown( - 'no urdf path set for "{}"'.format(urdf_path)) - + #rospy.signal_shutdown('no urdf path set for "{}"'.format(urdf_path)) + self.destroy_node('no urdf path set for "{}"'.format(urdf_path)) + try: + raise Exception('no urdf path set for "{}"'.format(urdf_path)) + except Exception as e: + node.get_logger().error(str(e)) + rclpy.shutdown() i = 0 while not os.path.isfile(urdf_path) and i < 5: - rospy.sleep(0.1) # wait for xacro build + #rospy.sleep(0.1) + time.sleep(0.1) # wait for xacro build i += 1 if not os.path.isfile(urdf_path): - rospy.signal_shutdown( - 'no urdf file exists at path {}'.format(urdf_path)) + #rospy.signal_shutdown('no urdf file exists at path {}'.format(urdf_path)) + self.destroy_node('no urdf file exists at path {}'.format(urdf_path)) + def effort_cb(self, effort_msg, car_id): """Sets a car's effort with throttle and steering (overwrites commands).""" self.cmd_lock.acquire() - self.sim.set_car_command(car_id, - (effort_msg.throttle, effort_msg.steering)) + self.sim.set_car_command(car_id, (effort_msg.throttle, effort_msg.steering)) self.cmd_lock.release() def cmd_cb(self, cmd_msg, car_id): """Sets a car's command with the velocity and curvature. (overwrites effort).""" self.cmd_lock.acquire() - self.sim.set_car_command(car_id, - (cmd_msg.velocity, cmd_msg.curvature)) + self.sim.set_car_command(car_id, (cmd_msg.velocity, cmd_msg.curvature)) self.cmd_lock.release() def reset_cb(self, _): @@ -195,7 +171,7 @@ class Simulator(object): self.last_time = None self.reset_lock.release() - return EmptyResponse() + return Empty_Response() def reset_ball_cb(self, _): """Resets the ball sensor noise and pose WITHOUT resetting the whole sim.""" @@ -207,13 +183,15 @@ class Simulator(object): self.ball_init_speed = self.get_sim_param('/ball/init_speed') self.sim.reset_ball() - return EmptyResponse() + return Empty_Response() def loop_once(self): """Step the simulation once step, updating match status, moving and publishing new car and ball positions.""" self.reset_lock.acquire() + + #now = rospy.Time.now() + now = self.get_clock().now() - now = rospy.Time.now() if self.last_time is not None and self.last_time != now: delta_t = (now - self.last_time).to_sec() self.sim.step(self.car_cmd, delta_t) @@ -305,77 +283,89 @@ class Simulator(object): self.last_time = now self.reset_lock.release() # def get_sim_dict(self, path, returnValue=False, secondParam=None): - # rospy_param = rospy.get_param(path, secondParam) - # if not rospy_param: + # rclpy_param = rclpy.get_param(path, secondParam) + # if not rclpy_param: # if returnValue: - # rospy.logfatal(f'invalid file path: {path}') + # rclpy.logfatal(f'invalid file path: {path}') # return None # else: - # for param in rospy_param: + # for param in rclpy_param: def get_sim_param(self, path, returnValue=False, secondParam=None): """ - @param secondParam: Specify if you want to pass in a second parameter to rospy. + @param secondParam: Specify if you want to pass in a second parameter to rclpy. @param path: A direct path to the variable. @param returnValue: True: None is returned if variable does not exist. False: An error is thrown if variable does not exist. @return: None or Exception. """ - rospy_param = rospy.get_param(path, secondParam) - if not rospy_param: + #rospy_param = rospy.get_param(path, secondParam) + rclpy_param = self.node.declare_parameter(path, secondParam).value + if not rclpy_param: if returnValue: - rospy.logfatal(f'invalid file path: {path}') + #rospy.logfatal(f'invalid file path: {path}') + + self.node.get_logger().fatal(f'invalid file path: {path}') return None else: if '~' in path: if secondParam is not None: - - return rospy.get_param(f'{path}', secondParam) + #return rospy.get_param(f'{path}', secondParam) + return self.node.declare_parameter(f'{path}', secondParam) else: - return rospy.get_param(f'{path}') + #return rospy.get_param(f'{path}') + return self.node.declare_parameter(f'{path}') - type_rospy = type(rospy_param) + type_rospy = type(rclpy_param) - if type_rospy == dict: + + if type_rclpy == dict: if secondParam is None: + + # min_param = (float)(rospy.get_param(f'{path}/min')) + # max_param = (float)(rospy.get_param(f'{path}/max')) - min_param = (float)(rospy.get_param(f'{path}/min')) - max_param = (float)(rospy.get_param(f'{path}/max')) + min_param = (float)(self.node.declare_parameter(f'{path}/min')) + max_param = (float)(self.node.declare_parameter(f'{path}/max')) else: - min_param = (float)(rospy.get_param( - f'{path}/min', secondParam)) - max_param = (float)(rospy.get_param( - f'{path}/max', secondParam)) + # min_param = (float)(rospy.get_param( + # f'{path}/min', secondParam)) + # max_param = (float)(rospy.get_param( + # f'{path}/max', secondParam)) + + min_param = (float)(self.node.declare_parameter(f'{path}/min', secondParam)) + max_param = (float)(self.node.declare_parameter(f'{path}/max', secondParam)) + if not max_param: if returnValue: - rospy.logfatal(f'invalid file path: {path}/max') - return None - if not min_param: - if returnValue: - rospy.logfatal(f'invalid file path: {path}/min') + #rospy.logfatal(f'invalid file path: {path}/min') + + self.node.get_logger().fatal(f'invalid file path: {path}/min') return None # accounting for bugs in yaml file if min_param > max_param: param_val =(float)(random.uniform(max_param, min_param)) - rospy.set_param(f'{path}',param_val) + #rospy.set_param(f'{path}',param_val) + self.node.declare_parameter(f'{path}',param_val) - return param_val - else: - param_val =(float)(random.uniform(min_param, max_param)) - rospy.set_param(f'{path}',param_val) + return param_val - elif type_rospy == float or type_rospy == int: + elif type_rclpy == float or type_rclpy == int: if secondParam is not None: - return rospy.get_param(path, secondParam) + #return rospy.get_param(path, secondParam) + return self.node.declare_parameter(path, secondParam) else: - return rospy.get_param(path) + #return rospy.get_param(path) + return self.node.declare_parameter(path) if returnValue: - rospy.logfatal(f'invalid file path: {path}') + #rospy.logfatal(f'invalid file path: {path}') + + self.node.get_logger().fatal(f'invalid file path: {path}') return None def configure_field(self): @@ -414,7 +404,9 @@ class Simulator(object): init_pose = self.get_sim_param('~cars/init_pose') if 'name' not in car_config: - rospy.signal_shutdown('no "name" set for car config in sim') + #rospy.signal_shutdown('no "name" set for car config in sim') + self.destroy_node('no "name" set for car config in sim') + car_name = car_config['name'] if 'randomize_pose' in car_config: init_pose = None @@ -428,22 +420,39 @@ class Simulator(object): 'car', init_pose=init_pose, noise=self.car_noise, car_props=self.car_properties) car_id = self.car_ids[car_name] # create the car's Subscribers - self.car_effort_subs[car_name] = rospy.Subscriber( - f'/cars/{car_name}/effort', ControlEffort, + # self.car_effort_subs[car_name] = rospy.Subscriber( + # f'/cars/{car_name}/effort', ControlEffort, + # self.effort_cb, callback_args=car_id) + self.car_effort_subs[car_name] = node.create_subscription( + ControlEffort, f'/cars/{car_name}/effort', self.effort_cb, callback_args=car_id) - self.car_cmd_subs[car_name] = rospy.Subscriber( - f'/cars/{car_name}/command', ControlCommand, + # self.car_cmd_subs[car_name] = rospy.Subscriber( + # f'/cars/{car_name}/command', ControlCommand, + # self.cmd_cb, callback_args=car_id) + self.car_cmd_subs[car_name] = node.create_subscription( + ControlCommand, f'/cars/{car_name}/command', + self.cmd_cb, callback_args=car_id) + # create the car's Publishers if self.mode == SimulatorMode.REALISTIC: - self.car_pose_pubs[car_name] = rospy.Publisher( - f'/cars/{car_name}/pose_sync_early', - PoseWithCovarianceStamped, queue_size=1) - self.car_odom_pubs[car_name] = rospy.Publisher( - f'/cars/{car_name}/odom_truth', Odometry, queue_size=1) + # self.car_pose_pubs[car_name] = rospy.Publisher( + # f'/cars/{car_name}/pose_sync_early', + # PoseWithCovarianceStamped, queue_size=1) + self.car_pose_pubs[car_name] = node.create_publisher( + PoseWithCovarianceStamped, f'/cars/{car_name}/pose_sync_early', + 1) + + # self.car_odom_pubs[car_name] = rospy.Publisher( + # f'/cars/{car_name}/odom_truth', Odometry, queue_size=1) + self.car_pose_pubs[car_name] = node.create_publisher( + Odometry, f'/cars/{car_name}/odom_truth', 1) + elif self.mode == SimulatorMode.IDEAL: - self.car_odom_pubs[car_name] = rospy.Publisher( - f'/cars/{car_name}/odom', Odometry, queue_size=1) + # self.car_odom_pubs[car_name] = rospy.Publisher( + # f'/cars/{car_name}/odom', Odometry, queue_size=1) + self.car_odom_pubs[car_name] = node.create_publisher( + Odometry, f'/cars/{car_name}/odom', 1) return True diff --git a/src/rktl_sim/rktl_sim/nodes/visualizer b/src/rktl_sim/rktl_sim/nodes/visualizer new file mode 100755 index 000000000..594163a14 --- /dev/null +++ b/src/rktl_sim/rktl_sim/nodes/visualizer @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +"""Node to run the visualizer with ROS bindings. +License: + BSD 3-Clause License + Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + All rights reserved. + +TODO: +- Scale to support multiple cars +""" + +# 3rd party modules +import math +from std_msgs.msg import Float32 +from nav_msgs.msg import Odometry +import rclpy +import sys + +from rktl_dependencies.transformations import euler_from_quaternion + +from threading import Lock +import numpy as np + + +# Local library +from rktl_sim.nodes import visualizer +from rktl_msgs.msg import Path, BezierPathList +from rktl_planner import BezierPath + + +class VisualizerROS(object): + """ROS wrapper for the visualizer.""" + + def __init__(self): + #rospy.init_node("visualizer") + rclpy.init(args=sys.argv) + global node + node = rclpy.create_node("visualizer") + + # Collecting global parameters + # field_width = node.get_param('/field/width') + # field_length = rospy.get_param('/field/length') + # goal_width = rospy.get_param('/field/goal/width') + # wall_thickness = rospy.get_param('/field/wall_thickness') + # ball_radius = rospy.get_param("/ball/radius") + field_width = node.declare_parameter('/field/width').value + field_length = node.declare_parameter('/field/length').value + goal_width = node.declare_parameter('/field/goal/width').value + wall_thickness = node.declare_parameter('/field/wall_thickness').value + ball_radius = node.declare_parameter("/ball/radius").value + + + + # Creating pygame render + + self.window = visualizer.Window( + field_width, field_length, wall_thickness, + # rospy.get_param('~window_name', 'Rocket League Visualizer')) + node.declare_parameter('~window_name', 'Rocket League Visualizer').value) + + # Collecting private parameters + # self.frame_id = rospy.get_param("~frame_id", "map") + # self.timeout = rospy.get_param("~timeout", 10) + # rate = rospy.Rate(rospy.get_param("~rate", 20)) + # car_width = rospy.get_param("~cars/body_width") + # car_length = rospy.get_param("~cars/body_length") + self.frame_id = node.declare_parameter("~frame_id", "map").value + self.timeout = node.declare_parameter("~timeout", 10).value + rate = rclpy.create_node('simple_node').create_rate((node.declare_parameter("~rate", 20).value)) + car_width = node.declare_parameter("~cars/body_width").value + car_length = node.declare_parameter("~cars/body_length").value + + # Setting up field + id = 0 + # field_img_path = rospy.get_param("~media/field", None) + field_img_path = node.declare_parameter("~media/field", None).value + + if field_img_path is not None: + self.window.createAsset( + id, field_width, field_length, imgPath=field_img_path, + initPos=(0, 0)) + id += 1 + + # Setting up sidewalls + sidewall_length = field_length + (wall_thickness * 2.0) + self.window.createAsset( + id, wall_thickness, sidewall_length, color=(0, 0, 0), + initPos=(0., (field_width + wall_thickness) / 2.)) + id += 1 + + self.window.createAsset( + id, wall_thickness, sidewall_length, color=(0, 0, 0), + initPos=(0., -(field_width + wall_thickness) / 2.)) + id += 1 + + # Setting up backwalls + backwall_width = (field_width - goal_width) / 2. + backwall_x = (field_length + wall_thickness) / 2. + backwall_y = (field_width / 2.) - (backwall_width / 2.) + self.window.createAsset( + id, backwall_width, wall_thickness, color=(0, 0, 0), + initPos=(backwall_x, backwall_y)) + id += 1 + + self.window.createAsset( + id, backwall_width, wall_thickness, color=(0, 0, 0), + initPos=(backwall_x, -backwall_y)) + id += 1 + + self.window.createAsset( + id, backwall_width, wall_thickness, color=(0, 0, 0), + initPos=(-backwall_x, backwall_y)) + id += 1 + + self.window.createAsset( + id, backwall_width, wall_thickness, color=(0, 0, 0), + initPos=(-backwall_x, -backwall_y)) + id += 1 + + # Setting up car + # car_img_path = rospy.get_param("~media/car", None) + car_img_path = node.declare_parameter("~media/car", None).value + + + if car_img_path is not None: + self.car_id = id + self.window.createAsset( + self.car_id, car_width, car_length, imgPath=car_img_path) + id += 1 + + # Setting up ball + # ball_img_path = rospy.get_param("~media/ball", None) + ball_img_path = node.declare_parameter("~media/ball", None).value + + if ball_img_path is not None: + self.ball_id = id + self.window.createAsset( + self.ball_id, ball_radius * 2, ball_radius * 2, imgPath=ball_img_path) + id += 1 + + # Setting up goals + self.goal1_id = id + self.window.createAsset( + self.goal1_id, goal_width, wall_thickness, color=(255, 255, 255)) + self.window.updateAssetPos( + self.goal1_id, (field_length / 2) + (wall_thickness / 2), 0) + id += 1 + + self.goal2_id = id + self.window.createAsset( + self.goal2_id, goal_width, wall_thickness, color=(255, 255, 255)) + self.window.updateAssetPos( + self.goal2_id, -((field_length / 2) + (wall_thickness / 2)), 0) + id += 1 + + self.lines_id = 11 + self.window.createAsset(self.lines_id, 0, 0, color=(255, 0, 0), lines=True) + + self.circle_id = 12 + self.window.createAsset(self.circle_id, 0, 0, circle=True, color=(255,0,0), radius=0) + + self.lock = Lock() + self.last_time = None + self.path = None + + # Subscribers + # rospy.Subscriber("/ball/odom", Odometry, self.ball_odom_cb) + # rospy.Subscriber("/cars/car0/odom", Odometry, self.car_odom_cb) + # rospy.Subscriber("/cars/car0/path", Path, self.path_arr_cb) + # rospy.Subscriber("/cars/car0/lookahead_pnt", Float32, self.lookahead_cb) + # rospy.Subscriber("/agents/agent0/bezier_path", BezierPathList, self.bezier_path_cb) + + node.create_subscription(Odometry, "/ball/odom", self.ball_odom_cb) + node.create_subscription(Odometry, "/cars/car0/odom", self.car_odom_cb) + node.create_subscription(Path, "/cars/car0/path", self.path_arr_cb) + node.create_subscription(Float32, "/cars/car0/lookahead_pnt", self.lookahead_cb) + node.create_subscription(BezierPathList, "/agents/agent0/bezier_path", self.bezier_path_cb) + + + while not rclpy.ok(): + try: + self.window.show() + except self.window.ShutdownError: + exit() + try: + rate.sleep() + except rclpy.ROSInterruptException: + pass + + + def car_odom_cb(self, odom_msg): + x = odom_msg.pose.pose.position.x + y = odom_msg.pose.pose.position.y + orient = odom_msg.pose.pose.orientation + quat = [orient.x, orient.y, orient.z, orient.w] + heading = euler_from_quaternion(quat)[2] + heading = heading * 180. / math.pi + self.window.updateAssetPos(self.car_id, x, y) + self.window.updateAssetAngle(self.car_id, heading) + self.window.updateAssetPos(self.circle_id, x, y) + + def ball_odom_cb(self, odom_msg): + x = odom_msg.pose.pose.position.x + y = odom_msg.pose.pose.position.y + self.window.updateAssetPos(self.ball_id, x, y) + + def path_arr_cb(self, path_msg: Path): + """Callback for path array messages.""" + self.window.resetAssetLines(self.lines_id) + self.path = path_msg.waypoint + for point in self.path: + x = point.pose.position.x + y = point.pose.position.y + self.window.updateAssetPos(self.lines_id, x, y) + + def bezier_path_cb(self, msg: BezierPathList): + """Callback for bezier path messages.""" + self.window.resetAssetLines(self.lines_id) + paths = [BezierPath(x) for x in msg.paths] + for path in paths: + #for point in path.bezier_curve.control_points: + # self.window.updateAssetPos(self.lines_id, point.x, point.y) + sec = path.duration.to_sec() + for t in np.linspace(0., sec, int(50 * sec + 0.5)): + point = path.at(t) + self.window.updateAssetPos(self.lines_id, point.x, point.y) + + def lookahead_cb(self, msg: Float32): + """Callback for lookahead_pnt messages.""" + self.window.updateAssetRadius(self.circle_id, msg.data) + +if __name__ == "__main__": + VisualizerROS() diff --git a/src/rktl_sim/rktl_sim/sim_simulator.py b/src/rktl_sim/rktl_sim/sim_simulator.py new file mode 100755 index 000000000..60ae17711 --- /dev/null +++ b/src/rktl_sim/rktl_sim/sim_simulator.py @@ -0,0 +1,516 @@ +#!/usr/bin/env python3 +"""Node to run the simulator with ROS bindings. +License: + BSD 3-Clause License + Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + All rights reserved. +""" + +# 3rd party modules +import rclpy #import node +import random +import sys +from geometry_msgs.msg import PoseWithCovarianceStamped +from nav_msgs.msg import Odometry +import os +import time + +#import rospy + +from std_srvs.srv import Empty, Empty_Response # might not need Empty_Response +from threading import Lock +from enum import Enum + + +# local libraries +from rktl_msgs.msg import MatchStatus, ControlCommand, ControlEffort + + +class SimulatorMode(Enum): + IDEAL = 1 # no sensor noise, publish car and ball odom & pose early + REALISTIC = 2 # sensor noise for pose & orient of ball and car, publish with a delay + + +class Simulator(object): + """Serves as an interface between params and services to the Sim class. + Handles simulator configuration parameters: + sim rate(default=30), frame id(default=map), timeout(default=10) + sensor noise, urdf paths, car list, car properties + + Two types of parameters: configuration-based and instance-based. + Configuration-based parameters determine the kinematics and dynamics of the sim. + A few examples: car size, sim timestep, friction, etc. + Instance-based parameters determine the initial state of a new run. + A few examples: car pose, ball pose, etc. + On startup, we load both configuration and instance parameters and create an initial setup. + + Simulator controls Configuration-based parameters. + Sim.py handles instance-based parameters. + """ + + def __init__(self): + """Initialize the simulator environment, field and objects properties, and car and ball objects.""" + self.props = None + self.spawn_bounds = None + self.sensor_noise = None + self.ball_noise = None + self.car_noise = None + self.ball_init_pose = None + self.ball_init_speed = None + self.car_properties = None + + # setting config parameters (stay constant for the whole simulator run) + # rospy.init_node('simulator') + rclpy.init(args=sys.argv) + #global node + self.node = rclpy.create_node("simulator") + + self.node.declare_parameter('mode', 'ideal') + self.node.declare_parameter('sensor_noise', None) + self.node.declare_parameter('/cars/length', 0.12) + self.node.declare_parameter('/cars/throttle/max_speed', 2.3) + self.node.declare_parameter('/cars/steering/max_throw', 0.1826) + self.node.declare_parameter('/cars/throttle/tau', 0.2) + self.node.declare_parameter('/cars/steering/rate', 0.9128) + #Sensor Noise Parameters + self.node.declare_parameter('/sensor_noise/car/pos', [0.01, 0.01, 0.00]) + self.node.declare_parameter('/sensor_noise/car/orient', [0.0, 0.0, 0.09]) + self.node.declare_parameter('/sensor_noise/car/dropout', 0.15) + self.node.declare_parameter('/sensor_noise/ball/pos', [0.01, 0.01, 0.0]) + self.node.declare_parameter('/sensor_noise/ball/orient', [0.0, 0.0, 0.0]) + self.node.declare_parameter('/sensor_noise/ball/dropout', 0.1) + #Ball Initial Position Parameters + self.node.declare_parameter('ball/init_pose/pos', [0.0, 0.5, 0.06]) + self.node.declare_parameter('ball/init_pose/orient', [0.0, 0.0, 0.0]) + + + #mode = self.get_sim_param('~mode') + mode = self.node.get_parameter('mode').get_parameter_value().string_value + if mode == 'ideal': + self.mode = SimulatorMode.IDEAL + elif mode == 'realistic': + self.mode = SimulatorMode.REALISTIC + else: + #rospy.signal_shutdown('unknown sim mode set "{}"'.format(mode)) + self.node.destroy_node()#f'unknown sim mode set "{mode}"') + return + + + + self.cmd_lock = Lock() + self.reset_lock = Lock() + self.car_cmd = (0.0, 0.0) + + + #render_enabled = self.get_sim_param('~render', secondParam=False) + #self.status_pub = rospy.Publisher('match_status', MatchStatus, queue_size=1) + self.status_pub = self.node.create_publisher(MatchStatus, 'match_status', 1) + self.ball_pose_pub, self.ball_odom_pub = None, None + if self.mode == SimulatorMode.REALISTIC: + #self.ball_pose_pub = rospy.Publisher('/ball/pose_sync_early', PoseWithCovarianceStamped, queue_size=1) + self.ball_pose_pub = self.node.create_publisher(PoseWithCovarianceStamped, '/ball/pose_sync_early', 1) + #self.ball_odom_pub = rospy.Publisher('/ball/odom_truth', Odometry, queue_size=1) + self.ball_odom_pub = self.node.create_publisher(Odometry, '/ball/odom_truth', 1) + elif self.mode == SimulatorMode.IDEAL: + #self.ball_odom_pub = rospy.Publisher('/ball/odom', Odometry, queue_size=1) + self.ball_odom_pub = self.node.create_publisher(Odometry, '/ball/odom', 1) + + # Services + # rospy.Service('sim_reset_car_and_ball', Empty, self.reset_cb) + # rospy.Service('sim_reset_ball', Empty, self.reset_ball_cb) + self.node.create_service(Empty, 'sim_reset_car_and_ball', self.reset_cb) + self.node.create_service(Empty, 'sim_reset_ball', self.reset_ball_cb) + + + while not rclpy.ok(): + self.loop_once() + try: + rate.sleep() + except rclpy.ROSInterruptException: + pass + + + + def check_urdf(self, urdf_path): + """Validates that a URDF exists, then returns its file.""" + + if urdf_path is None: + #rospy.signal_shutdown('no urdf path set for "{}"'.format(urdf_path)) + self.node.destroy_node() + try: + raise Exception('no urdf path set for "{}"'.format(urdf_path)) + except Exception as e: + self.node.get_logger().error(str(e)) + rclpy.shutdown() + i = 0 + while not os.path.isfile(urdf_path) and i < 5: + #rospy.sleep(0.1) + time.sleep(0.1) # wait for xacro build + i += 1 + + if not os.path.isfile(urdf_path): + #rospy.signal_shutdown('no urdf file exists at path {}'.format(urdf_path)) + self.node.destroy_node() + + + def effort_cb(self, effort_msg, car_id): + """Sets a car's effort with throttle and steering (overwrites commands).""" + self.cmd_lock.acquire() + + self.sim.set_car_command(car_id, (effort_msg.throttle, effort_msg.steering)) + self.cmd_lock.release() + + def cmd_cb(self, cmd_msg, car_id): + """Sets a car's command with the velocity and curvature. (overwrites effort).""" + self.cmd_lock.acquire() + self.sim.set_car_command(car_id, (cmd_msg.velocity, cmd_msg.curvature)) + self.cmd_lock.release() + + def reset_cb(self, _): + + self.reset_lock.acquire() + + # setting sim parameters (can be modified by the user) + self.spawn_bounds, self.field_setup = self.configure_field() + + #self.sensor_noise = self.get_sim_param('~sensor_noise', secondParam=None) + self.sensor_noise = { + 'car' : { + 'pos' : self.node.get_parameter('/sensor_noise/car/pos').get_parameter_value().double_array_value, + 'orient': self.node.get_parameter('/sensor_noise/car/orient').get_parameter_value().double_array_value, + 'dropout' : self.node.get_parameter('/sensor_noise/car/dropout').get_parameter_value().double_value + }, + 'ball' : { + 'pos' : self.node.get_parameter('/sensor_noise/ball/pos').get_parameter_value().double_array_value, + 'orient' : self.node.get_parameter('/sensor_noise/ball/orient').get_parameter_value().double_array_value, + 'dropout' : self.node.get_parameter('/sensor_noise/ball/dropout').get_parameter_value().double_value + } + } + #self.sensor_noise = self.node.get_parameter('sensor_noise').get_parameter_value().double_array_value + + self.car_noise = None + if self.sensor_noise is not None and self.mode == SimulatorMode.REALISTIC: + self.car_noise = self.sensor_noise.get('car', None) + + self.reset_ball_cb(None) + + '''self.car_properties = {'length': self.get_sim_param('/cars/length'), + 'max_speed': self.get_sim_param("/cars/throttle/max_speed"), + 'steering_throw': self.get_sim_param("/cars/steering/max_throw"), + 'throttle_tau': self.get_sim_param("/cars/throttle/tau"), + 'steering_rate': self.get_sim_param("/cars/steering/rate"), + 'simulate_effort': (self.mode == SimulatorMode.REALISTIC)}''' + self.car_properties = { + 'length' : self.node.get_parameter('/cars/length').get_parameter_value().double_value, + 'max_speed' : self.node.get_parameter('/cars/throttle/max_speed').get_parameter_value().double_value, + 'steering_throw' : self.node.get_parameter('/cars/steering/max_throw').get_parameter_value().double_value, + 'throttle_tau' : self.node.get_parameter('/cars/throttle/tau').get_parameter_value().double_value, + 'steering_rate' : self.node.get_parameter('/cars/steering/rate').get_parameter_value().double_value, + 'simulate_effort' : (self.mode == SimulatorMode.REALISTIC) + } + self.sim.reset(self.spawn_bounds, self.car_properties, + self.ball_init_pose, self.ball_init_speed) + + self.last_time = None + self.reset_lock.release() + return Empty_Response() # might be Empty() + + def reset_ball_cb(self, _): + """Resets the ball sensor noise and pose WITHOUT resetting the whole sim.""" + self.ball_noise = None + if self.sensor_noise is not None and self.mode == SimulatorMode.REALISTIC: + self.ball_noise = self.sensor_noise.get('ball', None) + + #self.ball_init_pose = self.get_sim_param('~ball/init_pose') + self.ball_init_pose = { + 'pos' : self.node.get_parameter('ball/init_pose/pos').get_parameter_value().double_array_value, + 'orient': self.node.get_parameter('ball/init_pose/orient').get_parameter_value().double_array_value + } + #self.ball_init_speed = self.get_sim_param('/ball/init_speed') + self.ball_init_speed = self.node.get_parameter('/ball/init_speed').get_parameter_value().double_value + + self.sim.reset_ball() + return Empty_Response() # might be Empty() + + def loop_once(self): + """Step the simulation once step, updating match status, moving and publishing new car and ball positions.""" + self.reset_lock.acquire() + + #now = rospy.Time.now() + now = self.get_clock().now() + + if self.last_time is not None and self.last_time != now: + delta_t = (now - self.last_time).to_sec() + self.sim.step(self.car_cmd, delta_t) + + # publish game status + status = MatchStatus() + if self.sim.scored: + if self.sim.winner == "A": + status.status = MatchStatus.VICTORY_TEAM_A + elif self.sim.winner == "B": + status.status = MatchStatus.VICTORY_TEAM_B + else: + status.status = MatchStatus.ONGOING + self.status_pub.publish(status) + + # publish pose and odom data + if self.mode == SimulatorMode.REALISTIC: + ball_msg = PoseWithCovarianceStamped() + ball_msg.header.stamp = now + ball_msg.header.frame_id = self.frame_id + ball_pos, ball_quat = self.sim.get_ball_pose(add_noise=True) + ball_msg.pose.pose.position.x = ball_pos[0] + ball_msg.pose.pose.position.y = ball_pos[1] + ball_msg.pose.pose.position.z = ball_pos[2] + ball_msg.pose.pose.orientation.x = ball_quat[0] + ball_msg.pose.pose.orientation.y = ball_quat[1] + ball_msg.pose.pose.orientation.z = ball_quat[2] + ball_msg.pose.pose.orientation.w = ball_quat[3] + self.ball_pose_pub.publish(ball_msg) + + for car_name in self.car_ids: + + car_msg = PoseWithCovarianceStamped() + car_msg.header.stamp = now + car_msg.header.frame_id = self.frame_id + car_pos, car_quat = self.sim.get_car_pose( + self.car_ids[car_name], add_noise=True) + car_msg.pose.pose.position.x = car_pos[0] + car_msg.pose.pose.position.y = car_pos[1] + car_msg.pose.pose.position.z = car_pos[2] + car_msg.pose.pose.orientation.x = car_quat[0] + car_msg.pose.pose.orientation.y = car_quat[1] + car_msg.pose.pose.orientation.z = car_quat[2] + car_msg.pose.pose.orientation.w = car_quat[3] + self.car_pose_pubs[car_name].publish(car_msg) + ball_msg = Odometry() + ball_msg.header.stamp = now + ball_msg.header.frame_id = self.frame_id + ball_pos, ball_quat = self.sim.get_ball_pose() + ball_msg.pose.pose.position.x = ball_pos[0] + ball_msg.pose.pose.position.y = ball_pos[1] + ball_msg.pose.pose.position.z = ball_pos[2] + ball_msg.pose.pose.orientation.x = ball_quat[0] + ball_msg.pose.pose.orientation.y = ball_quat[1] + ball_msg.pose.pose.orientation.z = ball_quat[2] + ball_msg.pose.pose.orientation.w = ball_quat[3] + ball_linear, ball_angular = self.sim.get_ball_velocity() + ball_msg.twist.twist.linear.x = ball_linear[0] + ball_msg.twist.twist.linear.y = ball_linear[1] + ball_msg.twist.twist.linear.z = ball_linear[2] + ball_msg.twist.twist.angular.x = ball_angular[0] + ball_msg.twist.twist.angular.y = ball_angular[1] + ball_msg.twist.twist.angular.z = ball_angular[2] + self.ball_odom_pub.publish(ball_msg) + + for car_name in self.car_ids: + car_msg = Odometry() + car_msg.header.stamp = now + car_msg.header.frame_id = self.frame_id + car_pos, car_quat = self.sim.get_car_pose( + self.car_ids[car_name]) + car_msg.pose.pose.position.x = car_pos[0] + car_msg.pose.pose.position.y = car_pos[1] + car_msg.pose.pose.position.z = car_pos[2] + car_msg.pose.pose.orientation.x = car_quat[0] + car_msg.pose.pose.orientation.y = car_quat[1] + car_msg.pose.pose.orientation.z = car_quat[2] + car_msg.pose.pose.orientation.w = car_quat[3] + car_linear, car_angular = self.sim.get_car_velocity( + self.car_ids[car_name]) + car_msg.twist.twist.linear.x = car_linear[0] + car_msg.twist.twist.linear.y = car_linear[1] + car_msg.twist.twist.linear.z = car_linear[2] + car_msg.twist.twist.angular.x = car_angular[0] + car_msg.twist.twist.angular.y = car_angular[1] + car_msg.twist.twist.angular.z = car_angular[2] + self.car_odom_pubs[car_name].publish(car_msg) + + self.last_time = now + self.reset_lock.release() + # def get_sim_dict(self, path, returnValue=False, secondParam=None): + # rclpy_param = rclpy.get_param(path, secondParam) + # if not rclpy_param: + # if returnValue: + # rclpy.logfatal(f'invalid file path: {path}') + # return None + # else: + # for param in rclpy_param: + + + # def get_sim_param(self, path, returnValue=False, secondParam=None): + # """ + # @param secondParam: Specify if you want to pass in a second parameter to rclpy. + # @param path: A direct path to the variable. + # @param returnValue: + # True: None is returned if variable does not exist. + # False: An error is thrown if variable does not exist. + # @return: None or Exception. + # """ + # #rospy_param = rospy.get_param(path, secondParam) + # rclpy_param = self.node.declare_parameter(path, secondParam).value + # if not rclpy_param: + # if returnValue: + # #rospy.logfatal(f'invalid file path: {path}') + + # self.node.get_logger().fatal(f'invalid file path: {path}') + # return None + # else: + # if '~' in path: + # if secondParam is not None: + # #return rospy.get_param(f'{path}', secondParam) + # return self.node.declare_parameter(f'{path}', secondParam) + # else: + + # #return rospy.get_param(f'{path}') + # return self.node.declare_parameter(f'{path}') + + # type_rclpy = type(rclpy_param) + + + # if type_rclpy == dict: + # if secondParam is None: + + # # min_param = (float)(rospy.get_param(f'{path}/min')) + # # max_param = (float)(rospy.get_param(f'{path}/max')) + + # min_param = (float)(self.node.declare_parameter(f'{path}/min')) + # max_param = (float)(self.node.declare_parameter(f'{path}/max')) + # else: + # # min_param = (float)(rospy.get_param( + # # f'{path}/min', secondParam)) + # # max_param = (float)(rospy.get_param( + # # f'{path}/max', secondParam)) + + + # min_param = (float)(self.node.declare_parameter(f'{path}/min', secondParam)) + # max_param = (float)(self.node.declare_parameter(f'{path}/max', secondParam)) + + # if not max_param: + # if returnValue: + # #rospy.logfatal(f'invalid file path: {path}/min') + + # self.node.get_logger().fatal(f'invalid file path: {path}/min') + # return None + # # accounting for bugs in yaml file + # if min_param > max_param: + # param_val =(float)(random.uniform(max_param, min_param)) + # #rospy.set_param(f'{path}',param_val) + # self.node.declare_parameter(f'{path}',param_val) + + + # return param_val + + # elif type_rclpy == float or type_rclpy == int: + # if secondParam is not None: + # #return rospy.get_param(path, secondParam) + # return self.node.declare_parameter(path, secondParam) + # else: + # #return rospy.get_param(path) + # return self.node.declare_parameter(path) + # if returnValue: + # #rospy.logfatal(f'invalid file path: {path}') + + # self.node.get_logger().fatal(f'invalid file path: {path}') + # return None + + def configure_field(self): + """Configures the field boundries and goals to be used in the simulator.""" + #fw = self.get_sim_param('/field/width') + fw = self.node.get_parameter('/field/width').get_parameter_value().double_value + #fl = self.get_sim_param('/field/length') + fl = self.node.get_parameter('/field/length').get_parameter_value().double_value + #wt = self.get_sim_param('/field/wall_thickness') + wt = self.node.get_parameter('/field/wall_thickness').get_parameter_value().double_value + #gw = self.get_sim_param('/field/goal/width') + gw = self.node.get_parameter('/field/goal/width').get_parameter_value().double_value + #spawn_height = self.get_sim_param('~spawn_height', 0.06) + spawn_height = self.node.get_parameter('spawn_height').get_parameter_value().double_value + + spawn_bounds = [[-(fl / 2) + (2 * wt), (fl / 2) - (2 * wt)], + [-(fw / 2) + (2 * wt), (fw / 2) - (2 * wt)], + [spawn_height, spawn_height]] + # Setting up field + field_setup = {} + field_setup["goalA"] = [(fl/2.) + (wt/2.), 0, spawn_height] + field_setup["goalB"] = [(-fl/2.) - (wt/2.), 0, spawn_height] + field_setup["rsidewall"] = [0, (fw/2) + (wt/2.), spawn_height] + field_setup["lsidewall"] = [0, (-fw/2) - (wt/2.), spawn_height] + + bww = (fw-gw)/2 + offset = (gw+bww)/2 + field_setup["flbackwall"] = [(fl/2) + (wt/2.), -offset, spawn_height] + field_setup["frbackwall"] = [(fl/2) + (wt/2.), offset, spawn_height] + field_setup["blbackwall"] = [(-fl/2) - (wt/2.), -offset, spawn_height] + field_setup["brbackwall"] = [(-fl/2) - (wt/2.), offset, spawn_height] + + return spawn_bounds, field_setup + + def update_all_cars(self): + """Generates instance-parameters, Subscribers, Publishers for each car.""" + #car_configs = self.get_sim_param('~cars', secondParam=[]) + car_configs = self.node.get_parameter('cars').get_parameter_value().string_value #Find value type + + + for car_config in car_configs: + init_pose = self.get_sim_param('~cars/init_pose') + + if 'name' not in car_config: + #rospy.signal_shutdown('no "name" set for car config in sim') + self.node.destroy_node() + + car_name = car_config['name'] + if 'randomize_pose' in car_config: + init_pose = None + + # means the car is already there and we only need to reset it + if car_name not in self.car_ids: + # the car does not exist so we will create it + # otherwise, we are only reseting the car's parameters which will happen in the sim.reset call + + self.car_ids[car_name] = self.sim.create_car( + 'car', init_pose=init_pose, noise=self.car_noise, car_props=self.car_properties) + car_id = self.car_ids[car_name] + # create the car's Subscribers + # self.car_effort_subs[car_name] = rospy.Subscriber( + # f'/cars/{car_name}/effort', ControlEffort, + # self.effort_cb, callback_args=car_id) + self.car_effort_subs[car_name] = self.node.create_subscription( + ControlEffort, f'/cars/{car_name}/effort', + self.effort_cb, callback_args=car_id) + # self.car_cmd_subs[car_name] = rospy.Subscriber( + # f'/cars/{car_name}/command', ControlCommand, + # self.cmd_cb, callback_args=car_id) + self.car_cmd_subs[car_name] = self.node.create_subscription( + ControlCommand, f'/cars/{car_name}/command', + + self.cmd_cb, callback_args=car_id) + + # create the car's Publishers + if self.mode == SimulatorMode.REALISTIC: + # self.car_pose_pubs[car_name] = rospy.Publisher( + # f'/cars/{car_name}/pose_sync_early', + # PoseWithCovarianceStamped, queue_size=1) + self.car_pose_pubs[car_name] = self.node.create_publisher( + PoseWithCovarianceStamped, f'/cars/{car_name}/pose_sync_early', + 1) + + # self.car_odom_pubs[car_name] = rospy.Publisher( + # f'/cars/{car_name}/odom_truth', Odometry, queue_size=1) + self.car_pose_pubs[car_name] = self.node.create_publisher( + Odometry, f'/cars/{car_name}/odom_truth', 1) + + elif self.mode == SimulatorMode.IDEAL: + # self.car_odom_pubs[car_name] = rospy.Publisher( + # f'/cars/{car_name}/odom', Odometry, queue_size=1) + self.car_odom_pubs[car_name] = self.node.create_publisher( + Odometry, f'/cars/{car_name}/odom', 1) + + return True + +def main(): + Simulator() + +if __name__ == "__main__": + main() diff --git a/rktl_sim/src/simulator/__init__.py b/src/rktl_sim/rktl_sim/simulator/__init__.py similarity index 100% rename from rktl_sim/src/simulator/__init__.py rename to src/rktl_sim/rktl_sim/simulator/__init__.py diff --git a/rktl_sim/src/simulator/car.py b/src/rktl_sim/rktl_sim/simulator/car.py similarity index 100% rename from rktl_sim/src/simulator/car.py rename to src/rktl_sim/rktl_sim/simulator/car.py diff --git a/rktl_sim/src/simulator/sim.py b/src/rktl_sim/rktl_sim/simulator/sim.py similarity index 99% rename from rktl_sim/src/simulator/sim.py rename to src/rktl_sim/rktl_sim/simulator/sim.py index 164eb5cd1..6b0a2b8bf 100644 --- a/rktl_sim/src/simulator/sim.py +++ b/src/rktl_sim/rktl_sim/simulator/sim.py @@ -291,11 +291,11 @@ def delete_car(self, car_id): del self._car_data[car_id] return True - def set_car_command(car_id, msg): + def set_car_command(self, car_id, msg): """ Set the command for the car """ - cars[car_id].setCmd(msg) + self._cars[car_id].setCmd(msg) def step(self, car_cmd, dt): """ diff --git a/rktl_sim/nodes/visualizer b/src/rktl_sim/rktl_sim/vis_visualizer.py similarity index 71% rename from rktl_sim/nodes/visualizer rename to src/rktl_sim/rktl_sim/vis_visualizer.py index f98f82701..0d949b505 100755 --- a/rktl_sim/nodes/visualizer +++ b/src/rktl_sim/rktl_sim/vis_visualizer.py @@ -13,45 +13,58 @@ import math from std_msgs.msg import Float32 from nav_msgs.msg import Odometry -import rospy -from tf.transformations import euler_from_quaternion +import rclpy +import sys +from rclpy.exceptions import ROSInterruptException + +from rktl_dependencies.transformations import euler_from_quaternion + +from window import Window from threading import Lock import numpy as np + # Local library -import visualizer from rktl_msgs.msg import Path, BezierPathList from rktl_planner import BezierPath + class VisualizerROS(object): """ROS wrapper for the visualizer.""" def __init__(self): - rospy.init_node("visualizer") - + #rospy.init_node("visualizer") + rclpy.init(args=sys.argv) + global node + node = rclpy.create_node("visualizer") + # Collecting global parameters - field_width = rospy.get_param('/field/width') - field_length = rospy.get_param('/field/length') - goal_width = rospy.get_param('/field/goal/width') - wall_thickness = rospy.get_param('/field/wall_thickness') - ball_radius = rospy.get_param("/ball/radius") + field_width = node.declare_parameter('field.width').get_parameter_value().double_value + field_length = node.declare_parameter('field.length').get_parameter_value().double_value + goal_width = node.declare_parameter('field.goal.width').get_parameter_value().double_value + wall_thickness = node.declare_parameter('field.wall_thickness').get_parameter_value().double_value + ball_radius = node.declare_parameter("ball.radius").get_parameter_value().double_value # Creating pygame render - self.window = visualizer.Window( + self.window = Window( field_width, field_length, wall_thickness, - rospy.get_param('~window_name', 'Rocket League Visualizer')) - + node.declare_parameter('~window_name', 'Rocket League Visualizer').value) + # Collecting private parameters - self.frame_id = rospy.get_param("~frame_id", "map") - self.timeout = rospy.get_param("~timeout", 10) - rate = rospy.Rate(rospy.get_param("~rate", 20)) - car_width = rospy.get_param("~cars/body_width") - car_length = rospy.get_param("~cars/body_length") + self.frame_id = node.declare_parameter("frame_id", "map").value + self.timeout = node.declare_parameter("timeout", 10).value + rate = node.create_rate(20) + car_width = node.declare_parameter("cars.body_width").value + car_length = node.declare_parameter("cars.body_length").value # Setting up field id = 0 - field_img_path = rospy.get_param("~media/field", None) + # field_img_path = rospy.get_param("~media/field", None) + field_img_path = node.declare_parameter("media/field", None).get_parameter_value().string_value + + print(f"field_img_path: {field_img_path}") + if field_img_path is not None: self.window.createAsset( id, field_width, field_length, imgPath=field_img_path, @@ -95,7 +108,9 @@ def __init__(self): id += 1 # Setting up car - car_img_path = rospy.get_param("~media/car", None) + car_img_path = node.declare_parameter("media/car", None).get_parameter_value().string_value + + if car_img_path is not None: self.car_id = id self.window.createAsset( @@ -103,7 +118,8 @@ def __init__(self): id += 1 # Setting up ball - ball_img_path = rospy.get_param("~media/ball", None) + ball_img_path = node.declare_parameter("media/ball", None).get_parameter_value().string_value + if ball_img_path is not None: self.ball_id = id self.window.createAsset( @@ -136,24 +152,24 @@ def __init__(self): self.path = None # Subscribers - rospy.Subscriber("/ball/odom", Odometry, self.ball_odom_cb) - rospy.Subscriber("/cars/car0/odom", Odometry, self.car_odom_cb) - rospy.Subscriber("/cars/car0/path", Path, self.path_arr_cb) - rospy.Subscriber( - "/cars/car0/lookahead_pnt", Float32, self.lookahead_cb - ) - rospy.Subscriber("/agents/agent0/bezier_path", BezierPathList, self.bezier_path_cb) - - while not rospy.is_shutdown(): + node.create_subscription(Odometry, "/ball/odom", self.ball_odom_cb, qos_profile=1) + node.create_subscription(Odometry, "/cars/car0/odom", self.car_odom_cb, qos_profile=1) + node.create_subscription(Path, "/cars/car0/path", self.path_arr_cb, qos_profile=1) + node.create_subscription(Float32, "/cars/car0/lookahead_pnt", self.lookahead_cb, qos_profile=1) + node.create_subscription(BezierPathList, "/agents/agent0/bezier_path", self.bezier_path_cb, qos_profile=1) + + + while not rclpy.ok(): try: self.window.show() except self.window.ShutdownError: exit() try: rate.sleep() - except rospy.ROSInterruptException: + except ROSInterruptException: pass + def car_odom_cb(self, odom_msg): x = odom_msg.pose.pose.position.x y = odom_msg.pose.pose.position.y @@ -195,5 +211,8 @@ def lookahead_cb(self, msg: Float32): """Callback for lookahead_pnt messages.""" self.window.updateAssetRadius(self.circle_id, msg.data) -if __name__ == "__main__": +def main(): VisualizerROS() + +if __name__ == "__main__": + main() diff --git a/rktl_sim/src/visualizer/__init__.py b/src/rktl_sim/rktl_sim/visualizer/__init__.py similarity index 86% rename from rktl_sim/src/visualizer/__init__.py rename to src/rktl_sim/rktl_sim/visualizer/__init__.py index e760de69b..27dc4d9ba 100644 --- a/rktl_sim/src/visualizer/__init__.py +++ b/src/rktl_sim/rktl_sim/visualizer/__init__.py @@ -8,6 +8,6 @@ All rights reserved. """ -from visualizer.window import Window +from window import Window __all__ = ["Window",] diff --git a/src/rktl_sim/rktl_sim/visualizer/asset.py b/src/rktl_sim/rktl_sim/visualizer/asset.py new file mode 100644 index 000000000..16ecda167 --- /dev/null +++ b/src/rktl_sim/rktl_sim/visualizer/asset.py @@ -0,0 +1,89 @@ +"""Contains the Asset class. +License: + BSD 3-Clause License + Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + All rights reserved. +""" + +# 3rd Party Modules +import pygame +from abc import ABC, abstractmethod + + +class Asset(ABC): + @abstractmethod + def setPos(self): + pass + + @abstractmethod + def blit(self): + pass + + +class Image(Asset): + def __init__(self, width, length, img_path): + self.width = width + self.length = length + self.pos = (0, 0) + self.angle = 0 + + self.init_img = pygame.image.load(img_path) + self.init_img = pygame.transform.scale( + self.init_img, (width, length)) + self.img = self.init_img + + def setPos(self, x, y): + self.pos = (x, y) + + def setAngle(self, angle): + if angle < 0: + angle = 360. + angle + + self.img = pygame.transform.rotate(self.init_img, angle) + + def blit(self, screen): + rect = self.img.get_rect(center=self.pos) + screen.blit(self.img, rect) + + +class Rectangle(Asset): + def __init__(self, width, length, color): + self.color = color + self.rect = pygame.Rect(0, 0, width, length) + + def setPos(self, x, y): + self.pos = (x, y) + self.rect.center = self.pos + + def blit(self, screen): + pygame.draw.rect(screen, self.color, self.rect) + +class Lines(Asset): + def __init__(self, color): + self.color = color + self.points = [] + + def resetPoints(self): + self.points = [] + + def setPos(self, x, y): + self.points.append((x, y)) + + def blit(self, screen): + if len(self.points) > 1: + pygame.draw.lines(screen, self.color, False, self.points) + +class Circle(Asset): + def __init__(self, color, radius): + self.color = color + self.radius = radius + self.pos = (0, 0) + + def setPos(self, x, y): + self.pos = (x, y) + + def setRadius(self, radius): + self.radius = radius + + def blit(self, screen): + pygame.draw.circle(screen, self.color, self.pos, self.radius, 5) diff --git a/rktl_sim/src/visualizer/window.py b/src/rktl_sim/rktl_sim/visualizer/window.py similarity index 100% rename from rktl_sim/src/visualizer/window.py rename to src/rktl_sim/rktl_sim/visualizer/window.py diff --git a/src/rktl_sim/rktl_sim/window.py b/src/rktl_sim/rktl_sim/window.py new file mode 100644 index 000000000..ee44657fc --- /dev/null +++ b/src/rktl_sim/rktl_sim/window.py @@ -0,0 +1,86 @@ +"""Contains the Window class. +License: + BSD 3-Clause License + Copyright (c) 2023, Autonomous Robotics Club of Purdue (Purdue ARC) + All rights reserved. +""" + +# 3rd party modules +import pygame + +# Local modules +from asset import Image, Rectangle, Lines, Circle + +class Window(object): + """Interfaces PyGame for rendering.""" + + BACKGROUND_COLOR = (0, 0, 0) + GOAL_COLOR = (0, 200, 0) + WALL_COLOR = (0, 0, 0) + + class ShutdownError(Exception): + """Exception for when pygame is shut down""" + pass + + def __init__(self, map_width, map_length, wall_thickness, name='Rocket League Visualizer'): + # Support for 1080p monitors + self.scaling = 1080 * 0.5 / (map_length + (wall_thickness*2.)) + + self.window_length = int( + (map_length + (wall_thickness*2.)) * self.scaling) + self.window_width = int( + (map_width + (wall_thickness*2.)) * self.scaling) + + self.assets = {} + + pygame.display.init() + pygame.display.set_caption(name) + self._screen = pygame.display.set_mode( + (self.window_width, self.window_length)) + + def createAsset(self, id, width, length, initPos=None, imgPath=None, color=None, radius=None, lines=False, circle=False): + width = int(width * self.scaling) + length = int(length * self.scaling) + + if lines: + self.assets[id] = Lines(color) + elif circle: + radius = int(radius * self.scaling) + self.assets[id] = Circle(color, radius) + elif imgPath is None: + self.assets[id] = Rectangle(width, length, color) + else: + self.assets[id] = Image(width, length, imgPath) + + if initPos is not None: + self.updateAssetPos(id, initPos[0], initPos[1]) + + def updateAssetPos(self, id, x, y): + # Adjust for simulation coordinate frame + x = self.window_length - \ + (int(x * self.scaling) + (self.window_length // 2)) + y = self.window_width - \ + (int(y * self.scaling) + (self.window_width // 2)) + self.assets[id].setPos(y, x) + + def updateAssetRadius(self, id, radius): + radius = int(radius * self.scaling) + self.assets[id].setRadius(radius) + + def updateAssetAngle(self, id, angle): + self.assets[id].setAngle(angle) + + def resetAssetLines(self, id): + self.assets[id].resetPoints() + + def show(self): + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + raise self.ShutdownError() + + self._screen.fill(self.BACKGROUND_COLOR) + for asset in self.assets.values(): + asset.blit(self._screen) + + pygame.display.flip() diff --git a/rktl_sim/rviz_config.rviz b/src/rktl_sim/rviz_config.rviz similarity index 100% rename from rktl_sim/rviz_config.rviz rename to src/rktl_sim/rviz_config.rviz diff --git a/src/rktl_sim/setup.cfg b/src/rktl_sim/setup.cfg new file mode 100644 index 000000000..45503f02c --- /dev/null +++ b/src/rktl_sim/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rktl_sim +[install] +install_scripts=$base/lib/rktl_sim diff --git a/src/rktl_sim/setup.py b/src/rktl_sim/setup.py new file mode 100644 index 000000000..2585fdbf0 --- /dev/null +++ b/src/rktl_sim/setup.py @@ -0,0 +1,36 @@ +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = 'rktl_sim' + +setup( + name=package_name, + version='0.0.0', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + (os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))), + (os.path.join('lib', package_name), glob(os.path.join(package_name, '*.py'))), + (os.path.join('share', package_name, 'config'), glob(os.path.join('config', '*.yaml'))), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='Autonomous Robotics Club of Purdue', + maintainer_email='autonomy@purdue.edu', + description='Simulator and Visualizer for ARC Rocket League project', + license='BSD 3 Clause', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'simulator = rktl_sim.sim_simulator:main', + 'visualizer = rktl_sim.vis_visualizer:main', + 'car = rktl_sim.simulator.car:main', + 'sim = rktl_sim.simulator.sim:main', + 'asset = rktl_sim.visualizer.asset:main', + 'window = rktl_sim.visualizer.window:main' + ], + }, +) diff --git a/rktl_sim/test/test_car.test b/src/rktl_sim/test/test_car.test similarity index 100% rename from rktl_sim/test/test_car.test rename to src/rktl_sim/test/test_car.test diff --git a/rktl_sim/test/test_car_node b/src/rktl_sim/test/test_car_node similarity index 77% rename from rktl_sim/test/test_car_node rename to src/rktl_sim/test/test_car_node index 4e5cd1e7c..dbadc868c 100755 --- a/rktl_sim/test/test_car_node +++ b/src/rktl_sim/test/test_car_node @@ -7,17 +7,25 @@ License: """ import unittest -import rospy -import simulator +import rclpy +import sys +import time +#import rospy +from rktl_sim.nodes import simulator class TestCar(unittest.TestCase): @classmethod def setUpClass(cls): - rospy.init_node('test_car_node') - + #rospy.init_node('test_car_node') + rclpy.init(args=sys.argv) + node = rclpy.create_node('test_car_node') + node.get_logger().info('Created node') + urdf_paths = {} - urdf_paths["car"] = rospy.get_param('~urdf/car') - urdf_paths["plane"] = rospy.get_param('~urdf/plane') + # urdf_paths["car"] = rospy.get_param('~urdf/car') + # urdf_paths["plane"] = rospy.get_param('~urdf/plane') + urdf_paths["car"] = node.node.declare_parameter('~urdf/car').value + urdf_paths["plane"] = node.declare_parameter('~urdf/plane').value # Creating physics simulator cls.sim = simulator.Sim( diff --git a/src/rktl_sim/test/test_copyright.py b/src/rktl_sim/test/test_copyright.py new file mode 100644 index 000000000..97a39196e --- /dev/null +++ b/src/rktl_sim/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/rktl_sim/test/test_flake8.py b/src/rktl_sim/test/test_flake8.py new file mode 100644 index 000000000..27ee1078f --- /dev/null +++ b/src/rktl_sim/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/rktl_sim/test/test_pep257.py b/src/rktl_sim/test/test_pep257.py new file mode 100644 index 000000000..b234a3840 --- /dev/null +++ b/src/rktl_sim/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/rktl_sim/urdf/backwall.urdf b/src/rktl_sim/urdf/backwall.urdf similarity index 100% rename from rktl_sim/urdf/backwall.urdf rename to src/rktl_sim/urdf/backwall.urdf diff --git a/rktl_sim/urdf/ball.urdf b/src/rktl_sim/urdf/ball.urdf similarity index 100% rename from rktl_sim/urdf/ball.urdf rename to src/rktl_sim/urdf/ball.urdf diff --git a/rktl_sim/urdf/car.urdf b/src/rktl_sim/urdf/car.urdf similarity index 100% rename from rktl_sim/urdf/car.urdf rename to src/rktl_sim/urdf/car.urdf diff --git a/rktl_sim/urdf/goal.urdf b/src/rktl_sim/urdf/goal.urdf similarity index 100% rename from rktl_sim/urdf/goal.urdf rename to src/rktl_sim/urdf/goal.urdf diff --git a/rktl_sim/urdf/plane.urdf b/src/rktl_sim/urdf/plane.urdf similarity index 100% rename from rktl_sim/urdf/plane.urdf rename to src/rktl_sim/urdf/plane.urdf diff --git a/rktl_sim/urdf/plane100.obj b/src/rktl_sim/urdf/plane100.obj similarity index 100% rename from rktl_sim/urdf/plane100.obj rename to src/rktl_sim/urdf/plane100.obj diff --git a/rktl_sim/urdf/sidewall.urdf b/src/rktl_sim/urdf/sidewall.urdf similarity index 100% rename from rktl_sim/urdf/sidewall.urdf rename to src/rktl_sim/urdf/sidewall.urdf diff --git a/rktl_sim/urdf/walls.urdf b/src/rktl_sim/urdf/walls.urdf similarity index 100% rename from rktl_sim/urdf/walls.urdf rename to src/rktl_sim/urdf/walls.urdf