diff --git a/scenarios/ArMemObjectMemory/config/RobotToArVizApp.cfg b/scenarios/ArMemObjectMemory/config/RobotToArVizApp.cfg index b023833a9d18ac3f4c1d92911b29ad1f8699a22c..55d034d9c687c16ad2f34f458f85ca66da54cdff 100644 --- a/scenarios/ArMemObjectMemory/config/RobotToArVizApp.cfg +++ b/scenarios/ArMemObjectMemory/config/RobotToArVizApp.cfg @@ -18,7 +18,7 @@ # ArmarX.ApplicationName = "" -# ArmarX.CachePath: Path for cache files. If relative path AND env. variable ARMARX_USER_CONFIG_DIR is set, the cache path will be made relative to ARMARX_USER_CONFIG_DIR. Otherwise if relative it will be relative to the default ArmarX config dir (${HOME}/.armarx) +# ArmarX.CachePath: Path for cache files. If relative path AND env. variable ARMARX_CONFIG_DIR is set, the cache path will be made relative to ARMARX_CONFIG_DIR. Otherwise if relative it will be relative to the default ArmarX config dir (${ARMARX_WORKSPACE}/armarx_config) # Attributes: # - Default: mongo/.cache # - Case sensitivity: yes @@ -109,6 +109,14 @@ # ArmarX.RemoteHandlesDeletionTimeout = 3000 +# ArmarX.RobotToArViz.ArVizStorageName: Name of the ArViz storage +# Attributes: +# - Default: ArVizStorage +# - Case sensitivity: yes +# - Required: no +# ArmarX.RobotToArViz.ArVizStorageName = ArVizStorage + + # ArmarX.RobotToArViz.ArVizTopicName: Name of the ArViz topic # Attributes: # - Default: ArVizTopic diff --git a/source/RobotAPI/components/ObjectPoseClientExample/ObjectPoseClientExample.cpp b/source/RobotAPI/components/ObjectPoseClientExample/ObjectPoseClientExample.cpp index 6359111c24ef51d35360ff8def6853f9fdae55ae..10a8b7ae1b665966787ea6a056ab3f1056f8abeb 100644 --- a/source/RobotAPI/components/ObjectPoseClientExample/ObjectPoseClientExample.cpp +++ b/source/RobotAPI/components/ObjectPoseClientExample/ObjectPoseClientExample.cpp @@ -22,7 +22,13 @@ #include "ObjectPoseClientExample.h" +#include <SimoxUtility/math/pose.h> + +#include <ArmarXCore/core/ice_conversions/ice_conversions_templates.h> #include <ArmarXCore/core/time/CycleUtil.h> +#include <ArmarXCore/core/time/ice_conversions.h> + +#include <RobotAPI/libraries/ArmarXObjects/ice_conversions.h> namespace armarx @@ -32,6 +38,12 @@ namespace armarx { armarx::PropertyDefinitionsPtr defs = new ComponentPropertyDefinitions(getConfigIdentifier()); + defs->optional(p.predictionsPerObject, "predictions.NumberPerObject", + "How many predictions with increasing time offsets to make per object."); + + defs->optional(p.millisecondPredictionIncrement, "predictions.TimeIncrement", + "The size of the prediction time offset increment in milliseconds."); + return defs; } @@ -81,6 +93,7 @@ namespace armarx sendDebugObserverBatch(); } + viz::StagedCommit stage = arviz.stage(); { // Visualize the objects. viz::Layer layer = arviz.layer("Objects"); @@ -91,10 +104,74 @@ namespace armarx .fileByObjectFinder(objectPose.objectID) .alpha(objectPose.confidence)); } - arviz.commit({layer}); + stage.add(layer); } + stage.add(visualizePredictions(client, objectPoses)); + arviz.commit(stage); cycle.waitForCycleDuration(); } } -} + + + viz::Layer + ObjectPoseClientExample::visualizePredictions(const objpose::ObjectPoseClient& client, + const objpose::ObjectPoseSeq& objectPoses) + { + viz::Layer layer = arviz.layer("PredictionArray"); + + objpose::ObjectPosePredictionRequestSeq requests; + for (const objpose::ObjectPose& objectPose : objectPoses) + { + for (int i = 0; i < p.predictionsPerObject; ++i) + { + objpose::ObjectPosePredictionRequest request; + toIce(request.objectID, objectPose.objectID); + request.settings.predictionEngineID = "Linear Position Regression"; + toIce(request.timestamp, + DateTime::Now() + + Duration::MilliSeconds((i + 1) * p.millisecondPredictionIncrement)); + toIce(request.timeWindow, Duration::Seconds(10)); + requests.push_back(request); + } + } + + objpose::ObjectPosePredictionResultSeq results; + try + { + results = client.objectPoseStorage->predictObjectPoses(requests); + } + catch (const Ice::LocalException& e) + { + ARMARX_INFO << "Failed to get predictions for object poses: " << e.what(); + } + + for (size_t i = 0; i < results.size(); ++i) + { + const objpose::ObjectPosePredictionResult& result = results.at(i); + if (result.success) + { + auto predictedPose = armarx::fromIce<objpose::ObjectPose>(result.prediction); + Eigen::Vector3f predictedPosition = + simox::math::position(predictedPose.objectPoseGlobal); + int alpha = + (p.predictionsPerObject - (static_cast<int>(i) % p.predictionsPerObject)) * + 255 / p.predictionsPerObject; // NOLINT + layer.add( + viz::Arrow(predictedPose.objectID.str() + " Linear Prediction " + + std::to_string(i % p.predictionsPerObject)) + .fromTo(simox::math::position( + objectPoses.at(i / p.predictionsPerObject).objectPoseGlobal), + predictedPosition) + .color(viz::Color::green(255, alpha))); // NOLINT + } + else + { + ARMARX_INFO << "Prediction for object '" << result.prediction.objectID + << "' failed: " << result.errorMessage; + } + } + + return layer; + } +} // namespace armarx diff --git a/source/RobotAPI/components/ObjectPoseClientExample/ObjectPoseClientExample.h b/source/RobotAPI/components/ObjectPoseClientExample/ObjectPoseClientExample.h index c49e28f3518755cc492ffa31b67710b005abf7a3..8cc667b26bad490c8fb314a6ef759e52f850430d 100644 --- a/source/RobotAPI/components/ObjectPoseClientExample/ObjectPoseClientExample.h +++ b/source/RobotAPI/components/ObjectPoseClientExample/ObjectPoseClientExample.h @@ -75,10 +75,20 @@ namespace armarx void objectProcessingTaskRun(); + viz::Layer visualizePredictions(const objpose::ObjectPoseClient& client, + const objpose::ObjectPoseSeq& objectPoses); + private: armarx::SimpleRunningTask<>::pointer_type objectProcessingTask; + struct Properties + { + int predictionsPerObject = 5; // NOLINT + int millisecondPredictionIncrement = 200; // NOLINT + }; + Properties p; + }; } diff --git a/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.cpp b/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.cpp index 0dd286903f1cdfefcc683cb1883ac12674971ca7..cd7e8b18230e38d21cf700f04c116cca830eaa14 100644 --- a/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.cpp +++ b/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.cpp @@ -218,7 +218,7 @@ namespace armarx ExampleMemory::predict(const armem::prediction::data::PredictionRequestSeq& requests) { armem::prediction::data::PredictionResultSeq result; - for (auto request : requests) + for (const auto& request : requests) { result.push_back(predictSingle(request)); } @@ -254,29 +254,31 @@ namespace armarx else { result.success = false; - result.errorMessage << - "Could not find entity referenced by MemoryID " << boID; + result.errorMessage = + "Could not find entity referenced by MemoryID '" + boID.str() + "'."; } } else { result.success = false; - result.errorMessage << "Could not find entity referenced by MemoryID " << boID; + result.errorMessage = + "Could not find entity referenced by MemoryID '" + boID.str() + "'."; } } else { result.success = false; - result.errorMessage << "This memory does not support the prediction engine \"" << engine << "\""; + result.errorMessage = + "This memory does not support the prediction engine '" + engine + "'."; } return result.toIce(); } - armem::prediction::data::PredictionEngineSeq + armem::prediction::data::EngineSupportMap ExampleMemory::getAvailableEngines() { - return {{"Latest"}}; + return {{armarx::toIce<armem::data::MemoryID>(workingMemory().id()), {{"Latest"}}}}; } // REMOTE GUI diff --git a/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.h b/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.h index de89914ba8ed44517ce40639849afa826d022ef1..fa9c2b1eacc314bcb37de05f4f19039d9bcbd582 100644 --- a/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.h +++ b/source/RobotAPI/components/armem/server/ExampleMemory/ExampleMemory.h @@ -76,7 +76,7 @@ namespace armarx armem::prediction::data::PredictionResultSeq predict(const armem::prediction::data::PredictionRequestSeq& requests) override; - armem::prediction::data::PredictionEngineSeq getAvailableEngines() override; + armem::prediction::data::EngineSupportMap getAvailableEngines() override; protected: diff --git a/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.cpp b/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.cpp index e67746b42af6fefdc379a50bb3f9559eee8b136b..02ac6e6aec08d86253f0512584a9921f8063bf9e 100644 --- a/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.cpp +++ b/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.cpp @@ -23,6 +23,13 @@ #include "ObjectMemory.h" #include <ArmarXCore/core/ice_conversions/ice_conversions_templates.h> +#include <ArmarXCore/core/time/ice_conversions.h> + +#include <RobotAPI/libraries/ArmarXObjects/aron_conversions.h> +#include <RobotAPI/libraries/ArmarXObjects/ice_conversions.h> +#include <RobotAPI/libraries/armem/client/Prediction.h> +#include <RobotAPI/libraries/armem/client/query.h> +#include <RobotAPI/libraries/armem_objects/aron/ObjectInstance.aron.generated.h> namespace armarx::armem::server::obj @@ -54,6 +61,9 @@ namespace armarx::armem::server::obj // defs->component(kinematicUnitObserver); // Optional dependency. defs->defineOptionalProperty<std::string>("cmp.KinematicUnitObserverName", "KinematicUnitObserver", "Name of the kinematic unit observer."); + defs->optional(predictionTimeWindow, "prediction.TimeWindow", + "Duration of time window into the past to use for predictions" + " when requested via the PredictingMemoryInterface (in seconds)."); return defs; } @@ -215,6 +225,84 @@ namespace armarx::armem::server::obj return outputs; } + armem::prediction::data::PredictionResultSeq + ObjectMemory::predict(const armem::prediction::data::PredictionRequestSeq& requests) + { + std::vector<armem::prediction::data::PredictionResult> results; + for (const auto& request : requests) + { + auto boRequest = armarx::fromIce<armem::client::PredictionRequest>(request); + armem::client::PredictionResult result; + result.snapshotID = boRequest.snapshotID; + if (armem::contains(workingMemory().id().withCoreSegmentName("Instance"), + boRequest.snapshotID) && + !boRequest.snapshotID.hasGap() && boRequest.snapshotID.hasInstanceIndex()) + { + objpose::ObjectPosePredictionRequest objPoseRequest; + toIce(objPoseRequest.timeWindow, Duration::SecondsDouble(predictionTimeWindow)); + objPoseRequest.objectID = toIce(ObjectID(request.snapshotID.entityName)); + objPoseRequest.settings = request.settings; + toIce(objPoseRequest.timestamp, boRequest.snapshotID.timestamp); + objpose::ObjectPosePredictionResult objPoseResult = + predictObjectPoses({objPoseRequest}).at(0); + result.success = objPoseResult.success; + result.errorMessage = objPoseResult.errorMessage; + + if (objPoseResult.success) + { + armem::client::QueryBuilder builder; + builder.singleEntitySnapshot(boRequest.snapshotID); + auto queryResult = armarx::fromIce<armem::client::QueryResult>( + query(builder.buildQueryInputIce())); + std::string instanceError = + "Could not find instance '" + boRequest.snapshotID.str() + "' in memory"; + if (!queryResult.success) + { + result.success = false; + result.errorMessage << instanceError << ":\n" << queryResult.errorMessage; + } + else + { + auto* aronInstance = queryResult.memory.findInstance(boRequest.snapshotID); + if (aronInstance == nullptr) + { + result.success = false; + result.errorMessage << instanceError; + } + else + { + auto instance = + armem::arondto::ObjectInstance::FromAron(aronInstance->data()); + objpose::toAron( + instance.pose, + armarx::fromIce<objpose::ObjectPose>(objPoseResult.prediction)); + result.prediction = instance.toAron(); + } + } + } + } + else + { + result.success = false; + result.errorMessage << "No predictions are supported for MemoryID " + << boRequest.snapshotID + << ". Have you given an instance index if requesting" + << " an object pose prediction?"; + } + results.push_back(result.toIce()); + } + + return results; + } + + armem::prediction::data::EngineSupportMap ObjectMemory::getAvailableEngines() + { + // TODO(phesch): Replace with generic code in Memory implementation + return {{armarx::toIce<armem::data::MemoryID>( + workingMemory().id().withCoreSegmentName("Instance")), + {{"Linear Position Regression"}}}}; + } + void ObjectMemory::createRemoteGuiTab() { using namespace armarx::RemoteGui::Client; diff --git a/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.h b/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.h index 82f39db591bd56537fa280e0dd10a56fa9806ca3..7067f42da1344fb4aa1936dfcafaa2b77f3df599 100644 --- a/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.h +++ b/source/RobotAPI/components/armem/server/ObjectMemory/ObjectMemory.h @@ -96,11 +96,21 @@ namespace armarx::armem::server::obj // Without this, ObjectMemory draws the original // methods from ObjectMemoryInterface and complains // that an overload is being hidden. - using ReadWritePluginUser::getActions; using ReadWritePluginUser::executeActions; + using ReadWritePluginUser::getActions; + using ReadWritePluginUser::getAvailableEngines; + using ReadWritePluginUser::predict; - armem::actions::GetActionsOutputSeq getActions(const armem::actions::GetActionsInputSeq& inputs) override; - armem::actions::ExecuteActionOutputSeq executeActions(const armem::actions::ExecuteActionInputSeq& inputs) override; + // Actions + armem::actions::GetActionsOutputSeq + getActions(const armem::actions::GetActionsInputSeq& inputs) override; + armem::actions::ExecuteActionOutputSeq + executeActions(const armem::actions::ExecuteActionInputSeq& inputs) override; + + // Predictions + armem::prediction::data::PredictionResultSeq + predict(const armem::prediction::data::PredictionRequestSeq& requests) override; + armem::prediction::data::EngineSupportMap getAvailableEngines() override; // Remote GUI void createRemoteGuiTab(); @@ -113,6 +123,8 @@ namespace armarx::armem::server::obj RobotStateComponentInterfacePrx robotStateComponent; KinematicUnitObserverInterfacePrx kinematicUnitObserver; + double predictionTimeWindow = 2; + clazz::Segment classSegment; attachments::Segment attachmentSegment; diff --git a/source/RobotAPI/components/units/RobotUnit/BasicControllers.h b/source/RobotAPI/components/units/RobotUnit/BasicControllers.h index 16f222d827a91c7eba560695487f756b3e7de2f7..c544ea8dd4b54eb15cebd9fb10e36a00e0623234 100644 --- a/source/RobotAPI/components/units/RobotUnit/BasicControllers.h +++ b/source/RobotAPI/components/units/RobotUnit/BasicControllers.h @@ -27,7 +27,6 @@ #include <ArmarXCore/core/util/algorithm.h> #include <RobotAPI/libraries/core/math/MathUtils.h> #include <RobotAPI/libraries/core/PIDController.h> -#include <ArmarXCore/core/rapidxml/wrapper/DefaultRapidXmlReader.h> // #define DEBUG_POS_CTRL #ifdef DEBUG_POS_CTRL #include <boost/circular_buffer.hpp> @@ -203,20 +202,6 @@ namespace armarx { public: RampedAccelerationVelocityControllerConfiguration() = default; - static RampedAccelerationVelocityControllerConfigurationPtr CreateFromXml(DefaultRapidXmlReaderNode node) - { - RampedAccelerationVelocityControllerConfigurationPtr config(new RampedAccelerationVelocityControllerConfiguration()); - FillFromXml(*config, node); - return config; - } - static void FillFromXml(RampedAccelerationVelocityControllerConfiguration& config, DefaultRapidXmlReaderNode node) - { - config.maxVelocityRad = node.first_node("maxVelocityRad").value_as_float(); - config.maxDecelerationRad = node.first_node("maxDecelerationRad").value_as_float(); - config.jerk = node.first_node("jerk").value_as_float(); - config.maxDt = node.first_node("maxDt").value_as_float(); - config.directSetVLimit = node.first_node("directSetVLimit").value_as_float(); - } float maxVelocityRad; float maxDecelerationRad; diff --git a/source/RobotAPI/components/units/RobotUnit/NJointControllers/NJointControllerBase.h b/source/RobotAPI/components/units/RobotUnit/NJointControllers/NJointControllerBase.h index b18689724a120d17a3eff61158d9e57cfd2ee7a9..d393f02ea5f90ff5ed41eab507fb6bf94cc7415a 100644 --- a/source/RobotAPI/components/units/RobotUnit/NJointControllers/NJointControllerBase.h +++ b/source/RobotAPI/components/units/RobotUnit/NJointControllers/NJointControllerBase.h @@ -23,6 +23,7 @@ #pragma once #include <RobotAPI/interface/units/RobotUnit/NJointController.h> +#include <RobotAPI/components/units/RobotUnit/RobotUnit.h> #include <ArmarXCore/core/ManagedIceObject.h> #include <ArmarXCore/core/exceptions/local/ExpressionException.h> // for ARMARX_CHECK diff --git a/source/RobotAPI/interface/armem/prediction.ice b/source/RobotAPI/interface/armem/prediction.ice index f42535928e434be36702a949f9929246a8f08f7b..a2aae725e3ef5a399ccfacf819d07eb56693f8e4 100644 --- a/source/RobotAPI/interface/armem/prediction.ice +++ b/source/RobotAPI/interface/armem/prediction.ice @@ -17,6 +17,7 @@ module armarx string engineID; } sequence<PredictionEngine> PredictionEngineSeq; + dictionary<armem::data::MemoryID, PredictionEngineSeq> EngineSupportMap; // Settings to use for a given prediction (e.g. which engine to use) struct PredictionSettings diff --git a/source/RobotAPI/interface/armem/server/PredictingMemoryInterface.ice b/source/RobotAPI/interface/armem/server/PredictingMemoryInterface.ice index 5b8b668ab72a3c4aef15720e450813891b140124..ead5ad03b5edd7f1bbb39dd1c528376005c8b041 100644 --- a/source/RobotAPI/interface/armem/server/PredictingMemoryInterface.ice +++ b/source/RobotAPI/interface/armem/server/PredictingMemoryInterface.ice @@ -13,14 +13,15 @@ module armarx { /* Request multiple predictions from the memory. * The timestamp to requst a prediction for is encoded in the MemoryIDs. - * The results are all packed into one QueryResult. */ prediction::data::PredictionResultSeq predict(prediction::data::PredictionRequestSeq requests); - /* Get the list of engines the memory offers for use in predictions. + /* Get a map from memory segments to the prediction engines they support. + * Best used with the armem::core::PrefixMap to easily retrieve + * the available engine selection for fully evaluated MemoryIDs. */ - prediction::data::PredictionEngineSeq getAvailableEngines(); + prediction::data::EngineSupportMap getAvailableEngines(); } }; }; diff --git a/source/RobotAPI/interface/objectpose/ObjectPoseStorageInterface.ice b/source/RobotAPI/interface/objectpose/ObjectPoseStorageInterface.ice index 1dfe8b9f299d24684a0acdd3d6f53ed61063d397..a069da9078a11beeb140d5477dd847beb85cf2c3 100644 --- a/source/RobotAPI/interface/objectpose/ObjectPoseStorageInterface.ice +++ b/source/RobotAPI/interface/objectpose/ObjectPoseStorageInterface.ice @@ -152,6 +152,23 @@ module armarx long discardUpdatesUntilMilliSeconds = -1; }; + struct ObjectPosePredictionRequest + { + armarx::data::ObjectID objectID; + armarx::core::time::dto::DateTime timestamp; + armarx::core::time::dto::Duration timeWindow; + armem::prediction::data::PredictionSettings settings; + }; + sequence<ObjectPosePredictionRequest> ObjectPosePredictionRequestSeq; + struct ObjectPosePredictionResult + { + bool success = false; + string errorMessage; + + data::ObjectPose prediction; + }; + sequence<ObjectPosePredictionResult> ObjectPosePredictionResultSeq; + interface ObjectPoseStorageInterface extends ObjectPoseTopic { @@ -191,6 +208,15 @@ module armarx SignalHeadMovementOutput signalHeadMovement(SignalHeadMovementInput input); + // Predicting + + // Request predictions for the poses of the given objects at the given timestamps. + ObjectPosePredictionResultSeq + predictObjectPoses(ObjectPosePredictionRequestSeq requests); + + // Get the prediction engines available for use with this storage interface. + armem::prediction::data::PredictionEngineSeq getAvailableObjectPoseEngines(); + }; }; diff --git a/source/RobotAPI/libraries/ArmarXObjects/CMakeLists.txt b/source/RobotAPI/libraries/ArmarXObjects/CMakeLists.txt index 2b2fb5f9d4ea899930351b1234873b2354cb50bf..ec6658fa85afba280fef0613fbc60596da57ee8a 100644 --- a/source/RobotAPI/libraries/ArmarXObjects/CMakeLists.txt +++ b/source/RobotAPI/libraries/ArmarXObjects/CMakeLists.txt @@ -31,6 +31,7 @@ set(LIB_FILES plugins/ObjectPoseClientPlugin.cpp plugins/RequestedObjects.cpp + predictions.cpp util.cpp ) set(LIB_HEADERS @@ -57,6 +58,7 @@ set(LIB_HEADERS plugins/ObjectPoseClientPlugin.h plugins/RequestedObjects.h + predictions.h util.h ) diff --git a/source/RobotAPI/libraries/ArmarXObjects/predictions.cpp b/source/RobotAPI/libraries/ArmarXObjects/predictions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..427868519c15f4f3c31342f980d3ee245f528a88 --- /dev/null +++ b/source/RobotAPI/libraries/ArmarXObjects/predictions.cpp @@ -0,0 +1,87 @@ +/* + * 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 + * @author phesch ( ulila at student dot kit dot edu ) + * @date 2022 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#include "predictions.h" + +#include <SimoxUtility/math/pose/pose.h> +#include <SimoxUtility/math/regression/linear.h> + +#include <ArmarXCore/core/ice_conversions/ice_conversions_templates.h> + +namespace armarx::objpose +{ + + objpose::ObjectPosePredictionResult + predictObjectPoseLinear(const std::map<DateTime, ObjectPose>& poses, + const DateTime& time, + const ObjectPose& latestPose) + { + objpose::ObjectPosePredictionResult result; + result.success = false; + + const DateTime timeOrigin = DateTime::Now(); + + std::vector<double> timestampsSec; + std::vector<Eigen::Vector3d> positions; + + // ToDo: How to handle attached objects? + for (const auto& [timestamp, pose] : poses) + { + timestampsSec.push_back((timestamp - timeOrigin).toSecondsDouble()); + positions.emplace_back(simox::math::position(pose.objectPoseGlobal).cast<double>()); + } + + ARMARX_CHECK_EQUAL(timestampsSec.size(), positions.size()); + + Eigen::Vector3d prediction; + // Static objects don't move. Objects that haven't moved for a while probably won't either. + if (timestampsSec.size() <= 1 || latestPose.isStatic) + { + prediction = simox::math::position(latestPose.objectPoseGlobal).cast<double>(); + } + else + { + using simox::math::LinearRegression3d; + const bool inputOffset = false; + + const LinearRegression3d model = + LinearRegression3d::Fit(timestampsSec, positions, inputOffset); + const auto predictionTime = armarx::fromIce<DateTime>(time); + prediction = model.predict((predictionTime - timeOrigin).toSecondsDouble()); + } + + // Used as a template for the returned pose prediction. + ObjectPose latestCopy = latestPose; + + // Reuse the rotation from the latest pose. + // This is a pretty generous assumption, but the linear model doesn't cover rotations, + // so it's our best guess. + Eigen::Matrix4f latest = latestPose.objectPoseGlobal; + simox::math::position(latest) = prediction.cast<float>(); + latestCopy.setObjectPoseGlobal(latest); + + result.success = true; + result.prediction = latestCopy.toIce(); + + return result; + } +} // namespace armarx::objpose diff --git a/source/RobotAPI/libraries/ArmarXObjects/predictions.h b/source/RobotAPI/libraries/ArmarXObjects/predictions.h new file mode 100644 index 0000000000000000000000000000000000000000..d9b647f9dd81a1e04ff435a0e20c94368cdb89f4 --- /dev/null +++ b/source/RobotAPI/libraries/ArmarXObjects/predictions.h @@ -0,0 +1,50 @@ +/* + * 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 + * @author phesch ( ulila at student dot kit dot edu ) + * @date 2022 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ +#pragma once + +#include <ArmarXCore/core/time/DateTime.h> + +#include <RobotAPI/interface/objectpose/ObjectPoseStorageInterface.h> +#include <RobotAPI/libraries/ArmarXObjects/ObjectPose.h> +#include <RobotAPI/libraries/armem/client/Prediction.h> + +namespace armarx::objpose +{ + /** + * @brief Predict the pose of an object given a history of poses + * based on a linear regression. + * + * If `poses` is empty, `latestPose` is returned. + * + * @param poses the history of poses to base the prediction on + * @param time the timestamp to make the prediction for + * @param latestPose used for metadata so the result is valid even if poses is empty + * @param settings the settings to use for the prediction + * @return the result of the prediction + */ + objpose::ObjectPosePredictionResult + predictObjectPoseLinear( + const std::map<DateTime, ObjectPose>& poses, + const DateTime& time, + const ObjectPose& latestPose); + +} // namespace armarx::objpose diff --git a/source/RobotAPI/libraries/armem/client/Prediction.cpp b/source/RobotAPI/libraries/armem/client/Prediction.cpp index f58649a457fc8117309ceaf5426a0a84540f35f5..dce9473bd58d557d3a37a511c7c257c65305bee8 100644 --- a/source/RobotAPI/libraries/armem/client/Prediction.cpp +++ b/source/RobotAPI/libraries/armem/client/Prediction.cpp @@ -118,7 +118,7 @@ namespace armarx::armem::client toIce(armem::prediction::data::PredictionResult& ice, const PredictionResult& result) { ice.success = result.success; - ice.errorMessage = result.errorMessage.str(); + ice.errorMessage = result.errorMessage; toIce(ice.snapshotID, result.snapshotID); ice.prediction = result.prediction->toAronDictDTO(); } @@ -127,7 +127,7 @@ namespace armarx::armem::client fromIce(const armem::prediction::data::PredictionResult& ice, PredictionResult& result) { result.success = ice.success; - result.errorMessage.str(ice.errorMessage); + result.errorMessage = ice.errorMessage; fromIce(ice.snapshotID, result.snapshotID); result.prediction = aron::data::Dict::FromAronDictDTO(ice.prediction); } diff --git a/source/RobotAPI/libraries/armem/client/Prediction.h b/source/RobotAPI/libraries/armem/client/Prediction.h index a1538758519228b93e4c6f95e131eed6c7fe9bad..0e9ac30d5e6e797dd228ca6f313c6cdbed19bf5b 100644 --- a/source/RobotAPI/libraries/armem/client/Prediction.h +++ b/source/RobotAPI/libraries/armem/client/Prediction.h @@ -57,7 +57,7 @@ namespace armarx::armem::client struct PredictionResult { bool success; - std::stringstream errorMessage; + std::string errorMessage; armem::MemoryID snapshotID; aron::data::DictPtr prediction; diff --git a/source/RobotAPI/libraries/armem/client/Reader.cpp b/source/RobotAPI/libraries/armem/client/Reader.cpp index 52be34eb42c8f5c54f2138731690a5f43728d6f8..ce2d0cfcfa41bedc7f6cab56f45e79d959bc68da 100644 --- a/source/RobotAPI/libraries/armem/client/Reader.cpp +++ b/source/RobotAPI/libraries/armem/client/Reader.cpp @@ -479,7 +479,7 @@ namespace armarx::armem::client return boResults; } - std::vector<PredictionEngine> + armem::prediction::data::EngineSupportMap Reader::getAvailablePredictionEngines() const { if (!predictionPrx) @@ -489,7 +489,7 @@ namespace armarx::armem::client "Prediction interface proxy must be set to request a prediction."); } - std::vector<armem::prediction::data::PredictionEngine> engines; + armem::prediction::data::EngineSupportMap engines; try { engines = predictionPrx->getAvailableEngines(); @@ -499,13 +499,13 @@ namespace armarx::armem::client // Just leave engines empty in this case. } - std::vector<PredictionEngine> boEngines; - boEngines.reserve(engines.size()); - for (const auto& engine : engines) - { - boEngines.push_back(armarx::fromIce<PredictionEngine>(engine)); - } + //std::vector<PredictionEngine> boEngines; + //boEngines.reserve(engines.size()); + //for (const auto& engine : engines) + //{ + // boEngines.push_back(armarx::fromIce<PredictionEngine>(engine)); + //} - return boEngines; + return engines; } } // namespace armarx::armem::client diff --git a/source/RobotAPI/libraries/armem/client/Reader.h b/source/RobotAPI/libraries/armem/client/Reader.h index beb0e53f5ff25078cb881d4477c19c5cd291fe1a..34b3ce320182c7995412dc7e02b04c58fe1a0dfa 100644 --- a/source/RobotAPI/libraries/armem/client/Reader.h +++ b/source/RobotAPI/libraries/armem/client/Reader.h @@ -178,6 +178,7 @@ namespace armarx::armem::client */ std::vector<PredictionResult> predict(const std::vector<PredictionRequest>& requests) const; + //TODO(phesch): Fix this interface with the proper type /** * @brief Get the list of prediction engines supported by the memory. * @@ -185,7 +186,7 @@ namespace armarx::armem::client * * @return the vector of supported prediction engines */ - std::vector<PredictionEngine> getAvailablePredictionEngines() const; + armem::prediction::data::EngineSupportMap getAvailablePredictionEngines() const; inline operator bool() const { diff --git a/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.cpp b/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.cpp index 115e38311ae09d9d5e1362bf362b1207a76173a8..f690cbaeef48944c8568ef58b0aae08b82735513 100644 --- a/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.cpp +++ b/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.cpp @@ -127,14 +127,14 @@ namespace armarx::armem::server::plugins { armem::client::PredictionResult singleResult; singleResult.success = false; - singleResult.errorMessage << "This memory does not implement predictions."; + singleResult.errorMessage = "This memory does not implement predictions."; singleResult.prediction = nullptr; result.push_back(singleResult.toIce()); } return result; } - armem::prediction::data::PredictionEngineSeq + armem::prediction::data::EngineSupportMap ReadWritePluginUser::getAvailableEngines() { return {}; @@ -147,7 +147,7 @@ namespace armarx::armem::server::plugins return predict(requests); } - armem::prediction::data::PredictionEngineSeq + armem::prediction::data::EngineSupportMap ReadWritePluginUser::getAvailableEngines(const ::Ice::Current& /*unused*/) { return getAvailableEngines(); diff --git a/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.h b/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.h index 1e0b4bc82568382ead82cbf7b337529db4f46251..37f25fd8f3199179402755dc76d98367964aa2af 100644 --- a/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.h +++ b/source/RobotAPI/libraries/armem/server/plugins/ReadWritePluginUser.h @@ -57,10 +57,10 @@ namespace armarx::armem::server::plugins // PredictingInterface interface virtual armem::prediction::data::PredictionResultSeq predict(const armem::prediction::data::PredictionRequestSeq& requests); - virtual armem::prediction::data::PredictionEngineSeq getAvailableEngines(); + virtual armem::prediction::data::EngineSupportMap getAvailableEngines(); virtual armem::prediction::data::PredictionResultSeq predict(const armem::prediction::data::PredictionRequestSeq& requests, const ::Ice::Current&) override; - virtual armem::prediction::data::PredictionEngineSeq getAvailableEngines(const ::Ice::Current&) override; + virtual armem::prediction::data::EngineSupportMap getAvailableEngines(const ::Ice::Current&) override; public: diff --git a/source/RobotAPI/libraries/armem_objects/server/instance/Segment.cpp b/source/RobotAPI/libraries/armem_objects/server/instance/Segment.cpp index b30c1a048e5a85f62a6efe79ae7d82ecab783ed8..6bad86c0f05ccdaa33855d9411c27d9cc04d50f6 100644 --- a/source/RobotAPI/libraries/armem_objects/server/instance/Segment.cpp +++ b/source/RobotAPI/libraries/armem_objects/server/instance/Segment.cpp @@ -37,8 +37,10 @@ #include <SimoxUtility/algorithm/string.h> #include <SimoxUtility/json.h> #include <SimoxUtility/math/pose/pose.h> +#include <SimoxUtility/math/regression/linear.h> #include <Eigen/Geometry> +#include <Eigen/Dense> #include <sstream> @@ -545,6 +547,35 @@ namespace armarx::armem::server::obj::instance } + std::map<DateTime, objpose::ObjectPose> + Segment::getObjectPosesInRange(const wm::Entity& entity, + const DateTime& start, + const DateTime& end) + { + std::map<DateTime, objpose::ObjectPose> result; + + entity.forEachSnapshotInTimeRange( + start, + end, + [&result](const wm::EntitySnapshot& snapshot) + { + snapshot.forEachInstance( + [&result, &snapshot](const wm::EntityInstance& instance) + { + arondto::ObjectInstance dto; + dto.fromAron(instance.data()); + + ObjectPose pose; + fromAron(dto, pose); + + result.emplace(snapshot.time(), pose); + }); + }); + + return result; + } + + std::optional<simox::OrientedBoxf> Segment::getObjectOOBB(const ObjectID& id) { return oobbCache.get(id); diff --git a/source/RobotAPI/libraries/armem_objects/server/instance/Segment.h b/source/RobotAPI/libraries/armem_objects/server/instance/Segment.h index da97aac841559b845fb308e93ac572739efb7c3c..0aa9b930411f0a6f6336ea439fbb98ff6dc74a50 100644 --- a/source/RobotAPI/libraries/armem_objects/server/instance/Segment.h +++ b/source/RobotAPI/libraries/armem_objects/server/instance/Segment.h @@ -18,6 +18,7 @@ #include <RobotAPI/libraries/ArmarXObjects/ObjectFinder.h> #include <RobotAPI/libraries/ArmarXObjects/forward_declarations.h> +#include <RobotAPI/libraries/armem/client/Prediction.h> #include <RobotAPI/libraries/armem/server/wm/memory_definitions.h> #include <RobotAPI/libraries/armem/server/segment/SpecializedSegment.h> @@ -104,7 +105,8 @@ namespace armarx::armem::server::obj::instance ::armarx::armem::articulated_object::ArticulatedObjects getArticulatedObjects(); - + static std::map<DateTime, ObjectPose> + getObjectPosesInRange(const wm::Entity& entity, const DateTime& start, const DateTime& end); private: @@ -139,7 +141,6 @@ namespace armarx::armem::server::obj::instance std::optional<wm::EntityInstance> findClassInstance(const ObjectID& objectID) const; - friend struct DetachVisitor; diff --git a/source/RobotAPI/libraries/armem_objects/server/instance/SegmentAdapter.cpp b/source/RobotAPI/libraries/armem_objects/server/instance/SegmentAdapter.cpp index b7f7a63085b31cb3672ad6c0307fc5afe2096c4e..7c7dac41c1a74bca1606721b86de565f821a7608 100644 --- a/source/RobotAPI/libraries/armem_objects/server/instance/SegmentAdapter.cpp +++ b/source/RobotAPI/libraries/armem_objects/server/instance/SegmentAdapter.cpp @@ -25,8 +25,14 @@ #include <RobotAPI/libraries/core/remoterobot/RemoteRobot.h> #include <RobotAPI/libraries/ArmarXObjects/aron_conversions.h> #include <RobotAPI/libraries/ArmarXObjects/ice_conversions.h> +#include <RobotAPI/libraries/ArmarXObjects/ObjectID.h> +#include <RobotAPI/libraries/ArmarXObjects/predictions.h> +#include <RobotAPI/libraries/armem_objects/aron/ObjectInstance.aron.generated.h> +#include <RobotAPI/libraries/aron/common/aron_conversions/core.h> +#include <ArmarXCore/core/ice_conversions/ice_conversions_templates.h> #include <ArmarXCore/core/time/CycleUtil.h> +#include <ArmarXCore/core/time/ice_conversions.h> #include <ArmarXCore/observers/variant/Variant.h> #include <VirtualRobot/Robot.h> @@ -88,6 +94,7 @@ namespace armarx::armem::server::obj::instance robotHead.fetchDatafields(); visu.arviz = arviz; + if (!visu.updateTask) { visu.updateTask = new SimpleRunningTask<>([this]() @@ -97,6 +104,7 @@ namespace armarx::armem::server::obj::instance visu.updateTask->start(); } + segment.connect(arviz); } @@ -431,8 +439,94 @@ namespace armarx::armem::server::obj::instance return robotHead.signalHeadMovement(input); } + objpose::ObjectPosePredictionResultSeq + SegmentAdapter::predictObjectPoses(const objpose::ObjectPosePredictionRequestSeq& requests, + const Ice::Current&) + { + objpose::ObjectPosePredictionResultSeq results; + std::vector<std::map<DateTime, objpose::ObjectPose>> poses; + std::vector<objpose::ObjectPose> latestPoses; + + segment.doLocked( + [this, &requests, &results, &poses, &latestPoses]() + { + for (const auto& request : requests) + { + auto& result = results.emplace_back(); + const ObjectID objID = armarx::fromIce(request.objectID); + + const Duration timeWindow = armarx::fromIce<Duration>(request.timeWindow); + + const wm::Entity* entity = segment.findObjectEntity(objID); + // Use result.success as a marker for whether to continue later + result.success = false; + poses.emplace_back(); + latestPoses.emplace_back(); + if (!entity) + { + std::stringstream sstream; + sstream << "Could not find object with ID " << objID << "."; + result.errorMessage = sstream.str(); + continue; + } + + const auto dto = entity->findLatestInstanceDataAs<arondto::ObjectInstance>(); + if (!dto) + { + std::stringstream sstream; + sstream << "No snapshots of the object " << objID << " were found." + << " Cannot make a prediction."; + result.errorMessage = sstream.str(); + continue; + } + + result.success = true; + poses.back() = segment.getObjectPosesInRange( + *entity, Time::Now() - timeWindow, Time::Invalid()); + latestPoses.back() = aron::fromAron<objpose::ObjectPose>(dto.value().pose); + } + }); + + for (size_t i = 0; i < requests.size(); ++i) + { + const objpose::ObjectPosePredictionRequest& request = requests.at(i); + objpose::ObjectPosePredictionResult& result = results.at(i); + + if (result.success) + { + armem::client::PredictionSettings settings = + armem::client::PredictionSettings::fromIce(request.settings); + + if (settings.predictionEngineID.empty() + or settings.predictionEngineID == "Linear Position Regression") + { + result = objpose::predictObjectPoseLinear( + poses.at(i), + armarx::fromIce<DateTime>(request.timestamp), + latestPoses.at(i)); + } + else + { + result.errorMessage = "Prediction engine '" + settings.predictionEngineID + + "' not available for object pose prediction."; + result.success = false; + } + } + } + return results; + } + + armem::prediction::data::PredictionEngineSeq + SegmentAdapter::getAvailableObjectPoseEngines(const Ice::Current&) + { + armem::prediction::data::PredictionEngine engine; + // TODO(phesch): Make this a constant somewhere + engine.engineID = "Linear Position Regression"; + return { engine }; + } - void SegmentAdapter::visualizeRun() + void + SegmentAdapter::visualizeRun() { ObjectFinder objectFinder; @@ -447,9 +541,10 @@ namespace armarx::armem::server::obj::instance TIMING_START(tVisu); objpose::ObjectPoseMap objectPoses; + std::map<ObjectID, std::map<DateTime, objpose::ObjectPose>> poseHistories; visu.minConfidence = -1; - segment.doLocked([this, &objectPoses, &objectFinder]() + segment.doLocked([this, &objectPoses, &poseHistories, &objectFinder]() { const Time now = Time::Now(); @@ -467,11 +562,27 @@ namespace armarx::armem::server::obj::instance { visu.minConfidence = segment.decay.removeObjectsBelowConfidence; } + + // Get histories. + for (const auto& [id, objectPose] : objectPoses) + { + const wm::Entity* entity = segment.findObjectEntity(id); + if (entity != nullptr) + { + poseHistories[id] = instance::Segment::getObjectPosesInRange( + *entity, + Time::Now() - Duration::SecondsDouble( + visu.linearPredictionTimeWindowSeconds), + Time::Invalid()); + } + } }); - const std::vector<viz::Layer> layers = visu.visualizeCommit(objectPoses, objectFinder); + const std::vector<viz::Layer> layers = + visu.visualizeCommit(objectPoses, poseHistories, objectFinder); arviz.commit(layers); + TIMING_END_STREAM(tVisu, ARMARX_VERBOSE); if (debugObserver) diff --git a/source/RobotAPI/libraries/armem_objects/server/instance/SegmentAdapter.h b/source/RobotAPI/libraries/armem_objects/server/instance/SegmentAdapter.h index 353f2e0644dc044edfd1f260a76eb0c5d7857a11..69595f7b640ec57cb1dcd7222233e7681f6f20e1 100644 --- a/source/RobotAPI/libraries/armem_objects/server/instance/SegmentAdapter.h +++ b/source/RobotAPI/libraries/armem_objects/server/instance/SegmentAdapter.h @@ -107,9 +107,16 @@ namespace armarx::armem::server::obj::instance virtual objpose::SignalHeadMovementOutput signalHeadMovement(const objpose::SignalHeadMovementInput& input, ICE_CURRENT_ARG) override; + // PREDICTING - private: + virtual objpose::ObjectPosePredictionResultSeq + predictObjectPoses(const objpose::ObjectPosePredictionRequestSeq& requests, + ICE_CURRENT_ARG) override; + + virtual armem::prediction::data::PredictionEngineSeq + getAvailableObjectPoseEngines(ICE_CURRENT_ARG) override; + private: void updateProviderInfo(const std::string& providerName, const objpose::ProviderInfo& info); void updateObjectPoses(const std::string& providerName, const objpose::data::ProvidedObjectPoseSeq& providedPoses); diff --git a/source/RobotAPI/libraries/armem_objects/server/instance/Visu.cpp b/source/RobotAPI/libraries/armem_objects/server/instance/Visu.cpp index e5bfd134140eae11fe3c6135d781dde2571f98eb..47206a42fa74036015b875f104c0503172d9ac2e 100644 --- a/source/RobotAPI/libraries/armem_objects/server/instance/Visu.cpp +++ b/source/RobotAPI/libraries/armem_objects/server/instance/Visu.cpp @@ -3,9 +3,14 @@ #include <SimoxUtility/math/pose.h> #include <SimoxUtility/math/rescale.h> +#include <ArmarXCore/core/time/ice_conversions.h> #include <ArmarXCore/core/time/TimeUtil.h> +#include <ArmarXCore/core/ice_conversions/ice_conversions_templates.h> +#include <RobotAPI/libraries/ArmarXObjects/ice_conversions.h> #include <RobotAPI/libraries/ArmarXObjects/ObjectFinder.h> +#include <RobotAPI/libraries/ArmarXObjects/predictions.h> +#include <RobotAPI/libraries/armem/client/Prediction.h> namespace armarx::armem::server::obj::instance @@ -45,43 +50,77 @@ namespace armarx::armem::server::obj::instance defs->optional(useArticulatedModels, prefix + "useArticulatedModels", "Prefer articulated object models if available."); + + defs->optional(showLinearPredictions, prefix + "predictions.linear.show", + "Show arrows linearly predicting object positions."); + defs->optional(linearPredictionTimeOffsetSeconds, prefix + "predictions.linear.timeOffset", + "The offset (in seconds) to the current time to make predictions for."); + defs->optional(linearPredictionTimeWindowSeconds, prefix + "predictions.linear.timeWindow", + "The time window (in seconds) into the past to perform the regression on."); } - std::vector<viz::Layer> Visu::visualizeCommit( + std::vector<viz::Layer> + Visu::visualizeCommit( const std::map<std::string, objpose::ObjectPoseSeq>& objectPoses, + const std::map<std::string, std::vector<std::map<DateTime, objpose::ObjectPose>>>& + poseHistories, const ObjectFinder& objectFinder) const { std::vector<viz::Layer> layers; for (const auto& [name, poses] : objectPoses) { - layers.push_back(visualizeProvider(name, poses, objectFinder)); + auto poseHistoryMap = poseHistories.find(name); + if (poseHistoryMap != poseHistories.end()) + { + layers.push_back(visualizeProvider(name, poses, poseHistoryMap->second, objectFinder)); + } + else + { + layers.push_back(visualizeProvider(name, poses, {}, objectFinder)); + } } return layers; } - std::vector<viz::Layer> Visu::visualizeCommit( - const objpose::ObjectPoseSeq& objectPoses, - const ObjectFinder& objectFinder) const + std::vector<viz::Layer> + Visu::visualizeCommit(const objpose::ObjectPoseSeq& objectPoses, + const std::vector<std::map<DateTime, objpose::ObjectPose>>& poseHistories, + const ObjectFinder& objectFinder) const { std::map<std::string, viz::Layer> stage; - for (const objpose::ObjectPose& objectPose : objectPoses) + for (size_t i = 0; i < objectPoses.size(); ++i) { - visualizeObjectPose(getLayer(objectPose.providerName, stage), objectPose, objectFinder); + visualizeObjectPose(getLayer(objectPoses.at(i).providerName, stage), + objectPoses.at(i), + poseHistories.at(i), + objectFinder); } return simox::alg::get_values(stage); } - std::vector<viz::Layer> Visu::visualizeCommit( + std::vector<viz::Layer> + Visu::visualizeCommit( const objpose::ObjectPoseMap& objectPoses, + const std::map<ObjectID, std::map<DateTime, objpose::ObjectPose>>& poseHistories, const ObjectFinder& objectFinder) const { std::map<std::string, viz::Layer> stage; - for (const auto& [id, objectPose] : objectPoses) + for (const auto& [id, pose] : objectPoses) { - visualizeObjectPose(getLayer(objectPose.providerName, stage), objectPose, objectFinder); + + auto poseHistoryMap = poseHistories.find(id); + if (poseHistoryMap != poseHistories.end()) + { + visualizeObjectPose( + getLayer(pose.providerName, stage), pose, poseHistoryMap->second, objectFinder); + } + else + { + visualizeObjectPose(getLayer(pose.providerName, stage), pose, {}, objectFinder); + } } return simox::alg::get_values(stage); } @@ -100,15 +139,17 @@ namespace armarx::armem::server::obj::instance } - viz::Layer Visu::visualizeProvider( + viz::Layer + Visu::visualizeProvider( const std::string& providerName, const objpose::ObjectPoseSeq& objectPoses, + const std::vector<std::map<DateTime, objpose::ObjectPose>>& poseHistories, const ObjectFinder& objectFinder) const { viz::Layer layer = arviz.layer(providerName); - for (const objpose::ObjectPose& objectPose : objectPoses) + for (size_t i = 0; i < poseHistories.size(); ++i) { - visualizeObjectPose(layer, objectPose, objectFinder); + visualizeObjectPose(layer, objectPoses.at(i), poseHistories.at(i), objectFinder); } return layer; } @@ -116,6 +157,7 @@ namespace armarx::armem::server::obj::instance void Visu::visualizeObjectPose( viz::Layer& layer, const objpose::ObjectPose& objectPose, + const std::map<DateTime, objpose::ObjectPose>& poseHistory, const ObjectFinder& objectFinder) const { const bool show = objectPose.confidence >= minConfidence; @@ -236,6 +278,28 @@ namespace armarx::armem::server::obj::instance } } } + if (showLinearPredictions) + { + auto predictionResult = objpose::predictObjectPoseLinear( + poseHistory, + Time::Now() + Duration::SecondsDouble(linearPredictionTimeOffsetSeconds), + objectPose); + if (predictionResult.success) + { + auto predictedPose = + armarx::fromIce<objpose::ObjectPose>(predictionResult.prediction); + Eigen::Vector3f predictedPosition = simox::math::position( + inGlobalFrame ? predictedPose.objectPoseGlobal : predictedPose.objectPoseRobot); + layer.add(viz::Arrow(key + " Linear Prediction") + .fromTo(simox::math::position(pose), predictedPosition) + .color(viz::Color::blue())); + } + else + { + ARMARX_INFO << "Linear prediction for visualization failed: " + << predictionResult.errorMessage; + } + } } @@ -270,6 +334,7 @@ namespace armarx::armem::server::obj::instance useArticulatedModels.setValue(visu.useArticulatedModels); + GridLayout grid; int row = 0; grid.add(Label("Enabled"), {row, 0}).add(enabled, {row, 1}); @@ -291,10 +356,13 @@ namespace armarx::armem::server::obj::instance grid.add(gaussians.group, {row, 0}, {1, 4}); row++; - grid.add(Label("Use Articulated Models"), {row, 0}).add(useArticulatedModels, {row, 1}); row++; + linearPredictions.setup(visu); + grid.add(linearPredictions.group, {row, 0}, {1, 4}); + row++; + group = GroupBox(); group.setLabel("Visualization"); group.addChild(grid); @@ -342,6 +410,48 @@ namespace armarx::armem::server::obj::instance visu.gaussiansOrientationDisplaced = gaussians.orientationDisplaced.getValue(); visu.useArticulatedModels = useArticulatedModels.getValue(); + + linearPredictions.update(visu); + } + + + void Visu::RemoteGui::LinearPredictions::setup(const Visu& visu) + { + using namespace armarx::RemoteGui::Client; + + show.setValue(visu.showLinearPredictions); + timeOffsetSeconds.setValue(visu.linearPredictionTimeOffsetSeconds); + timeOffsetSeconds.setRange(-1e6, 1e6); + timeOffsetSeconds.setSteps(2 * 2 * 1000 * 1000); + + timeWindowSeconds.setValue(visu.linearPredictionTimeWindowSeconds); + timeWindowSeconds.setRange(0, 1e6); + timeWindowSeconds.setSteps(2 * 1000 * 1000); + + + GridLayout grid; + int row = 0; + + grid.add(Label("Show"), {row, 0}).add(show, {row, 1}); + row++; + grid.add(Label("Time (seconds from now):"), {row, 0}) + .add(timeOffsetSeconds, {row, 1}); + row++; + grid.add(Label("Time Window (seconds):"), {row, 0}) + .add(timeWindowSeconds, {row, 1}); + row++; + + group = GroupBox(); + group.setLabel("Linear Predictions"); + group.addChild(grid); + } + + + void Visu::RemoteGui::LinearPredictions::update(Visu& visu) + { + visu.showLinearPredictions = show.getValue(); + visu.linearPredictionTimeOffsetSeconds = timeOffsetSeconds.getValue(); + visu.linearPredictionTimeWindowSeconds = timeWindowSeconds.getValue(); } } diff --git a/source/RobotAPI/libraries/armem_objects/server/instance/Visu.h b/source/RobotAPI/libraries/armem_objects/server/instance/Visu.h index f1ce436435eec9de5c9fd1cbffcea98bc7201410..0341f514421e10a9158a446c1cb2c4a7eb3625e6 100644 --- a/source/RobotAPI/libraries/armem_objects/server/instance/Visu.h +++ b/source/RobotAPI/libraries/armem_objects/server/instance/Visu.h @@ -7,6 +7,7 @@ #include <ArmarXGui/libraries/RemoteGui/Client/Widgets.h> #include <RobotAPI/components/ArViz/Client/Client.h> +#include <RobotAPI/interface/objectpose/ObjectPoseStorageInterface.h> #include <RobotAPI/libraries/ArmarXObjects/ObjectPose.h> @@ -29,22 +30,26 @@ namespace armarx::armem::server::obj::instance std::vector<viz::Layer> visualizeCommit( const std::map<std::string, objpose::ObjectPoseSeq>& objectPoses, - const ObjectFinder& objectFinder - ) const; + const std::map<std::string, std::vector<std::map<DateTime, objpose::ObjectPose>>>& + poseHistories, + const ObjectFinder& objectFinder) const; /// Visualize the given object poses, with one layer per provider. std::vector<viz::Layer> visualizeCommit( const objpose::ObjectPoseSeq& objectPoses, + const std::vector<std::map<DateTime, objpose::ObjectPose>>& poseHistories, const ObjectFinder& objectFinder ) const; std::vector<viz::Layer> visualizeCommit( const objpose::ObjectPoseMap& objectPoses, + const std::map<ObjectID, std::map<DateTime, objpose::ObjectPose>>& poseHistories, const ObjectFinder& objectFinder ) const; viz::Layer visualizeProvider( const std::string& providerName, const objpose::ObjectPoseSeq& objectPoses, + const std::vector<std::map<DateTime, objpose::ObjectPose>>& poseHistories, const ObjectFinder& objectFinder ) const; @@ -52,6 +57,7 @@ namespace armarx::armem::server::obj::instance void visualizeObjectPose( viz::Layer& layer, const objpose::ObjectPose& objectPose, + const std::map<DateTime, objpose::ObjectPose>& poseHistory, const ObjectFinder& objectFinder ) const; @@ -86,8 +92,12 @@ namespace armarx::armem::server::obj::instance /// Prefer articulated models if available. bool useArticulatedModels = true; - SimpleRunningTask<>::pointer_type updateTask; + /// Linear prediction arrows for object positions. + bool showLinearPredictions = false; + float linearPredictionTimeOffsetSeconds = 1; + float linearPredictionTimeWindowSeconds = 2; + SimpleRunningTask<>::pointer_type updateTask; struct RemoteGui { @@ -121,6 +131,20 @@ namespace armarx::armem::server::obj::instance armarx::RemoteGui::Client::CheckBox useArticulatedModels; + struct LinearPredictions + { + armarx::RemoteGui::Client::CheckBox show; + + armarx::RemoteGui::Client::FloatSpinBox timeOffsetSeconds; + armarx::RemoteGui::Client::FloatSpinBox timeWindowSeconds; + + armarx::RemoteGui::Client::GroupBox group; + + void setup(const Visu& data); + void update(Visu& data); + }; + LinearPredictions linearPredictions; + void setup(const Visu& visu); void update(Visu& visu); };