diff --git a/source/RobotAPI/components/ObjectPoseObserver/CMakeLists.txt b/source/RobotAPI/components/ObjectPoseObserver/CMakeLists.txt index 867170f06d57371fee6e25cda2f28ea4dffdda79..f369dd0247620277b719e59891bbff9c88d13e6e 100644 --- a/source/RobotAPI/components/ObjectPoseObserver/CMakeLists.txt +++ b/source/RobotAPI/components/ObjectPoseObserver/CMakeLists.txt @@ -5,6 +5,7 @@ set(COMPONENT_LIBS ArmarXCore ArmarXCoreInterfaces ArmarXGuiComponentPlugins RobotAPIArmarXObjects RobotAPIComponentPlugins + ArViz ${PROJECT_NAME}Interfaces ) diff --git a/source/RobotAPI/components/ObjectPoseObserver/ObjectPose.cpp b/source/RobotAPI/components/ObjectPoseObserver/ObjectPose.cpp index ae8531edacea24b495fac67c39fd50f51a927835..af808d1de3634821a761e2ae4bb3183aa1e2bcfd 100644 --- a/source/RobotAPI/components/ObjectPoseObserver/ObjectPose.cpp +++ b/source/RobotAPI/components/ObjectPoseObserver/ObjectPose.cpp @@ -113,6 +113,25 @@ namespace armarx::objpose objpose::fromIce(provided.localOOBB, localOOBB); } + + std::optional<simox::OrientedBoxf> ObjectPose::oobbRobot() const + { + if (localOOBB) + { + return localOOBB->transformed(objectPoseRobot); + } + return std::nullopt; + } + + std::optional<simox::OrientedBoxf> ObjectPose::oobbGlobal() const + { + if (localOOBB) + { + return localOOBB->transformed(objectPoseGlobal); + } + return std::nullopt; + } + } namespace armarx diff --git a/source/RobotAPI/components/ObjectPoseObserver/ObjectPose.h b/source/RobotAPI/components/ObjectPoseObserver/ObjectPose.h index 78cbfbfc6bbe46bd0dc8c24995dfc33973f68e3b..d00e548b82bc9bdd72a46aeb997a6eb297e89ba4 100644 --- a/source/RobotAPI/components/ObjectPoseObserver/ObjectPose.h +++ b/source/RobotAPI/components/ObjectPoseObserver/ObjectPose.h @@ -54,7 +54,15 @@ namespace armarx::objpose IceUtil::Time timestamp = IceUtil::Time::microSeconds(-1); /// Object bounding box in object's local coordinate frame. + /// @see oobbRobot(), oobbGlobal() std::optional<simox::OrientedBoxf> localOOBB; + + + /// Get the OOBB in the robot frame (according to `objectPoseRobot`). + std::optional<simox::OrientedBoxf> oobbRobot() const; + /// Get the OOBB in the global frame (according to `objectPoseGlobal`). + std::optional<simox::OrientedBoxf> oobbGlobal() const; + }; using ObjectPoseSeq = std::vector<ObjectPose>; diff --git a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp index 8f24af53ed2dca19b6c8b273da70842bbc26dc48..fda9571084893b61eef4cdde94a599720bc3fd34 100644 --- a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp +++ b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp @@ -53,6 +53,9 @@ namespace armarx defs->optional(visu.enabled, "visu.enabled", "Enable or disable visualization of objects."); defs->optional(visu.inGlobalFrame, "visu.inGlobalFrame", "If true, show global poses. If false, show poses in robot frame."); defs->optional(visu.alpha, "visu.alpha", "Alpha of objects (1 = solid, 0 = transparent)."); + defs->optional(visu.oobbs, "visu.oobbs", "Enable showing oriented bounding boxes."); + defs->optional(visu.objectFrames, "visu.objectFrames", "Enable showing object frames."); + defs->optional(visu.objectFramesScale, "visu.objectFramesScale", "Scaling of object frames."); defs->optional(calibration.robotNode, "calibration.robotNode", "Robot node which can be calibrated"); defs->optional(calibration.offset, "calibration.offset", "Offset for the node to be calibrated"); @@ -100,6 +103,14 @@ namespace armarx tab.visuAlpha.setRange(0, 1.0); tab.visuAlpha.setValue(visu.alpha); tab.visuOOBBs.setValue(visu.oobbs); + tab.visuObjectFrames.setValue(visu.objectFrames); + { + float max = 10000; + tab.visuObjectFramesScale.setRange(0, max); + tab.visuObjectFramesScale.setDecimals(2); + tab.visuObjectFramesScale.setSteps(int(10 * max)); + tab.visuObjectFramesScale.setValue(visu.objectFramesScale); + } GroupBox visuGroup; { @@ -109,10 +120,13 @@ namespace armarx row++; grid.add(Label("Global Frame"), {row, 0}).add(tab.visuInGlobalFrame, {row, 1}); row++; - grid.add(Label("Alpha"), {row, 0}).add(tab.visuAlpha, {row, 1}); + grid.add(Label("Alpha"), {row, 0}).add(tab.visuAlpha, {row, 1}, {1, 3}); row++; grid.add(Label("OOBB"), {row, 0}).add(tab.visuOOBBs, {row, 1}); row++; + grid.add(Label("Object Frames"), {row, 0}).add(tab.visuObjectFrames, {row, 1}); + grid.add(Label("Scale:"), {row, 2}).add(tab.visuObjectFramesScale, {row, 3}); + row++; visuGroup.setLabel("Visualization"); visuGroup.addChild(grid); @@ -131,6 +145,8 @@ namespace armarx visu.inGlobalFrame = tab.visuInGlobalFrame.getValue(); visu.alpha = tab.visuAlpha.getValue(); visu.oobbs = tab.visuOOBBs.getValue(); + visu.objectFrames = tab.visuObjectFrames.getValue(); + visu.objectFramesScale = tab.visuObjectFramesScale.getValue(); } @@ -372,26 +388,23 @@ namespace armarx const armarx::ObjectID id = objectPose.objectID; std::string key = id.str(); - std::optional<ObjectInfo> objectInfo = objectFinder.findObject(id); - Eigen::Matrix4f pose = visu.inGlobalFrame ? objectPose.objectPoseGlobal : objectPose.objectPoseRobot; - - if (objectInfo) { - viz::Object object = viz::Object(key) - .file(objectInfo->package(), objectInfo->simoxXML().relativePath) - .pose(pose); + viz::Object object = viz::Object(key).pose(pose); + if (std::optional<ObjectInfo> objectInfo = objectFinder.findObject(id)) + { + object.file(objectInfo->package(), objectInfo->simoxXML().relativePath); + } + else + { + object.fileByObjectFinder(id); + } if (visu.alpha < 1) { object.overrideColor(simox::Color::white().with_alpha(visu.alpha)); } - layer.add(object); } - else - { - ARMARX_WARNING << "Cannot visualize object '" << key << "'."; - } if (visu.oobbs && objectPose.localOOBB) { @@ -400,6 +413,10 @@ namespace armarx .pose(pose * simox::math::pose(oobb.center(), oobb.rotation())) .size(oobb.dimensions()).color(simox::Color::lime(255, 64))); } + if (visu.objectFrames) + { + layer.add(viz::Pose(key + " Pose").pose(pose).scale(visu.objectFramesScale)); + } } arviz.commit(layer); diff --git a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h index c6271e22e5edcc83b1cb9d532da6405a31920d30..1e7197d3b068ad58fad6a01912b6f9e7d6b51a6f 100644 --- a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h +++ b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h @@ -152,6 +152,8 @@ namespace armarx bool inGlobalFrame = true; float alpha = 1.0; bool oobbs = false; + bool objectFrames = false; + float objectFramesScale = 1.0; }; Visu visu; @@ -169,6 +171,8 @@ namespace armarx RemoteGui::Client::CheckBox visuInGlobalFrame; RemoteGui::Client::FloatSlider visuAlpha; RemoteGui::Client::CheckBox visuOOBBs; + RemoteGui::Client::CheckBox visuObjectFrames; + RemoteGui::Client::FloatSpinBox visuObjectFramesScale; }; RemoteGuiTab tab; }; diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectID.cpp b/source/RobotAPI/libraries/ArmarXObjects/ObjectID.cpp index 796c658992a1db36d884ff3f510fa994dc5bc80c..b756dc4aaac803245000b0deac6524ec7615a7bf 100644 --- a/source/RobotAPI/libraries/ArmarXObjects/ObjectID.cpp +++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectID.cpp @@ -25,10 +25,14 @@ namespace armarx << ", but got: '" << nameOrID << "' (too many '/')."; _dataset = split[0]; _className = split[1]; + if (split.size() == 3) + { + _instanceName = split[2]; + } } else { - // dataset is left empty. + // Dataset, instanceName are left empty. _className = nameOrID; } } @@ -43,6 +47,11 @@ namespace armarx return _str; } + bool ObjectID::equalClass(const ObjectID& rhs) const + { + return _className == rhs._className && _dataset == rhs._dataset; + } + bool ObjectID::operator==(const ObjectID& rhs) const { return _className == rhs._className @@ -52,14 +61,16 @@ namespace armarx bool ObjectID::operator<(const ObjectID& rhs) const { - if (_dataset != rhs._dataset) + int c = _dataset.compare(rhs._dataset); + if (c != 0) { - return _dataset < rhs._dataset; + return c < 0; } // equal dataset - if (_className != rhs._className) + c = _className.compare(rhs._className); + if (c != 0) { - return _className < rhs._className; + return c < 0; } // equal class name return _instanceName < rhs._instanceName; diff --git a/source/RobotAPI/libraries/ArmarXObjects/ObjectID.h b/source/RobotAPI/libraries/ArmarXObjects/ObjectID.h index 2b953ea4c2d126caaa0b41db612704e0649d284f..0d3bfb128d35ce5fa879c7abbbb24ff34404e7b7 100644 --- a/source/RobotAPI/libraries/ArmarXObjects/ObjectID.h +++ b/source/RobotAPI/libraries/ArmarXObjects/ObjectID.h @@ -14,7 +14,7 @@ namespace armarx ObjectID(); ObjectID(const std::string& dataset, const std::string& className, const std::string& instancName = ""); - /// Construct from either a name ("myobject") or ID ("mydataset/myobject", "mydataset/myclass/myinstance"). + /// Construct from either a class name ("myobject") or ID ("mydataset/myobject", "mydataset/myclass/myinstance"). ObjectID(const std::string& nameOrID); @@ -38,7 +38,10 @@ namespace armarx /// Return "dataset/className" or "dataset/className/instanceName". std::string str() const; + /// Indicates whether dataset and class name are equal. + bool equalClass(const ObjectID& rhs) const; + /// Indicates whether dataset, class name and instance name are equal. bool operator==(const ObjectID& rhs) const; inline bool operator!=(const ObjectID& rhs) const { diff --git a/source/RobotAPI/libraries/ArmarXObjects/test/CMakeLists.txt b/source/RobotAPI/libraries/ArmarXObjects/test/CMakeLists.txt index 2ad25f7ea27f28908cf50d0c950ba4170487fc85..4fe275d872221c3c49e83d9d295917e5d809c401 100644 --- a/source/RobotAPI/libraries/ArmarXObjects/test/CMakeLists.txt +++ b/source/RobotAPI/libraries/ArmarXObjects/test/CMakeLists.txt @@ -3,3 +3,4 @@ SET(LIBS ${LIBS} ArmarXCore ${LIB_NAME}) armarx_add_test(ArmarXObjectsTest ArmarXObjectsTest.cpp "${LIBS}") +armarx_add_test(ArmarXObjects_ObjectIDTest ObjectIDTest.cpp "${LIBS}") diff --git a/source/RobotAPI/libraries/ArmarXObjects/test/ObjectIDTest.cpp b/source/RobotAPI/libraries/ArmarXObjects/test/ObjectIDTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ca0f7001ebdeeff9e164b44179fca8156a10f003 --- /dev/null +++ b/source/RobotAPI/libraries/ArmarXObjects/test/ObjectIDTest.cpp @@ -0,0 +1,121 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @package RobotAPI::ArmarXObjects::ArmarXObjects + * @author Rainer Kartmann ( rainer dot kartmann at kit dot edu ) + * @date 2020 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#define BOOST_TEST_MODULE RobotAPI::ArmarXLibraries::ArmarXObjects + +#define ARMARX_BOOST_TEST + +#include <RobotAPI/Test.h> +#include "../ArmarXObjects.h" + +#include "../ObjectID.h" + +#include <iostream> + + +namespace +{ + struct Fixture + { + std::vector<armarx::ObjectID> dcs + { + { "Data/Class/0" }, + { "Data/Class/1" }, + { "Data/Class/2" } + }; + armarx::ObjectID dc0 { "Data/Class/0" }; + + std::vector<armarx::ObjectID> ots + { + { "Other/Type/0" }, + { "Other/Type/1" }, + { "Other/Type/2" } + }; + armarx::ObjectID ot0 { "Other/Type/0" }; + }; +} + +BOOST_FIXTURE_TEST_SUITE(ObjectIDTests, Fixture) + +BOOST_AUTO_TEST_CASE(test_construction_from_string) +{ + for (std::size_t i = 0; i < dcs.size(); ++i) + { + BOOST_CHECK_EQUAL(dcs[i].dataset(), "Data"); + BOOST_CHECK_EQUAL(dcs[i].className(), "Class"); + BOOST_CHECK_EQUAL(dcs[i].instanceName(), std::to_string(i)); + + BOOST_CHECK_EQUAL(ots[i].dataset(), "Other"); + BOOST_CHECK_EQUAL(ots[i].className(), "Type"); + BOOST_CHECK_EQUAL(ots[i].instanceName(), std::to_string(i)); + } +} + +BOOST_AUTO_TEST_CASE(test_equals_operator) +{ + for (std::size_t i = 0; i < dcs.size(); ++i) + { + BOOST_TEST_CONTEXT("i=" << i) + { + BOOST_CHECK_EQUAL(dcs[i], dcs[i]); + BOOST_CHECK_NE(dcs[i], ots[i]); + if (i != 0) + { + BOOST_CHECK_NE(dcs[i], dc0); + } + } + } +} + +BOOST_AUTO_TEST_CASE(test_less_than_operator) +{ + for (std::size_t i = 0; i < dcs.size(); ++i) + { + for (std::size_t j = i; j < dcs.size(); ++j) + { + BOOST_CHECK_LE(dcs[i], dcs[j]); + BOOST_CHECK_GE(dcs[j], dcs[i]); + if (i != j) + { + BOOST_CHECK_LT(dcs[i], dcs[j]); + BOOST_CHECK_GT(dcs[j], dcs[i]); + } + } + } +} + +BOOST_AUTO_TEST_CASE(test_equalClass) +{ + for (std::size_t i = 0; i < dcs.size(); ++i) + { + for (std::size_t j = 0; j < dcs.size(); ++j) + { + BOOST_CHECK(dcs[i].equalClass(dcs[j])); + BOOST_CHECK(ots[i].equalClass(ots[j])); + + BOOST_CHECK(!dcs[i].equalClass(ots[j])); + BOOST_CHECK(!ots[i].equalClass(dcs[j])); + } + } +} + +BOOST_AUTO_TEST_SUITE_END()