From 8fcdf729d9dc92d77eae8920caab4823a3de62fd Mon Sep 17 00:00:00 2001 From: David Leins Date: Thu, 21 Dec 2023 12:24:09 +0100 Subject: [PATCH 1/2] chg: sensors overhaul (wip for compound) --- mujoco_ros/CHANGELOG.md | 5 + mujoco_ros_sensors/CMakeLists.txt | 1 + .../mujoco_sensor_handler_plugin.h | 37 +- .../include/mujoco_ros_sensors/sensors.h | 155 +++++++ .../mujoco_ros_sensors/serialization.h | 86 ++++ .../src/mujoco_sensor_handler_plugin.cpp | 409 ++++-------------- mujoco_ros_sensors/src/serialization.cpp | 213 +++++++++ .../test/mujoco_sensors_test.cpp | 207 +++++---- 8 files changed, 678 insertions(+), 435 deletions(-) create mode 100644 mujoco_ros_sensors/include/mujoco_ros_sensors/sensors.h create mode 100644 mujoco_ros_sensors/include/mujoco_ros_sensors/serialization.h create mode 100644 mujoco_ros_sensors/src/serialization.cpp diff --git a/mujoco_ros/CHANGELOG.md b/mujoco_ros/CHANGELOG.md index 9a9e85ef..1bce6ad1 100644 --- a/mujoco_ros/CHANGELOG.md +++ b/mujoco_ros/CHANGELOG.md @@ -4,11 +4,16 @@ ### Fixed * Repaired SIGINT handler callback. `C-c` in the roslaunch terminal now shuts down the MuJoCo ROS node instead of escalating to SIGTERM. * Added actionlib to the list of mujoco_ros' dependencies. +* Fixed bug where cutoff was not correctly applied to noisy sensors. ### Changed * replaced `boost::shared_ptr` with `std::shared_ptr` or `std::unique_ptr` wherever possible (ROS 1 fast intra-process message-passing requires boost::shared_ptr). * replaced `shared_ptr` with `unique_ptr` wherever possible. * replaced smart pointer constructor initialization with `make_shared` or `make_unique` wherever possible. +* **Sensors overhaul:** +* * Increased sensor unit test speed by parallelly buffering and running computations for statistics. +* * Renamed sensor unit tests to serialization types for more clarity. +* * Replaced `SensorConfig` with `RosSensorInterfaceBase` and derived, templated `RosSensorInterface` classes with specialized serializers and noise management. ## [0.8.0] - 2023-10-23 diff --git a/mujoco_ros_sensors/CMakeLists.txt b/mujoco_ros_sensors/CMakeLists.txt index 74f163ed..6f6ed3af 100644 --- a/mujoco_ros_sensors/CMakeLists.txt +++ b/mujoco_ros_sensors/CMakeLists.txt @@ -46,6 +46,7 @@ include_directories( add_library(${PROJECT_NAME} src/mujoco_sensor_handler_plugin.cpp + src/serialization.cpp ) target_link_libraries(${PROJECT_NAME} ${catkin_LIBRARIES} diff --git a/mujoco_ros_sensors/include/mujoco_ros_sensors/mujoco_sensor_handler_plugin.h b/mujoco_ros_sensors/include/mujoco_ros_sensors/mujoco_sensor_handler_plugin.h index af12876d..dd187aed 100644 --- a/mujoco_ros_sensors/include/mujoco_ros_sensors/mujoco_sensor_handler_plugin.h +++ b/mujoco_ros_sensors/include/mujoco_ros_sensors/mujoco_sensor_handler_plugin.h @@ -38,7 +38,7 @@ #pragma once -#include +#include #include #include @@ -46,36 +46,12 @@ #include +#include + #include namespace mujoco_ros::sensors { -struct SensorConfig -{ -public: - SensorConfig() : frame_id(""){}; - SensorConfig(std::string frame_id) : frame_id(std::move(frame_id)){}; - - void setFrameId(const std::string &frame_id) { this->frame_id = frame_id; }; - - void registerPub(const ros::Publisher &pub) { value_pub = pub; }; - - void registerGTPub(const ros::Publisher &pub) { gt_pub = pub; }; - - std::string frame_id; - - ros::Publisher gt_pub; - ros::Publisher value_pub; - - // Noise params - double mean[3]; - double sigma[3]; - - uint8_t is_set = 0; // 0 for unset, otherwise binary code for combination of dims -}; - -using SensorConfigPtr = std::unique_ptr; - class MujocoRosSensorsPlugin : public mujoco_ros::MujocoPlugin { public: @@ -91,10 +67,11 @@ class MujocoRosSensorsPlugin : public mujoco_ros::MujocoPlugin private: ros::NodeHandle sensors_nh_; void initSensors(const mjModel *model, mjData *data); - std::mt19937 rand_generator = std::mt19937(std::random_device{}()); - std::normal_distribution noise_dist; + std::mt19937 rand_generator_ = std::mt19937(std::random_device{}()); + std::normal_distribution noise_dist_; - std::map sensor_map_; + std::map sensor_map_; + // std::vector enabled_sensors_; ros::ServiceServer register_noise_model_server_; diff --git a/mujoco_ros_sensors/include/mujoco_ros_sensors/sensors.h b/mujoco_ros_sensors/include/mujoco_ros_sensors/sensors.h new file mode 100644 index 00000000..9bfeb41c --- /dev/null +++ b/mujoco_ros_sensors/include/mujoco_ros_sensors/sensors.h @@ -0,0 +1,155 @@ +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2023, Bielefeld University + * 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 Bielefeld University 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. + *********************************************************************/ + +/* Authors: David P. Leins */ + +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +namespace mujoco_ros::sensors { + +class RosSensorInterfaceBase +{ +public: + RosSensorInterfaceBase(const std::string &frame_id, const std::string &sensor_name, const int dofs) + : frame_id_(frame_id), sensor_name_(sensor_name), dofs_(dofs) + { + if (dofs_ == 0) + return; + + mu_noise_ = new mjtNum[dofs]; + sigma_noise_ = new mjtNum[dofs]; + }; + + void setNoise(const mjtNum *mu_noise, const mjtNum *sigma_noise, uint8_t noise_set); + + ~RosSensorInterfaceBase() + { + if (dofs_ == 0) + return; + + delete[] mu_noise_; + delete[] sigma_noise_; + }; + + virtual void publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) = 0; + +protected: + std::string frame_id_; + std::string sensor_name_; + + ros::Time last_pub_; + ros::Publisher value_pub_; + ros::Publisher gt_pub_; + + mjtNum *mu_noise_; + mjtNum *sigma_noise_; + + uint dofs_; + + uint8_t noise_set_ = 0; // 0 for unset, otherwise binary code for set dimensions +}; + +template +class RosSensorInterface : public RosSensorInterfaceBase +{ +public: + RosSensorInterface(const std::string &frame_id, const std::string &sensor_name, const int dofs, const int adr, + const mjtNum cutoff, bool eval_mode, ros::NodeHandle *sensors_nh); + + void publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) override; + T serialize(const mjData *data); + void addNoise(T &msg, std::normal_distribution &dist, std::mt19937 &gen); + +private: + int adr_; + mjtNum cutoff_; +}; + +// template impl needs to be in header +template +void RosSensorInterface::publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) +{ + T msg = serialize(data); + if (publish_gt) { + gt_pub_.publish(msg); + } + addNoise(msg, dist, gen); + value_pub_.publish(msg); +} + +template +RosSensorInterface::RosSensorInterface(const std::string &frame_id, const std::string &sensor_name, const int dofs, + const int adr, const mjtNum cutoff, bool eval_mode, + ros::NodeHandle *sensors_nh) + : RosSensorInterfaceBase(frame_id, sensor_name, dofs), adr_(adr), cutoff_(cutoff) +{ + value_pub_ = sensors_nh->advertise(sensor_name, 1); + if (!eval_mode) { + gt_pub_ = sensors_nh->advertise(sensor_name + "_GT", 1); + } +}; + +template +T RosSensorInterface::serialize(const mjData *data) +{ + T msg; + mujoco_ros::sensors::serialize(msg, data, frame_id_, adr_, cutoff_); + return msg; +} + +template +void RosSensorInterface::addNoise(T &msg, std::normal_distribution &dist, std::mt19937 &gen) +{ + mujoco_ros::sensors::addNoise(msg, mu_noise_, sigma_noise_, noise_set_, dist, gen); +} + +} // namespace mujoco_ros::sensors diff --git a/mujoco_ros_sensors/include/mujoco_ros_sensors/serialization.h b/mujoco_ros_sensors/include/mujoco_ros_sensors/serialization.h new file mode 100644 index 00000000..8a6c77e3 --- /dev/null +++ b/mujoco_ros_sensors/include/mujoco_ros_sensors/serialization.h @@ -0,0 +1,86 @@ +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2023, Bielefeld University + * 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 Bielefeld University 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. + *********************************************************************/ + +/* Authors: David P. Leins */ + +#pragma once + +#include +#include + +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +namespace mujoco_ros::sensors { + +void serialize(geometry_msgs::QuaternionStamped &msg, const mjData *data, const std::string &frame_id, const int &adr, + const mjtNum &cutoff); +void serialize(geometry_msgs::Vector3Stamped &msg, const mjData *data, const std::string &frame_id, const int &adr, + const mjtNum &cutoff); +void serialize(geometry_msgs::PointStamped &msg, const mjData *data, const std::string &frame_id, const int &adr, + const mjtNum &cutoff); +void serialize(mujoco_ros_msgs::ScalarStamped &msg, const mjData *data, const std::string &frame_id, const int &adr, + const mjtNum &cutoff); + +void addNoise(geometry_msgs::Quaternion &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen); +void addNoise(geometry_msgs::QuaternionStamped &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen); + +void addNoise(geometry_msgs::Vector3 &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, const uint8_t &noise_set, + std::normal_distribution &dist, std::mt19937 &gen); +void addNoise(geometry_msgs::Vector3Stamped &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen); + +void addNoise(geometry_msgs::Point &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, const uint8_t &noise_set, + std::normal_distribution &dist, std::mt19937 &gen); +void addNoise(geometry_msgs::PointStamped &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen); + +void addNoise(double &value, const mjtNum *mu_noise, const mjtNum *sigma_noise, const uint8_t &noise_set, + std::normal_distribution &dist, std::mt19937 &gen); +void addNoise(mujoco_ros_msgs::ScalarStamped &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen); + +} // namespace mujoco_ros::sensors diff --git a/mujoco_ros_sensors/src/mujoco_sensor_handler_plugin.cpp b/mujoco_ros_sensors/src/mujoco_sensor_handler_plugin.cpp index 0a524a55..e30664aa 100644 --- a/mujoco_ros_sensors/src/mujoco_sensor_handler_plugin.cpp +++ b/mujoco_ros_sensors/src/mujoco_sensor_handler_plugin.cpp @@ -37,6 +37,7 @@ /* Authors: David P. Leins */ #include +#include #include @@ -45,14 +46,44 @@ #include #include -#include - #include +#include + namespace mujoco_ros::sensors { +void RosSensorInterfaceBase::setNoise(const mjtNum *mu_noise, const mjtNum *sigma_noise, uint8_t noise_set) +{ + uint noise_count = noise_set - ((noise_set >> 1) & 033333333333) - ((noise_set >> 2) & 011111111111); + noise_count = ((noise_count + (noise_count >> 3)) & 030707070707) % 63; + if (noise_count > dofs_) { + throw std::runtime_error("Too many noise parameters provided for sensor " + sensor_name_); + } + + int noise_idx = 0; + if (noise_set & 0x01) { + mu_noise_[noise_idx] = mu_noise[noise_idx]; + sigma_noise_[noise_idx] = sigma_noise[noise_idx]; + noise_idx++; + } + if (noise_set & 0x02) { + mu_noise_[noise_idx] = mu_noise[noise_idx]; + sigma_noise_[noise_idx] = sigma_noise[noise_idx]; + noise_idx++; + } + if (noise_set & 0x04) { + mu_noise_[noise_idx] = mu_noise[noise_idx]; + sigma_noise_[noise_idx] = sigma_noise[noise_idx]; + } + + noise_set_ = noise_set_ | noise_set; +} + MujocoRosSensorsPlugin::~MujocoRosSensorsPlugin() { + for (auto &sensor : sensor_map_) { + delete sensor.second; + } sensor_map_.clear(); ROS_DEBUG_STREAM_NAMED("sensors", "Shutting down service " << register_noise_model_server_.getService()); register_noise_model_server_.shutdown(); @@ -62,7 +93,7 @@ bool MujocoRosSensorsPlugin::load(const mjModel *model, mjData *data) { ROS_INFO_NAMED("sensors", "Loading sensors plugin ..."); if (env_ptr_->settings_.eval_mode) { - ROS_WARN_NAMED("sensors", "Evalutaion mode is active, ground truth topics won't be available!"); + ROS_WARN_NAMED("sensors", "Evaluation mode is active, ground truth topics won't be available!"); } else { ROS_WARN_NAMED("sensors", "Train mode is active, ground truth topics will be available!"); } @@ -109,7 +140,7 @@ bool MujocoRosSensorsPlugin::load(const mjModel *model, mjData *data) } sensors_nh_ = ros::NodeHandle("/" + sensors_namespace); - noise_dist = std::normal_distribution(0.0, 1.0); + noise_dist_ = std::normal_distribution(0.0, 1.0); initSensors(model, data); ROS_INFO_NAMED("sensors", "All sensors initialized"); @@ -132,42 +163,31 @@ bool MujocoRosSensorsPlugin::registerNoiseModelsCB(mujoco_ros_msgs::RegisterSens ROS_DEBUG_NAMED("mujoco", "Hash valid, request authorized."); } - int noise_idx; + resp.success = true; for (const mujoco_ros_msgs::SensorNoiseModel &noise_model : req.noise_models) { ROS_WARN_STREAM_NAMED("sensors", "registering noise model for " << noise_model.sensor_name); - noise_idx = 0; - const std::map::const_iterator &pos = sensor_map_.find(noise_model.sensor_name); + const std::map::const_iterator &pos = + sensor_map_.find(noise_model.sensor_name); if (pos == sensor_map_.end()) { ROS_WARN_STREAM_NAMED("sensors", "No sensor with name '" << noise_model.sensor_name << "' was registered on init. Can not apply noise model"); + resp.success = false; continue; } - const SensorConfigPtr &config = pos->second; - ROS_DEBUG_STREAM_COND_NAMED(config->is_set > 0, "sensors", "Overriding current noise model with newly provided"); - - if (noise_model.set_flag & 0x01) { - config->mean[noise_idx] = noise_model.mean[noise_idx]; - config->sigma[noise_idx] = noise_model.std[noise_idx]; - noise_idx += 1; - } - if (noise_model.set_flag & 0x02) { - config->mean[noise_idx] = noise_model.mean[noise_idx]; - config->sigma[noise_idx] = noise_model.std[noise_idx]; - noise_idx += 1; - } - if (noise_model.set_flag & 0x04) { - config->mean[noise_idx] = noise_model.mean[noise_idx]; - config->sigma[noise_idx] = noise_model.std[noise_idx]; + RosSensorInterfaceBase *sensor = pos->second; + try { + sensor->setNoise(noise_model.mean.data(), noise_model.std.data(), noise_model.set_flag); + } catch (const std::runtime_error &e) { + ROS_ERROR_STREAM_NAMED("sensors", "Error while setting noise model for sensor " << noise_model.sensor_name + << ": " << e.what()); + resp.success = false; + return true; } - - config->is_set = config->is_set | noise_model.set_flag; } - resp.success = true; - return true; } @@ -175,262 +195,11 @@ void MujocoRosSensorsPlugin::lastStageCallback(const mjModel *model, mjData *dat { std::string sensor_name; - int adr, type, noise_idx; - mjtNum cutoff; - double noise = 0.0; - - for (int n = 0; n < model->nsensor; n++) { - adr = model->sensor_adr[n]; - type = model->sensor_type[n]; - cutoff = (model->sensor_cutoff[n] > 0 ? model->sensor_cutoff[n] : 1); - noise_idx = 0; - - if (model->names[model->name_sensoradr[n]]) { - sensor_name = mj_id2name(const_cast(model), mjOBJ_SENSOR, n); - } else { - continue; - } - - if (sensor_map_.find(sensor_name) == sensor_map_.end()) - continue; - - SensorConfigPtr &config = sensor_map_[sensor_name]; - - switch (type) { - { - case mjSENS_FRAMELINVEL: - case mjSENS_FRAMELINACC: - case mjSENS_FRAMEANGACC: - case mjSENS_SUBTREECOM: - case mjSENS_SUBTREELINVEL: - case mjSENS_SUBTREEANGMOM: - case mjSENS_ACCELEROMETER: - case mjSENS_VELOCIMETER: - case mjSENS_GYRO: - case mjSENS_FORCE: - case mjSENS_TORQUE: - case mjSENS_MAGNETOMETER: - case mjSENS_BALLANGVEL: - case mjSENS_FRAMEXAXIS: - case mjSENS_FRAMEYAXIS: - case mjSENS_FRAMEZAXIS: - geometry_msgs::Vector3Stamped msg; - msg.header.frame_id = config->frame_id; - msg.header.stamp = ros::Time::now(); - - // No noise configured - if (config->is_set == 0) { - msg.vector.x = static_cast(data->sensordata[adr] / cutoff); - msg.vector.y = static_cast(data->sensordata[adr + 1] / cutoff); - msg.vector.z = static_cast(data->sensordata[adr + 2] / cutoff); - - config->value_pub.publish(msg); - - if (!env_ptr_->settings_.eval_mode) { - config->gt_pub.publish(msg); - } - } else { // Noise at least in one dim - if (config->is_set & 0x01) { - // shift and scale standard normal to desired distribution - noise = noise_dist(rand_generator) * config->sigma[noise_idx] + config->mean[noise_idx]; - noise_idx += 1; - } else { - noise = 0; - } - msg.vector.x = static_cast(data->sensordata[adr] + noise / cutoff); - - if (config->is_set & 0x02) { - // shift and scale standard normal to desired distribution - noise = noise_dist(rand_generator) * config->sigma[noise_idx] + config->mean[noise_idx]; - noise_idx += 1; - } else { - noise = 0; - } - msg.vector.y = (float)(data->sensordata[adr + 1] + noise / cutoff); - - if (config->is_set & 0x04) { - // shift and scale standard normal to desired distribution - noise = noise_dist(rand_generator) * config->sigma[noise_idx] + config->mean[noise_idx]; - } else { - noise = 0; - } - msg.vector.z = (float)(data->sensordata[adr + 2] + noise / cutoff); - - config->value_pub.publish(msg); - - if (!env_ptr_->settings_.eval_mode) { - msg.vector.x = static_cast(data->sensordata[adr] / cutoff); - msg.vector.y = static_cast(data->sensordata[adr + 1] / cutoff); - msg.vector.z = static_cast(data->sensordata[adr + 2] / cutoff); - - config->gt_pub.publish(msg); - } - } - break; - } - - case mjSENS_FRAMEPOS: { - geometry_msgs::PointStamped msg; - msg.header.frame_id = config->frame_id; - msg.header.stamp = ros::Time::now(); - - // No noise configured - if (config->is_set == 0) { - msg.point.x = static_cast(data->sensordata[adr] / cutoff); - msg.point.y = static_cast(data->sensordata[adr + 1] / cutoff); - msg.point.z = static_cast(data->sensordata[adr + 2] / cutoff); - - config->value_pub.publish(msg); - - if (!env_ptr_->settings_.eval_mode) { - config->gt_pub.publish(msg); - } - } else { // Noise at least in one dim - if (config->is_set & 0x01) { - // shift and scale standard normal to desired distribution - noise = noise_dist(rand_generator) * config->sigma[noise_idx] + config->mean[noise_idx]; - noise_idx += 1; - } else { - noise = 0; - } - msg.point.x = static_cast(data->sensordata[adr] + noise / cutoff); - - if (config->is_set & 0x02) { - // shift and scale standard normal to desired distribution - noise = noise_dist(rand_generator) * config->sigma[noise_idx] + config->mean[noise_idx]; - noise_idx += 1; - } else { - noise = 0; - } - msg.point.y = static_cast(data->sensordata[adr + 1] + noise / cutoff); - - if (config->is_set & 0x04) { - // shift and scale standard normal to desired distribution - noise = noise_dist(rand_generator) * config->sigma[noise_idx] + config->mean[noise_idx]; - } else { - noise = 0; - } - msg.point.z = static_cast(data->sensordata[adr + 2] + noise / cutoff); - - config->value_pub.publish(msg); - - if (!env_ptr_->settings_.eval_mode) { - msg.point.x = static_cast(data->sensordata[adr] / cutoff); - msg.point.y = static_cast(data->sensordata[adr + 1] / cutoff); - msg.point.z = static_cast(data->sensordata[adr + 2] / cutoff); - - config->gt_pub.publish(msg); - } - } - break; - } - - { - case mjSENS_TOUCH: - case mjSENS_RANGEFINDER: - case mjSENS_JOINTPOS: - case mjSENS_JOINTVEL: - case mjSENS_TENDONPOS: - case mjSENS_TENDONVEL: - case mjSENS_ACTUATORPOS: - case mjSENS_ACTUATORVEL: - case mjSENS_ACTUATORFRC: - case mjSENS_JOINTLIMITPOS: - case mjSENS_JOINTLIMITVEL: - case mjSENS_JOINTLIMITFRC: - case mjSENS_TENDONLIMITPOS: - case mjSENS_TENDONLIMITVEL: - case mjSENS_TENDONLIMITFRC: - mujoco_ros_msgs::ScalarStamped msg; - msg.header.frame_id = config->frame_id; - msg.header.stamp = ros::Time::now(); - - // No noise configured - if (config->is_set == 0) { - msg.value = static_cast(data->sensordata[adr] / cutoff); - - config->value_pub.publish(msg); - - if (!env_ptr_->settings_.eval_mode) { - config->gt_pub.publish(msg); - } - } else { // Noise set - // shift and scale standard normal to desired distribution - noise = noise_dist(rand_generator) * config->sigma[0] + config->mean[0]; - msg.value = static_cast(data->sensordata[adr] + noise / cutoff); - - config->value_pub.publish(msg); - - if (!env_ptr_->settings_.eval_mode) { - msg.value = static_cast(data->sensordata[adr] / cutoff); - - config->gt_pub.publish(msg); - } - } - break; - } - - case mjSENS_BALLQUAT: { - case mjSENS_FRAMEQUAT: - geometry_msgs::QuaternionStamped msg; - tf2::Quaternion q_orig, q_rot; - msg.header.frame_id = config->frame_id; - msg.header.stamp = ros::Time::now(); - - msg.quaternion.w = static_cast(data->sensordata[adr] / cutoff); - msg.quaternion.x = static_cast(data->sensordata[adr + 1] / cutoff); - msg.quaternion.y = static_cast(data->sensordata[adr + 2] / cutoff); - msg.quaternion.z = static_cast(data->sensordata[adr + 3] / cutoff); - - if (!env_ptr_->settings_.eval_mode) { - config->gt_pub.publish(msg); - } - - if (config->is_set == 0) { - config->value_pub.publish(msg); - } else { - tf2::fromMsg(msg.quaternion, q_orig); - q_orig.normalize(); - - double r, p, y; - - if (config->is_set & 0x01) { - // shift and scale standard normal to desired distribution - r = noise_dist(rand_generator) * config->sigma[noise_idx] + config->mean[noise_idx]; - noise_idx += 1; - } else { - r = 0; - } - if (config->is_set & 0x02) { - // shift and scale standard normal to desired distribution - p = noise_dist(rand_generator) * config->sigma[noise_idx] + config->mean[noise_idx]; - noise_idx += 1; - } else { - p = 0; - } - if (config->is_set & 0x04) { - // shift and scale standard normal to desired distribution - y = noise_dist(rand_generator) * config->sigma[noise_idx] + config->mean[noise_idx]; - } else { - y = 0; - } - - q_rot.setRPY(r, p, y); - q_rot.normalize(); - - msg.quaternion = tf2::toMsg((q_rot * q_orig).normalize()); - config->value_pub.publish(msg); - } - break; - } - - default: - ROS_ERROR_STREAM_NAMED( - "sensors", - "Sensor publisher and frame_id defined but type can't be serialized. This shouldn't happen! (" - << sensor_name << " of type " << type << ")"); - break; - } + std::map::iterator pos = sensor_map_.begin(); + while (pos != sensor_map_.end()) { + RosSensorInterfaceBase *sensor = pos->second; + sensor->publish(!env_ptr_->settings_.eval_mode, data, noise_dist_, rand_generator_); + pos++; } } @@ -441,6 +210,8 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) int site_id = model->sensor_objid[n]; int parent_id = model->site_bodyid[site_id]; int type = model->sensor_type[n]; + mjtNum cutoff = (model->sensor_cutoff[n] > 0 ? model->sensor_cutoff[n] : 1); + int adr = model->sensor_adr[n]; site = mj_id2name(const_cast(model), model->sensor_objtype[n], site_id); @@ -452,11 +223,10 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) continue; } - // Global frame sensors bool global_frame = false; frame_id = "world"; - SensorConfigPtr config; switch (type) { + // Global or relative frame 3 DoF sensors { case mjSENS_FRAMEXAXIS: case mjSENS_FRAMEYAXIS: @@ -476,27 +246,19 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) << reftype << " and ref_frame " << frame_id); } - config = std::make_unique(frame_id); - config->registerPub(sensors_nh_.advertise(sensor_name, 1, true)); - if (!env_ptr_->settings_.eval_mode) { - config->registerGTPub( - sensors_nh_.advertise(sensor_name + "_GT", 1, true)); - } - sensor_map_[sensor_name] = std::move(config); + sensor_map_[sensor_name] = new RosSensorInterface( + frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); break; } + // Global frame 3 DoF sensors case mjSENS_SUBTREECOM: case mjSENS_SUBTREELINVEL: case mjSENS_SUBTREEANGMOM: - config = std::make_unique(frame_id); - config->registerPub(sensors_nh_.advertise(sensor_name, 1, true)); - if (!env_ptr_->settings_.eval_mode) { - config->registerGTPub( - sensors_nh_.advertise(sensor_name + "_GT", 1, true)); - } - sensor_map_[sensor_name] = std::move(config); - global_frame = true; + sensor_map_[sensor_name] = new RosSensorInterface( + frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + global_frame = true; break; + // Global or local frame position sensors (1 DoF) { case mjSENS_FRAMEPOS: int refid = model->sensor_refid[n]; @@ -511,42 +273,36 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) << refid << " and type " << reftype << " and ref_frame " << frame_id); } - config = std::make_unique(frame_id); - config->registerPub(sensors_nh_.advertise(sensor_name, 1, true)); - if (!env_ptr_->settings_.eval_mode) { - config->registerGTPub( - sensors_nh_.advertise(sensor_name + "_GT", 1, true)); - } - sensor_map_[sensor_name] = std::move(config); - global_frame = true; + sensor_map_[sensor_name] = new RosSensorInterface( + frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + global_frame = true; break; } + // Global frame quaternion sensors (4 DoF) case mjSENS_BALLQUAT: case mjSENS_FRAMEQUAT: - config = std::make_unique(frame_id); - config->registerPub(sensors_nh_.advertise(sensor_name, 1, true)); - if (!env_ptr_->settings_.eval_mode) { - config->registerGTPub( - sensors_nh_.advertise(sensor_name + "_GT", 1, true)); - } - sensor_map_[sensor_name] = std::move(config); - global_frame = true; + sensor_map_[sensor_name] = new RosSensorInterface( + frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + global_frame = true; break; } - // Check if sensor is in global frame and already setup + // If global_frame was explicitly set to true or frame_id was overridden by relative frame + // the sensor is already registered and we can skip checking the remaining types if (global_frame || frame_id != "world") { ROS_DEBUG_STREAM_NAMED("sensors", "Setting up sensor " << sensor_name << " on site " << site << " (frame_id: " << frame_id << ") of type " << SENSOR_STRING[type]); continue; } + // The remaining sensors all measure in the site's parent body frame frame_id = mj_id2name(const_cast(model), mjOBJ_BODY, parent_id); ROS_DEBUG_STREAM_NAMED("sensors", "Setting up sensor " << sensor_name << " on site " << site << " (frame_id: " << frame_id << ") of type " << SENSOR_STRING[type]); switch (type) { + // 3D-vector type sensors case mjSENS_ACCELEROMETER: case mjSENS_VELOCIMETER: case mjSENS_GYRO: @@ -554,15 +310,11 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) case mjSENS_TORQUE: case mjSENS_MAGNETOMETER: case mjSENS_BALLANGVEL: - config = std::make_unique(frame_id); - config->registerPub(sensors_nh_.advertise(sensor_name, 1, true)); - if (!env_ptr_->settings_.eval_mode) { - config->registerGTPub( - sensors_nh_.advertise(sensor_name + "_GT", 1, true)); - } - sensor_map_[sensor_name] = std::move(config); + sensor_map_[sensor_name] = new RosSensorInterface( + frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); break; + // Scalar type sensors case mjSENS_TOUCH: case mjSENS_RANGEFINDER: case mjSENS_JOINTPOS: @@ -578,13 +330,8 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) case mjSENS_TENDONLIMITPOS: case mjSENS_TENDONLIMITVEL: case mjSENS_TENDONLIMITFRC: - config = std::make_unique(frame_id); - config->registerPub(sensors_nh_.advertise(sensor_name, 1, true)); - if (!env_ptr_->settings_.eval_mode) { - config->registerGTPub( - sensors_nh_.advertise(sensor_name + "_GT", 1, true)); - } - sensor_map_[sensor_name] = std::move(config); + sensor_map_[sensor_name] = new RosSensorInterface( + frame_id, sensor_name, 1, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); break; default: diff --git a/mujoco_ros_sensors/src/serialization.cpp b/mujoco_ros_sensors/src/serialization.cpp new file mode 100644 index 00000000..53624a62 --- /dev/null +++ b/mujoco_ros_sensors/src/serialization.cpp @@ -0,0 +1,213 @@ +/********************************************************************* + * Software License Agreement (BSD License) + * + * Copyright (c) 2023, Bielefeld University + * 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 Bielefeld University 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. + *********************************************************************/ + +/* Authors: David P. Leins */ + +#include +#include + +namespace mujoco_ros::sensors { + +void serialize(geometry_msgs::QuaternionStamped &msg, const mjData *data, const std::string &frame_id, const int &adr, + const mjtNum &cutoff) +{ + msg.header.stamp = ros::Time::now(); + msg.header.frame_id = frame_id; + msg.quaternion.w = static_cast(data->sensordata[adr] / cutoff); + msg.quaternion.x = static_cast(data->sensordata[adr + 1] / cutoff); + msg.quaternion.y = static_cast(data->sensordata[adr + 2] / cutoff); + msg.quaternion.z = static_cast(data->sensordata[adr + 3] / cutoff); +} + +void serialize(geometry_msgs::Vector3Stamped &msg, const mjData *data, const std::string &frame_id, const int &adr, + const mjtNum &cutoff) +{ + msg.header.stamp = ros::Time::now(); + msg.header.frame_id = frame_id; + msg.vector.x = static_cast(data->sensordata[adr] / cutoff); + msg.vector.y = static_cast(data->sensordata[adr + 1] / cutoff); + msg.vector.z = static_cast(data->sensordata[adr + 2] / cutoff); +} + +void serialize(geometry_msgs::PointStamped &msg, const mjData *data, const std::string &frame_id, const int &adr, + const mjtNum &cutoff) +{ + msg.header.stamp = ros::Time::now(); + msg.header.frame_id = frame_id; + msg.point.x = static_cast(data->sensordata[adr] / cutoff); + msg.point.y = static_cast(data->sensordata[adr + 1] / cutoff); + msg.point.z = static_cast(data->sensordata[adr + 2] / cutoff); +} + +void serialize(mujoco_ros_msgs::ScalarStamped &msg, const mjData *data, const std::string &frame_id, const int &adr, + const mjtNum &cutoff) +{ + msg.header.stamp = ros::Time::now(); + msg.header.frame_id = frame_id; + msg.value = static_cast(data->sensordata[adr] / cutoff); +} + +///////////// Noise addition /////////////// + +////// Quaternion ////// + +void addNoise(geometry_msgs::Quaternion &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen) +{ + if (noise_set == 0) { // no noise, leave unchanged + return; + } + + // For correct application, generate noise in roll, pitch, yaw, then convert to quaternion + tf2::Quaternion q, q_noise; + tf2::fromMsg(msg, q); + q.normalize(); + + double r = 0., p = 0., y = 0.; + int noise_idx = 0; + if (noise_set & 0x01) { // Noise in roll + r = dist(gen) * sigma_noise[noise_idx] + mu_noise[noise_idx]; + noise_idx++; + } + if (noise_set & 0x02) { // Noise in pitch + p = dist(gen) * sigma_noise[noise_idx] + mu_noise[noise_idx]; + noise_idx++; + } + if (noise_set & 0x04) { // Noise in yaw + y = dist(gen) * sigma_noise[noise_idx] + mu_noise[noise_idx]; + } + q_noise.setRPY(r, p, y); + + // Rotate original quaternion by noise quaternion + msg = tf2::toMsg((q_noise * q).normalize()); +} + +void addNoise(geometry_msgs::QuaternionStamped &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen) +{ + if (noise_set == 0) { // no noise, leave unchanged + return; + } + addNoise(msg.quaternion, mu_noise, sigma_noise, noise_set, dist, gen); +} + +////// Vector3 ////// + +void addNoise(geometry_msgs::Vector3 &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, const uint8_t &noise_set, + std::normal_distribution &dist, std::mt19937 &gen) +{ + if (noise_set == 0) { // no noise, leave unchanged + return; + } + + int noise_idx = 0; + if (noise_set & 0x01) { // Noise in x + msg.x += dist(gen) * sigma_noise[noise_idx] + mu_noise[noise_idx]; + noise_idx++; + } + if (noise_set & 0x02) { // Noise in y + msg.y += dist(gen) * sigma_noise[noise_idx] + mu_noise[noise_idx]; + noise_idx++; + } + if (noise_set & 0x04) { // Noise in z + msg.z += dist(gen) * sigma_noise[noise_idx] + mu_noise[noise_idx]; + } +} + +void addNoise(geometry_msgs::Vector3Stamped &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen) +{ + if (noise_set == 0) { // no noise, leave unchanged + return; + } + addNoise(msg.vector, mu_noise, sigma_noise, noise_set, dist, gen); +} + +////// Point ////// + +void addNoise(geometry_msgs::Point &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, const uint8_t &noise_set, + std::normal_distribution &dist, std::mt19937 &gen) +{ + if (noise_set == 0) { // no noise, leave unchanged + return; + } + + int noise_idx = 0; + if (noise_set & 0x01) { // Noise in x + msg.x += dist(gen) * sigma_noise[noise_idx] + mu_noise[noise_idx]; + noise_idx++; + } + if (noise_set & 0x02) { // Noise in y + msg.y += dist(gen) * sigma_noise[noise_idx] + mu_noise[noise_idx]; + noise_idx++; + } + if (noise_set & 0x04) { // Noise in z + msg.z += dist(gen) * sigma_noise[noise_idx] + mu_noise[noise_idx]; + } +} + +void addNoise(geometry_msgs::PointStamped &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen) +{ + if (noise_set == 0) { // no noise, leave unchanged + return; + } + addNoise(msg.point, mu_noise, sigma_noise, noise_set, dist, gen); +} + +////// Scalar ////// + +void addNoise(double &value, const mjtNum *mu_noise, const mjtNum *sigma_noise, const uint8_t &noise_set, + std::normal_distribution &dist, std::mt19937 &gen) +{ + if (noise_set == 0) { // no noise, leave unchanged + return; + } + + if (noise_set & 0x01) { // Noise in x + // shift and scale standard normal to desired distribution + value += dist(gen) * sigma_noise[0] + mu_noise[0]; + } +} + +void addNoise(mujoco_ros_msgs::ScalarStamped &msg, const mjtNum *mu_noise, const mjtNum *sigma_noise, + const uint8_t &noise_set, std::normal_distribution &dist, std::mt19937 &gen) +{ + if (noise_set == 0) { // no noise, leave unchanged + return; + } + addNoise(msg.value, mu_noise, sigma_noise, noise_set, dist, gen); +} + +} // namespace mujoco_ros::sensors diff --git a/mujoco_ros_sensors/test/mujoco_sensors_test.cpp b/mujoco_ros_sensors/test/mujoco_sensors_test.cpp index 3d265079..a31565d2 100644 --- a/mujoco_ros_sensors/test/mujoco_sensors_test.cpp +++ b/mujoco_ros_sensors/test/mujoco_sensors_test.cpp @@ -54,7 +54,8 @@ #include #include #include -#include + +#include using namespace mujoco_ros; namespace mju = ::mujoco::sample_util; @@ -278,7 +279,7 @@ void compare_vectors(std::vector a, std::vector b, double tol, b } } -TEST_F(TrainEnvFixture, Sensor3DOF) +TEST_F(TrainEnvFixture, Vector3Sensor) { int n_sensor; getSensorByName("vel_EE", m, n_sensor); @@ -316,39 +317,55 @@ TEST_F(TrainEnvFixture, Sensor3DOF) nh->serviceClient("/sensors/register_noise_models"); EXPECT_TRUE(client.call(srv)) << "Service call failed!"; - // Pause sim for synchronous message + // Pause sim for synchronous messages env_ptr->settings_.run.store(0); - msgPtr = ros::topic::waitForMessage("/vel_EE"); - msgPtr_GT = ros::topic::waitForMessage("/vel_EE_GT"); - // GT == Sensor reading compare_vectors({ msgPtr_GT->vector.x, msgPtr_GT->vector.y, msgPtr_GT->vector.z }, { d->sensordata[adr] / cutoff, d->sensordata[adr + 1] / cutoff, d->sensordata[adr + 2] / cutoff }, 0.0001, true); + // Create message buffers + std::deque value_buf; + std::deque gt_buf; + + // Create callbacks to fill message buffers + auto value_cb = [&value_buf](auto msg) { value_buf.push_back(msg); }; + + auto gt_cb = [>_buf](auto msg) { gt_buf.push_back(msg); }; + + auto value_sub = nh->subscribe("/vel_EE", 1, value_cb); + auto gt_sub = nh->subscribe("/vel_EE_GT", 1, gt_cb); + + env_ptr->settings_.env_steps_request.store(1000); + int n = 0; double means[6] = { 0.0 }; double deltas[6] = { 0.0 }; double variances[6] = { 0.0 }; - for (int i = 0; i <= 1000; i++) { - env_ptr->settings_.env_steps_request.store(1); - - while (env_ptr->settings_.env_steps_request.load() != 0) { + while (n < 1000) { + if (value_buf.empty() || gt_buf.empty()) { + if (env_ptr->settings_.env_steps_request.load() == 0) { + env_ptr->settings_.env_steps_request.store(1000 - n); // Request missing steps + } std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; } - msgPtr = ros::topic::waitForMessage("/vel_EE"); - msgPtr_GT = ros::topic::waitForMessage("/vel_EE_GT"); + + auto msg = value_buf.front(); + auto msg_GT = gt_buf.front(); + value_buf.pop_front(); + gt_buf.pop_front(); n += 1; - deltas[0] = (msgPtr->vector.x - msgPtr_GT->vector.x) - means[0]; - deltas[1] = (msgPtr->vector.y - msgPtr_GT->vector.y) - means[1]; - deltas[2] = (msgPtr->vector.z - msgPtr_GT->vector.z) - means[2]; + deltas[0] = (msg->vector.x - msg_GT->vector.x) - means[0]; + deltas[1] = (msg->vector.y - msg_GT->vector.y) - means[1]; + deltas[2] = (msg->vector.z - msg_GT->vector.z) - means[2]; - deltas[3] = msgPtr_GT->vector.x - means[3]; - deltas[4] = msgPtr_GT->vector.y - means[4]; - deltas[5] = msgPtr_GT->vector.z - means[5]; + deltas[3] = msg_GT->vector.x - means[3]; + deltas[4] = msg_GT->vector.y - means[4]; + deltas[5] = msg_GT->vector.z - means[5]; means[0] += deltas[0] / n; means[1] += deltas[1] / n; @@ -358,13 +375,13 @@ TEST_F(TrainEnvFixture, Sensor3DOF) means[4] += deltas[4] / n; means[5] += deltas[5] / n; - variances[0] += deltas[0] * ((msgPtr->vector.x - msgPtr_GT->vector.x) - means[0]); - variances[1] += deltas[1] * ((msgPtr->vector.y - msgPtr_GT->vector.y) - means[1]); - variances[2] += deltas[2] * ((msgPtr->vector.z - msgPtr_GT->vector.z) - means[2]); + variances[0] += deltas[0] * ((msg->vector.x - msg_GT->vector.x) - means[0]); + variances[1] += deltas[1] * ((msg->vector.y - msg_GT->vector.y) - means[1]); + variances[2] += deltas[2] * ((msg->vector.z - msg_GT->vector.z) - means[2]); - variances[3] += deltas[3] * (msgPtr_GT->vector.x - means[3]); - variances[4] += deltas[4] * (msgPtr_GT->vector.y - means[4]); - variances[5] += deltas[5] * (msgPtr_GT->vector.z - means[5]); + variances[3] += deltas[3] * (msg_GT->vector.x - means[3]); + variances[4] += deltas[4] * (msg_GT->vector.y - means[4]); + variances[5] += deltas[5] * (msg_GT->vector.z - means[5]); } variances[0] /= n - 1; @@ -391,7 +408,7 @@ TEST_F(TrainEnvFixture, Sensor3DOF) EXPECT_EQ(variances[5], 0); } -TEST_F(TrainEnvFixture, Framepos) +TEST_F(TrainEnvFixture, PointSensor) { int n_sensor; getSensorByName("immovable_pos", m, n_sensor); @@ -428,41 +445,55 @@ TEST_F(TrainEnvFixture, Framepos) ros::ServiceClient client = nh->serviceClient("/sensors/register_noise_models"); EXPECT_TRUE(client.call(srv)) << "Service call failed!"; - // sensor_plugin->registerNoiseModelsCB(srv.request, srv.response); // Pause sim for synchronous message env_ptr->settings_.run.store(0); - msgPtr = ros::topic::waitForMessage("/immovable_pos"); - msgPtr_GT = ros::topic::waitForMessage("/immovable_pos_GT"); - // GT == Sensor reading compare_vectors({ msgPtr_GT->point.x, msgPtr_GT->point.y, msgPtr_GT->point.z }, { d->sensordata[adr] / cutoff, d->sensordata[adr + 1] / cutoff, d->sensordata[adr + 2] / cutoff }, 0.0001, true); + // Create message buffers + std::deque value_buf; + std::deque gt_buf; + + // Create callbacks to fill message buffers + auto value_cb = [&value_buf](auto msg) { value_buf.push_back(msg); }; + auto gt_cb = [>_buf](auto msg) { gt_buf.push_back(msg); }; + + auto value_sub = nh->subscribe("/immovable_pos", 1, value_cb); + auto gt_sub = nh->subscribe("/immovable_pos_GT", 1, gt_cb); + + env_ptr->settings_.env_steps_request.store(1000); + int n = 0; double means[6] = { 0.0 }; double deltas[6] = { 0.0 }; double variances[6] = { 0.0 }; - for (int i = 0; i <= 1000; i++) { - env_ptr->settings_.env_steps_request.store(1); - - while (env_ptr->settings_.env_steps_request.load() != 0) { + while (n < 1000) { + if (value_buf.empty() || gt_buf.empty()) { + if (env_ptr->settings_.env_steps_request.load() == 0) { + env_ptr->settings_.env_steps_request.store(1000 - n); // Request missing steps + } std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; } - msgPtr = ros::topic::waitForMessage("/immovable_pos"); - msgPtr_GT = ros::topic::waitForMessage("/immovable_pos_GT"); + + auto msg = value_buf.front(); + auto msg_GT = gt_buf.front(); + value_buf.pop_front(); + gt_buf.pop_front(); n += 1; - deltas[0] = (msgPtr->point.x - msgPtr_GT->point.x) - means[0]; - deltas[1] = (msgPtr->point.y - msgPtr_GT->point.y) - means[1]; - deltas[2] = (msgPtr->point.z - msgPtr_GT->point.z) - means[2]; + deltas[0] = (msg->point.x - msg_GT->point.x) - means[0]; + deltas[1] = (msg->point.y - msg_GT->point.y) - means[1]; + deltas[2] = (msg->point.z - msg_GT->point.z) - means[2]; - deltas[3] = msgPtr_GT->point.x - means[3]; - deltas[4] = msgPtr_GT->point.y - means[4]; - deltas[5] = msgPtr_GT->point.z - means[5]; + deltas[3] = msg_GT->point.x - means[3]; + deltas[4] = msg_GT->point.y - means[4]; + deltas[5] = msg_GT->point.z - means[5]; means[0] += deltas[0] / n; means[1] += deltas[1] / n; @@ -472,13 +503,13 @@ TEST_F(TrainEnvFixture, Framepos) means[4] += deltas[4] / n; means[5] += deltas[5] / n; - variances[0] += deltas[0] * ((msgPtr->point.x - msgPtr_GT->point.x) - means[0]); - variances[1] += deltas[1] * ((msgPtr->point.y - msgPtr_GT->point.y) - means[1]); - variances[2] += deltas[2] * ((msgPtr->point.z - msgPtr_GT->point.z) - means[2]); + variances[0] += deltas[0] * ((msg->point.x - msg_GT->point.x) - means[0]); + variances[1] += deltas[1] * ((msg->point.y - msg_GT->point.y) - means[1]); + variances[2] += deltas[2] * ((msg->point.z - msg_GT->point.z) - means[2]); - variances[3] += deltas[3] * (msgPtr_GT->point.x - means[3]); - variances[4] += deltas[4] * (msgPtr_GT->point.y - means[4]); - variances[5] += deltas[5] * (msgPtr_GT->point.z - means[5]); + variances[3] += deltas[3] * (msg_GT->point.x - means[3]); + variances[4] += deltas[4] * (msg_GT->point.y - means[4]); + variances[5] += deltas[5] * (msg_GT->point.z - means[5]); } variances[0] /= n - 1; @@ -505,7 +536,7 @@ TEST_F(TrainEnvFixture, Framepos) EXPECT_EQ(variances[5], 0); } -TEST_F(TrainEnvFixture, scalar_stamped) +TEST_F(TrainEnvFixture, ScalarSensor) { int n_sensor; getSensorByName("vel_joint2", m, n_sensor); @@ -540,38 +571,53 @@ TEST_F(TrainEnvFixture, scalar_stamped) nh->serviceClient("/sensors/register_noise_models"); EXPECT_TRUE(client.call(srv)) << "Service call failed!"; + // GT == Sensor reading + EXPECT_NEAR(msgPtr_GT->value, d->sensordata[adr] / cutoff, 0.0001) << "GT differs from actual sensor value"; + // Pause sim for synchronous message env_ptr->settings_.run.store(0); - msgPtr_GT = ros::topic::waitForMessage("/vel_joint2_GT"); - msgPtr = ros::topic::waitForMessage("/vel_joint2"); + // Create message buffers + std::deque value_buf; + std::deque gt_buf; - // GT == Sensor reading - EXPECT_NEAR(msgPtr_GT->value, d->sensordata[adr] / cutoff, 0.0001) << "GT differs from actual sensor value"; + // Create callbacks to fill message buffers + auto value_cb = [&value_buf](auto msg) { value_buf.push_back(msg); }; + auto gt_cb = [>_buf](auto msg) { gt_buf.push_back(msg); }; + + auto value_sub = nh->subscribe("/vel_joint2", 1, value_cb); + auto gt_sub = nh->subscribe("/vel_joint2_GT", 1, gt_cb); + + env_ptr->settings_.env_steps_request.store(1000); int n = 0; double means[2] = { 0.0 }; double deltas[2] = { 0.0 }; double variances[2] = { 0.0 }; - for (int i = 0; i <= 1000; i++) { - env_ptr->settings_.env_steps_request.store(1); - - while (env_ptr->settings_.env_steps_request.load() != 0) { + while (n < 1000) { + if (value_buf.empty() || gt_buf.empty()) { + if (env_ptr->settings_.env_steps_request.load() == 0) { + env_ptr->settings_.env_steps_request.store(1000 - n); // Request missing steps + } std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; } - msgPtr_GT = ros::topic::waitForMessage("/vel_joint2_GT"); - msgPtr = ros::topic::waitForMessage("/vel_joint2"); + + auto msg = value_buf.front(); + auto msg_GT = gt_buf.front(); + value_buf.pop_front(); + gt_buf.pop_front(); n += 1; - deltas[0] = (msgPtr->value - msgPtr_GT->value) - means[0]; - deltas[1] = msgPtr_GT->value - means[1]; + deltas[0] = (msg->value - msg_GT->value) - means[0]; + deltas[1] = msg_GT->value - means[1]; means[0] += deltas[0] / n; means[1] += deltas[1] / n; - variances[0] += deltas[0] * ((msgPtr->value - msgPtr_GT->value) - means[0]); - variances[1] += deltas[1] * (msgPtr_GT->value - means[1]); + variances[0] += deltas[0] * ((msg->value - msg_GT->value) - means[0]); + variances[1] += deltas[1] * (msg_GT->value - means[1]); } variances[0] /= n - 1; @@ -584,7 +630,7 @@ TEST_F(TrainEnvFixture, scalar_stamped) EXPECT_EQ(variances[1], 0); } -TEST_F(TrainEnvFixture, quaternion) +TEST_F(TrainEnvFixture, QuaternionSensor) { int n_sensor; getSensorByName("immovable_quat", m, n_sensor); @@ -624,9 +670,6 @@ TEST_F(TrainEnvFixture, quaternion) // Pause sim for synchronous message env_ptr->settings_.run.store(0); - msgPtr = ros::topic::waitForMessage("/immovable_quat"); - msgPtr_GT = ros::topic::waitForMessage("/immovable_quat_GT"); - // GT == Sensor reading compare_vectors( { msgPtr_GT->quaternion.w, msgPtr_GT->quaternion.x, msgPtr_GT->quaternion.y, msgPtr_GT->quaternion.z }, @@ -634,6 +677,17 @@ TEST_F(TrainEnvFixture, quaternion) d->sensordata[adr + 3] / cutoff }, 0.0001, true); + // Create message buffers + std::deque value_buf; + std::deque gt_buf; + + // Create callbacks to fill message buffers + auto value_cb = [&value_buf](auto msg) { value_buf.push_back(msg); }; + auto gt_cb = [>_buf](auto msg) { gt_buf.push_back(msg); }; + + auto value_sub = nh->subscribe("/immovable_quat", 1, value_cb); + auto gt_sub = nh->subscribe("/immovable_quat_GT", 1, gt_cb); + int n = 0; double means[6] = { 0.0 }; double deltas[6] = { 0.0 }; @@ -643,19 +697,24 @@ TEST_F(TrainEnvFixture, quaternion) tf2::Matrix3x3 m; double r, p, y, R, P, Y; - for (int i = 0; i <= 1000; i++) { - env_ptr->settings_.env_steps_request.store(1); - - while (env_ptr->settings_.env_steps_request.load() != 0) { + while (n < 1000) { + if (value_buf.empty() || gt_buf.empty()) { + if (env_ptr->settings_.env_steps_request.load() == 0) { + env_ptr->settings_.env_steps_request.store(1000 - n); // Request missing steps + } std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; } - msgPtr = ros::topic::waitForMessage("/immovable_quat"); - msgPtr_GT = ros::topic::waitForMessage("/immovable_quat_GT"); + + auto msg = value_buf.front(); + auto msg_GT = gt_buf.front(); + value_buf.pop_front(); + gt_buf.pop_front(); n += 1; - tf2::fromMsg(msgPtr->quaternion, q); - tf2::fromMsg(msgPtr_GT->quaternion, q_GT); + tf2::fromMsg(msg->quaternion, q); + tf2::fromMsg(msg_GT->quaternion, q_GT); m = tf2::Matrix3x3(q); m.getRPY(r, p, y); From 86f691c5ec8991e6af7b1f2cf37e5f0c82fae648 Mon Sep 17 00:00:00 2001 From: David Leins Date: Thu, 21 Dec 2023 23:34:18 +0100 Subject: [PATCH 2/2] feat: compound sensors (Twist, IMU, Wrench, Pose) --- mujoco_ros/CHANGELOG.md | 4 + mujoco_ros_sensors/CMakeLists.txt | 1 + .../config/example_sensors.yaml | 22 ++ .../mujoco_sensor_handler_plugin.h | 2 +- .../include/mujoco_ros_sensors/sensors.h | 80 ++++- mujoco_ros_sensors/src/compound_sensors.cpp | 314 ++++++++++++++++++ .../src/mujoco_sensor_handler_plugin.cpp | 115 ++++++- mujoco_ros_sensors/test/sensors_world.xml | 23 +- 8 files changed, 538 insertions(+), 23 deletions(-) create mode 100644 mujoco_ros_sensors/src/compound_sensors.cpp diff --git a/mujoco_ros/CHANGELOG.md b/mujoco_ros/CHANGELOG.md index 1bce6ad1..f60ef46b 100644 --- a/mujoco_ros/CHANGELOG.md +++ b/mujoco_ros/CHANGELOG.md @@ -1,10 +1,14 @@ ## Unreleased +### Added +* Compound sensors (for now limited to IMU, Pose, Twist, and Wrench). + ### Fixed * Repaired SIGINT handler callback. `C-c` in the roslaunch terminal now shuts down the MuJoCo ROS node instead of escalating to SIGTERM. * Added actionlib to the list of mujoco_ros' dependencies. * Fixed bug where cutoff was not correctly applied to noisy sensors. +* Fixed missing FRAMEANGVEL serialization in sensors plugin. ### Changed * replaced `boost::shared_ptr` with `std::shared_ptr` or `std::unique_ptr` wherever possible (ROS 1 fast intra-process message-passing requires boost::shared_ptr). diff --git a/mujoco_ros_sensors/CMakeLists.txt b/mujoco_ros_sensors/CMakeLists.txt index 6f6ed3af..fd5c1485 100644 --- a/mujoco_ros_sensors/CMakeLists.txt +++ b/mujoco_ros_sensors/CMakeLists.txt @@ -47,6 +47,7 @@ include_directories( add_library(${PROJECT_NAME} src/mujoco_sensor_handler_plugin.cpp src/serialization.cpp + src/compound_sensors.cpp ) target_link_libraries(${PROJECT_NAME} ${catkin_LIBRARIES} diff --git a/mujoco_ros_sensors/config/example_sensors.yaml b/mujoco_ros_sensors/config/example_sensors.yaml index 47c3333b..cc03e862 100644 --- a/mujoco_ros_sensors/config/example_sensors.yaml +++ b/mujoco_ros_sensors/config/example_sensors.yaml @@ -1,2 +1,24 @@ MujocoPlugins: - type: mujoco_ros_sensors/MujocoRosSensorsPlugin + compound_sensors: + - name: test_imu + type: IMUSensor + sensors: + - linacc_EE + - gyro_EE + - quat_EE + - name: test_wrench + type: WrenchSensor + sensors: + - force_EE + - torque_EE + - name: test_twist + type: TwistSensor + sensors: + - linvel_EE + - angvel_EE + - name: test_pose + type: PoseSensor + sensors: + - pos_joint1 + - quat_joint1 diff --git a/mujoco_ros_sensors/include/mujoco_ros_sensors/mujoco_sensor_handler_plugin.h b/mujoco_ros_sensors/include/mujoco_ros_sensors/mujoco_sensor_handler_plugin.h index dd187aed..e19406bd 100644 --- a/mujoco_ros_sensors/include/mujoco_ros_sensors/mujoco_sensor_handler_plugin.h +++ b/mujoco_ros_sensors/include/mujoco_ros_sensors/mujoco_sensor_handler_plugin.h @@ -71,7 +71,7 @@ class MujocoRosSensorsPlugin : public mujoco_ros::MujocoPlugin std::normal_distribution noise_dist_; std::map sensor_map_; - // std::vector enabled_sensors_; + std::map enabled_sensors_; ros::ServiceServer register_noise_model_server_; diff --git a/mujoco_ros_sensors/include/mujoco_ros_sensors/sensors.h b/mujoco_ros_sensors/include/mujoco_ros_sensors/sensors.h index 9bfeb41c..2197a732 100644 --- a/mujoco_ros_sensors/include/mujoco_ros_sensors/sensors.h +++ b/mujoco_ros_sensors/include/mujoco_ros_sensors/sensors.h @@ -41,6 +41,9 @@ #include #include #include +#include +#include +#include #include #include @@ -80,6 +83,12 @@ class RosSensorInterfaceBase virtual void publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, std::mt19937 &gen) = 0; + void unregisterPublishers(bool eval_mode); + + int getType() const { return type_; } + + std::string getFrameId() const { return frame_id_; } + protected: std::string frame_id_; std::string sensor_name_; @@ -92,6 +101,7 @@ class RosSensorInterfaceBase mjtNum *sigma_noise_; uint dofs_; + int type_ = -1; uint8_t noise_set_ = 0; // 0 for unset, otherwise binary code for set dimensions }; @@ -100,8 +110,8 @@ template class RosSensorInterface : public RosSensorInterfaceBase { public: - RosSensorInterface(const std::string &frame_id, const std::string &sensor_name, const int dofs, const int adr, - const mjtNum cutoff, bool eval_mode, ros::NodeHandle *sensors_nh); + RosSensorInterface(const std::string &frame_id, const std::string &sensor_name, const int dofs, const int type, + const int adr, const mjtNum cutoff, bool eval_mode, ros::NodeHandle *sensors_nh); void publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, std::mt19937 &gen) override; @@ -113,6 +123,67 @@ class RosSensorInterface : public RosSensorInterfaceBase mjtNum cutoff_; }; +class WrenchSensor : public RosSensorInterfaceBase +{ +public: + WrenchSensor(const std::string &sensor_name, std::vector &wrench_sensors, bool eval_mode, + ros::NodeHandle *sensors_nh); + + void publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) override; + +private: + RosSensorInterface *force_ = nullptr; + RosSensorInterface *torque_ = nullptr; +}; + +class TwistSensor : public RosSensorInterfaceBase +{ +public: + TwistSensor(const std::string &sensor_name, std::vector &twist_sensors, bool eval_mode, + ros::NodeHandle *sensors_nh); + + void publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) override; + +private: + RosSensorInterface *linear_velocity_ = nullptr; + RosSensorInterface *angular_velocity_ = nullptr; +}; + +class PoseSensor : public RosSensorInterfaceBase +{ +public: + PoseSensor(const std::string &sensor_name, std::vector &pose_sensors, bool eval_mode, + ros::NodeHandle *sensors_nh); + + void publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) override; + +private: + RosSensorInterface *position_ = nullptr; + RosSensorInterface *orientation_ = nullptr; +}; + +class IMUSensor : public RosSensorInterfaceBase +{ +public: + IMUSensor(const std::string &sensor_name, std::vector &imu_sensors, bool eval_mode, + ros::NodeHandle *sensors_nh); + + void publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) override; + +private: + RosSensorInterface *linear_acceleration_ = nullptr; + RosSensorInterface *angular_acceleration_ = nullptr; + RosSensorInterface *orientation_ = nullptr; + + mjtNum lin_cov_[9]; + mjtNum ang_cov_[9]; + mjtNum ori_cov_[9]; +}; + // template impl needs to be in header template void RosSensorInterface::publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, @@ -128,7 +199,7 @@ void RosSensorInterface::publish(const bool publish_gt, const mjData *data, s template RosSensorInterface::RosSensorInterface(const std::string &frame_id, const std::string &sensor_name, const int dofs, - const int adr, const mjtNum cutoff, bool eval_mode, + const int type, const int adr, const mjtNum cutoff, bool eval_mode, ros::NodeHandle *sensors_nh) : RosSensorInterfaceBase(frame_id, sensor_name, dofs), adr_(adr), cutoff_(cutoff) { @@ -136,6 +207,7 @@ RosSensorInterface::RosSensorInterface(const std::string &frame_id, const std if (!eval_mode) { gt_pub_ = sensors_nh->advertise(sensor_name + "_GT", 1); } + type_ = type; }; template @@ -152,4 +224,6 @@ void RosSensorInterface::addNoise(T &msg, std::normal_distribution &d mujoco_ros::sensors::addNoise(msg, mu_noise_, sigma_noise_, noise_set_, dist, gen); } +extern const char *SENSOR_STRING[35]; + } // namespace mujoco_ros::sensors diff --git a/mujoco_ros_sensors/src/compound_sensors.cpp b/mujoco_ros_sensors/src/compound_sensors.cpp new file mode 100644 index 00000000..8dabe21d --- /dev/null +++ b/mujoco_ros_sensors/src/compound_sensors.cpp @@ -0,0 +1,314 @@ +/** + * Software License Agreement (BSD 3-Clause License) + * + * Copyright (c) 2023, Bielefeld University + * 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 Bielefeld University 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. + */ + +/* Authors: David P. Leins */ + +#include +#include + +#include + +namespace mujoco_ros::sensors { + +void RosSensorInterfaceBase::unregisterPublishers(bool eval_mode) +{ + value_pub_.shutdown(); + if (!eval_mode) { + gt_pub_.shutdown(); + } +} + +/////////// Wrench /////////// + +WrenchSensor::WrenchSensor(const std::string &sensor_name, std::vector &wrench_sensors, + bool eval_mode, ros::NodeHandle *sensors_nh) + : RosSensorInterfaceBase("world", sensor_name, 0) +{ + value_pub_ = sensors_nh->advertise(sensor_name, 1); + if (!eval_mode) { + gt_pub_ = sensors_nh->advertise(sensor_name + "_GT", 1); + } + + frame_id_ = wrench_sensors[0]->getFrameId(); + for (const auto &sensor : wrench_sensors) { + if (frame_id_ != sensor->getFrameId()) { + ROS_ERROR_STREAM("Wrench sensor " << sensor_name << " has sensors with different frame ids. Expected " + << frame_id_ << ", but got " << sensor->getFrameId() << " instead"); + throw std::runtime_error("Invalid wrench sensor configuration."); + } + if (sensor->getType() == mjtSensor::mjSENS_FORCE) { + if (force_ != nullptr) { + ROS_ERROR_STREAM("Wrench sensor " << sensor_name << " has multiple force sensors."); + throw std::runtime_error("Invalid wrench sensor configuration."); + } + force_ = dynamic_cast *>(sensor); + } else if (sensor->getType() == mjtSensor::mjSENS_TORQUE) { + if (torque_ != nullptr) { + ROS_ERROR_STREAM("Wrench sensor " << sensor_name << " has multiple torque sensors."); + throw std::runtime_error("Invalid wrench sensor configuration."); + } + torque_ = dynamic_cast *>(sensor); + } else { + ROS_ERROR_STREAM("Wrench sensor " << sensor_name << " has an invalid sensor type (" + << SENSOR_STRING[sensor->getType()] << ")."); + throw std::runtime_error("Invalid wrench sensor configuration."); + } + } +} + +void WrenchSensor::publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) +{ + geometry_msgs::WrenchStamped msg; + msg.header.stamp = ros::Time::now(); + msg.header.frame_id = frame_id_; + + auto f = force_->serialize(data); + auto t = torque_->serialize(data); + + msg.wrench.force = f.vector; + msg.wrench.torque = t.vector; + + if (publish_gt) { + gt_pub_.publish(msg); + } + + force_->addNoise(f, dist, gen); + torque_->addNoise(t, dist, gen); + + value_pub_.publish(msg); +} + +/////////// Twist /////////// + +TwistSensor::TwistSensor(const std::string &sensor_name, std::vector &twist_sensors, + bool eval_mode, ros::NodeHandle *sensors_nh) + : RosSensorInterfaceBase("world", sensor_name, 0) +{ + value_pub_ = sensors_nh->advertise(sensor_name, 1); + if (!eval_mode) { + gt_pub_ = sensors_nh->advertise(sensor_name + "_GT", 1); + } + + frame_id_ = twist_sensors[0]->getFrameId(); + for (const auto &sensor : twist_sensors) { + if (frame_id_ != sensor->getFrameId()) { + ROS_ERROR_STREAM("Twist sensor " << sensor_name << " has sensors with different frame ids. Expected " + << frame_id_ << ", but got " << sensor->getFrameId() << " instead"); + throw std::runtime_error("Invalid twist sensor configuration."); + } + if (sensor->getType() == mjtSensor::mjSENS_FRAMELINVEL) { + if (linear_velocity_ != nullptr) { + ROS_ERROR_STREAM("Twist sensor " << sensor_name << " has multiple linear velocity sensors."); + throw std::runtime_error("Invalid twist sensor configuration."); + } + linear_velocity_ = dynamic_cast *>(sensor); + } else if (sensor->getType() == mjtSensor::mjSENS_FRAMEANGVEL) { + if (angular_velocity_ != nullptr) { + ROS_ERROR_STREAM("Twist sensor " << sensor_name << " has multiple angular velocity sensors."); + throw std::runtime_error("Invalid twist sensor configuration."); + } + angular_velocity_ = dynamic_cast *>(sensor); + } else { + ROS_ERROR_STREAM("Twist sensor " << sensor_name << " has an invalid sensor type (" + << SENSOR_STRING[sensor->getType()] << ")."); + throw std::runtime_error("Invalid twist sensor configuration."); + } + } +} + +void TwistSensor::publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) +{ + geometry_msgs::TwistStamped msg; + msg.header.stamp = ros::Time::now(); + msg.header.frame_id = frame_id_; + + auto lv = linear_velocity_->serialize(data); + auto av = angular_velocity_->serialize(data); + + msg.twist.linear = lv.vector; + msg.twist.angular = av.vector; + + if (publish_gt) { + gt_pub_.publish(msg); + } + + linear_velocity_->addNoise(lv, dist, gen); + angular_velocity_->addNoise(av, dist, gen); + + value_pub_.publish(msg); +} + +/////////// Pose /////////// + +PoseSensor::PoseSensor(const std::string &sensor_name, std::vector &pose_sensors, + bool eval_mode, ros::NodeHandle *sensors_nh) + : RosSensorInterfaceBase("world", sensor_name, 0) +{ + value_pub_ = sensors_nh->advertise(sensor_name, 1); + if (!eval_mode) { + gt_pub_ = sensors_nh->advertise(sensor_name + "_GT", 1); + } + + frame_id_ = pose_sensors[0]->getFrameId(); + for (const auto &sensor : pose_sensors) { + if (frame_id_ != sensor->getFrameId()) { + ROS_ERROR_STREAM("Pose sensor " << sensor_name << " has sensors with different frame ids. Expected " + << frame_id_ << ", but got " << sensor->getFrameId() << " instead"); + throw std::runtime_error("Invalid pose sensor configuration."); + } + if (sensor->getType() == mjtSensor::mjSENS_FRAMEPOS) { + if (position_ != nullptr) { + ROS_ERROR_STREAM("Pose sensor " << sensor_name << " has multiple position sensors."); + throw std::runtime_error("Invalid pose sensor configuration."); + } + position_ = dynamic_cast *>(sensor); + } else if (sensor->getType() == mjtSensor::mjSENS_FRAMEQUAT) { + if (orientation_ != nullptr) { + ROS_ERROR_STREAM("Pose sensor " << sensor_name << " has multiple orientation sensors."); + throw std::runtime_error("Invalid pose sensor configuration."); + } + orientation_ = dynamic_cast *>(sensor); + } else { + ROS_ERROR_STREAM("Pose sensor " << sensor_name << " has an invalid sensor type (" + << SENSOR_STRING[sensor->getType()] << ")."); + throw std::runtime_error("Invalid pose sensor configuration."); + } + } +} + +void PoseSensor::publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) +{ + geometry_msgs::PoseStamped msg; + msg.header.stamp = ros::Time::now(); + msg.header.frame_id = frame_id_; + + auto p = position_->serialize(data); + auto o = orientation_->serialize(data); + + msg.pose.position = p.point; + msg.pose.orientation = o.quaternion; + + if (publish_gt) { + gt_pub_.publish(msg); + } + + position_->addNoise(p, dist, gen); + orientation_->addNoise(o, dist, gen); + + value_pub_.publish(msg); +} + +/////////// IMU /////////// + +IMUSensor::IMUSensor(const std::string &sensor_name, std::vector &imu_sensors, bool eval_mode, + ros::NodeHandle *sensors_nh) + : RosSensorInterfaceBase("world", sensor_name, 0) +{ + value_pub_ = sensors_nh->advertise(sensor_name, 1); + if (!eval_mode) { + gt_pub_ = sensors_nh->advertise(sensor_name + "_GT", 1); + } + + if (imu_sensors.size() != 3) { + ROS_ERROR_STREAM("IMU sensor " << sensor_name << " has " << imu_sensors.size() + << " sensors, but exactly 3 are required."); + throw std::runtime_error("Invalid IMU sensor configuration."); + } + + frame_id_ = imu_sensors[0]->getFrameId(); + for (const auto &sensor : imu_sensors) { + if (frame_id_ != sensor->getFrameId()) { + ROS_ERROR_STREAM("IMU sensor " << sensor_name << " has sensors with different frame ids. Expected " + << frame_id_ << ", but got " << sensor->getFrameId() << " instead"); + throw std::runtime_error("Invalid IMU sensor configuration."); + } + if (sensor->getType() == mjtSensor::mjSENS_FRAMELINACC || sensor->getType() == mjtSensor::mjSENS_ACCELEROMETER) { + if (linear_acceleration_ != nullptr) { + ROS_ERROR_STREAM("IMU sensor " << sensor_name << " has multiple linear acceleration sensors."); + throw std::runtime_error("Invalid IMU sensor configuration."); + } + linear_acceleration_ = dynamic_cast *>(sensor); + } else if (sensor->getType() == mjtSensor::mjSENS_FRAMEANGACC || sensor->getType() == mjtSensor::mjSENS_GYRO || + sensor->getType() == mjtSensor::mjSENS_BALLANGVEL) { + if (angular_acceleration_ != nullptr) { + ROS_ERROR_STREAM("IMU sensor " << sensor_name << " has multiple angular acceleration sensors."); + throw std::runtime_error("Invalid IMU sensor configuration."); + } + angular_acceleration_ = dynamic_cast *>(sensor); + } else if (sensor->getType() == mjtSensor::mjSENS_FRAMEQUAT || sensor->getType() == mjtSensor::mjSENS_BALLQUAT) { + if (orientation_ != nullptr) { + ROS_ERROR_STREAM("IMU sensor " << sensor_name << " has multiple orientation sensors."); + throw std::runtime_error("Invalid IMU sensor configuration."); + } + orientation_ = dynamic_cast *>(sensor); + } else { + ROS_ERROR_STREAM("IMU sensor " << sensor_name << " has an invalid sensor type (" + << SENSOR_STRING[sensor->getType()] << ")."); + throw std::runtime_error("Invalid IMU sensor configuration."); + } + } +} + +void IMUSensor::publish(const bool publish_gt, const mjData *data, std::normal_distribution &dist, + std::mt19937 &gen) +{ + sensor_msgs::Imu msg; + msg.header.stamp = ros::Time::now(); + msg.header.frame_id = frame_id_; + + auto la = linear_acceleration_->serialize(data); + auto aa = angular_acceleration_->serialize(data); + auto o = orientation_->serialize(data); + + msg.linear_acceleration = la.vector; + msg.angular_velocity = aa.vector; + msg.orientation = o.quaternion; + + if (publish_gt) { + gt_pub_.publish(msg); + } + + linear_acceleration_->addNoise(la, dist, gen); + angular_acceleration_->addNoise(aa, dist, gen); + orientation_->addNoise(o, dist, gen); + + value_pub_.publish(msg); +} + +} // namespace mujoco_ros::sensors diff --git a/mujoco_ros_sensors/src/mujoco_sensor_handler_plugin.cpp b/mujoco_ros_sensors/src/mujoco_sensor_handler_plugin.cpp index e30664aa..2418bd97 100644 --- a/mujoco_ros_sensors/src/mujoco_sensor_handler_plugin.cpp +++ b/mujoco_ros_sensors/src/mujoco_sensor_handler_plugin.cpp @@ -195,8 +195,8 @@ void MujocoRosSensorsPlugin::lastStageCallback(const mjModel *model, mjData *dat { std::string sensor_name; - std::map::iterator pos = sensor_map_.begin(); - while (pos != sensor_map_.end()) { + std::map::iterator pos = enabled_sensors_.begin(); + while (pos != enabled_sensors_.end()) { RosSensorInterfaceBase *sensor = pos->second; sensor->publish(!env_ptr_->settings_.eval_mode, data, noise_dist_, rand_generator_); pos++; @@ -223,8 +223,8 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) continue; } - bool global_frame = false; - frame_id = "world"; + bool matched = false; + frame_id = "world"; switch (type) { // Global or relative frame 3 DoF sensors { @@ -232,6 +232,7 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) case mjSENS_FRAMEYAXIS: case mjSENS_FRAMEZAXIS: case mjSENS_FRAMELINVEL: + case mjSENS_FRAMEANGVEL: case mjSENS_FRAMELINACC: case mjSENS_FRAMEANGACC: int refid = model->sensor_refid[n]; @@ -247,7 +248,8 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) << frame_id); } sensor_map_[sensor_name] = new RosSensorInterface( - frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + frame_id, sensor_name, 3, type, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + matched = true; break; } // Global frame 3 DoF sensors @@ -255,8 +257,8 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) case mjSENS_SUBTREELINVEL: case mjSENS_SUBTREEANGMOM: sensor_map_[sensor_name] = new RosSensorInterface( - frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); - global_frame = true; + frame_id, sensor_name, 3, type, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + matched = true; break; // Global or local frame position sensors (1 DoF) { @@ -274,8 +276,8 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) << frame_id); } sensor_map_[sensor_name] = new RosSensorInterface( - frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); - global_frame = true; + frame_id, sensor_name, 3, type, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + matched = true; break; } @@ -283,14 +285,13 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) case mjSENS_BALLQUAT: case mjSENS_FRAMEQUAT: sensor_map_[sensor_name] = new RosSensorInterface( - frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); - global_frame = true; + frame_id, sensor_name, 3, type, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + matched = true; break; } - // If global_frame was explicitly set to true or frame_id was overridden by relative frame - // the sensor is already registered and we can skip checking the remaining types - if (global_frame || frame_id != "world") { + // If the sensor is already registered and we can skip checking the remaining types + if (matched) { ROS_DEBUG_STREAM_NAMED("sensors", "Setting up sensor " << sensor_name << " on site " << site << " (frame_id: " << frame_id << ") of type " << SENSOR_STRING[type]); continue; @@ -311,7 +312,7 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) case mjSENS_MAGNETOMETER: case mjSENS_BALLANGVEL: sensor_map_[sensor_name] = new RosSensorInterface( - frame_id, sensor_name, 3, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + frame_id, sensor_name, 3, type, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); break; // Scalar type sensors @@ -331,7 +332,7 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) case mjSENS_TENDONLIMITVEL: case mjSENS_TENDONLIMITFRC: sensor_map_[sensor_name] = new RosSensorInterface( - frame_id, sensor_name, 1, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); + frame_id, sensor_name, 1, type, adr, cutoff, env_ptr_->settings_.eval_mode, &sensors_nh_); break; default: @@ -340,6 +341,88 @@ void MujocoRosSensorsPlugin::initSensors(const mjModel *model, mjData *data) break; } } + + // Copy all found sensors into enabled_sensors_ map + enabled_sensors_ = sensor_map_; + + if (!rosparam_config_.hasMember("compound_sensors")) { + ROS_DEBUG_NAMED("sensors", "No compound sensors configured"); + return; + } + ROS_DEBUG_STREAM("Processing compound sensors (n = " << rosparam_config_["compound_sensors"].size() << ")"); + for (uint i = 0; i < rosparam_config_["compound_sensors"].size(); i++) { + const auto compound_sensor = rosparam_config_["compound_sensors"][i]; + + // Check if definition contains name, type, and sensors, otherwise skip + if (!compound_sensor.hasMember("name")) { + ROS_ERROR_STREAM_NAMED("sensors", "Compound sensor definition " << i << " has no name. Skipping"); + continue; + } + std::string compound_sensor_name = static_cast(compound_sensor["name"]); + if (!compound_sensor.hasMember("type") || + (compound_sensor["type"] != "IMUSensor" && compound_sensor["type"] != "WrenchSensor" && + compound_sensor["type"] != "TwistSensor" && compound_sensor["type"] != "PoseSensor")) { + ROS_ERROR_STREAM_NAMED("sensors", + "Compound sensor '" << compound_sensor_name << "' has no valid type. Skipping"); + continue; + } + if (!compound_sensor.hasMember("sensors")) { + ROS_ERROR_STREAM_NAMED("sensors", "Compound sensor '" << compound_sensor_name << "' has no sensors. Skipping"); + continue; + } + + ROS_DEBUG_STREAM_NAMED("sensors", "Setting up compound sensor " << compound_sensor_name); + + std::vector compound_sensor_interfaces; + for (uint j = 0; j < compound_sensor["sensors"].size(); j++) { + const auto &sub_sensor = compound_sensor["sensors"][j]; + ROS_DEBUG_STREAM("Processing sub sensor " << sub_sensor << " of compound sensor " << compound_sensor_name); + const std::map::const_iterator &pos = sensor_map_.find(sub_sensor); + if (pos == sensor_map_.end()) { + ROS_ERROR_STREAM_NAMED("sensors", "No sensor with name '" << sub_sensor + << "' was registered on init. Skipping " + "compound sensor"); + continue; + } + // Remove sensor used in compound sensor from enabled_sensors_ map + compound_sensor_interfaces.emplace_back(pos->second); + enabled_sensors_.erase(sub_sensor); + // Unregister publishers for sensor + pos->second->unregisterPublishers(env_ptr_->settings_.eval_mode); + ROS_DEBUG_STREAM("Unregistered publishers for sensor " << sub_sensor); + } + + // TODO(dleins): Use factory pattern for better maintainability or is that overkill? + RosSensorInterfaceBase *compound_sensor_inst = nullptr; + try { + if (compound_sensor["type"] == "IMUSensor") { + compound_sensor_inst = new IMUSensor(compound_sensor_name, compound_sensor_interfaces, + env_ptr_->settings_.eval_mode, &sensors_nh_); + } else if (compound_sensor["type"] == "WrenchSensor") { + compound_sensor_inst = new WrenchSensor(compound_sensor_name, compound_sensor_interfaces, + env_ptr_->settings_.eval_mode, &sensors_nh_); + } else if (compound_sensor["type"] == "TwistSensor") { + compound_sensor_inst = new TwistSensor(compound_sensor_name, compound_sensor_interfaces, + env_ptr_->settings_.eval_mode, &sensors_nh_); + } else if (compound_sensor["type"] == "PoseSensor") { + compound_sensor_inst = new PoseSensor(compound_sensor_name, compound_sensor_interfaces, + env_ptr_->settings_.eval_mode, &sensors_nh_); + } + } catch (const std::runtime_error &e) { + ROS_ERROR_STREAM_NAMED("sensors", + "Error while setting up compound sensor " << compound_sensor_name << ": " << e.what()); + continue; + } + + // TODO(dleins): is further composition sensible? Alphabetical sorting of yaml could cause problems + // Add compound sensor to enabled sensors and register for deletion (and further composition ?) + enabled_sensors_[compound_sensor_name] = compound_sensor_inst; + sensor_map_[compound_sensor_name] = compound_sensor_inst; + ROS_DEBUG_STREAM_NAMED("sensors", "Added compound sensor " << compound_sensor_name << " to enabled sensors"); + } + + ROS_DEBUG_STREAM_NAMED("sensors", "Enabled sensors: " << enabled_sensors_.size()); + ROS_DEBUG_STREAM_NAMED("sensors", "All sensors: " << sensor_map_.size()); } // Nothing to do on reset diff --git a/mujoco_ros_sensors/test/sensors_world.xml b/mujoco_ros_sensors/test/sensors_world.xml index bbc674e5..c421eb0e 100644 --- a/mujoco_ros_sensors/test/sensors_world.xml +++ b/mujoco_ros_sensors/test/sensors_world.xml @@ -21,11 +21,12 @@ - + + + - @@ -43,7 +44,23 @@ - + + + + + + + + + + + + + + + + +