diff --git a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp
index 31c31934b335d79b7eb426e6606179c21d910546..760af325a522618218a7d39ceaf45d9fa20fa65a 100644
--- a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp
+++ b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.cpp
@@ -30,10 +30,14 @@
 #include <VirtualRobot/Robot.h>
 #include <VirtualRobot/RobotConfig.h>
 
+#include <ArmarXCore/core/exceptions/local/UnexpectedEnumValueException.h>
+#include <ArmarXCore/observers/variant/Variant.h>
+
 #include <RobotAPI/libraries/core/Pose.h>
 #include <RobotAPI/libraries/core/FramedPose.h>
 #include <RobotAPI/libraries/ArmarXObjects/ice_conversions.h>
 
+
 namespace armarx
 {
 
@@ -47,6 +51,8 @@ namespace armarx
         armarx::PropertyDefinitionsPtr defs(new ObjectPoseObserverPropertyDefinitions(getConfigIdentifier()));
 
         defs->defineOptionalProperty<std::string>("ObjectPoseTopicName", "ObjectPoseTopic", "Name of the Object Pose Topic.");
+        defs->defineOptionalProperty<std::string>("KinematicUnitObserverName", "KinematicUnitObserver", "Name of the kinematic unit observer.");
+        defs->topic(debugObserver);
 
         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.");
@@ -54,8 +60,13 @@ namespace armarx
         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");
+
+        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.");
+
+        defs->optional(robotHead.checkHeadVelocity, "head.checkHeadVelocity", "If true, check whether the head is moving and discard updates in the meantime.");
+        defs->optional(robotHead.maxJointVelocity, "head.maxJointVelocity", "If a head joint's velocity is higher, the head is considered moving.");
+        defs->optional(robotHead.discardIntervalAfterMoveMS, "head.discardIntervalAfterMoveMS", "For how long new updates are ignored after moving the head.");
 
         return defs;
     }
@@ -67,6 +78,8 @@ namespace armarx
 
     void ObjectPoseObserver::onInitObserver()
     {
+        robotHead.setTag(getName());
+
         usingTopicFromProperty("ObjectPoseTopicName");
     }
 
@@ -79,6 +92,10 @@ namespace armarx
             robot = RobotState::addRobot("robot", VirtualRobot::RobotIO::RobotDescription::eStructure);
         }
 
+        getProxyFromProperty(robotHead.kinematicUnitObserver, "KinematicUnitObserverName", false, "", false);
+        robotHead.debugObserver = debugObserver;
+        robotHead.fetchDatafields();
+
         createRemoteGuiTab();
         RemoteGui_startRunningTask();
     }
@@ -92,62 +109,6 @@ namespace armarx
     }
 
 
-    void ObjectPoseObserver::createRemoteGuiTab()
-    {
-        using namespace armarx::RemoteGui::Client;
-
-        tab.visuEnabled.setValue(visu.enabled);
-        tab.visuInGlobalFrame.setValue(visu.inGlobalFrame);
-        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;
-        {
-            GridLayout grid;
-            int row = 0;
-            grid.add(Label("Enabled"), {row, 0}).add(tab.visuEnabled, {row, 1});
-            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}, {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);
-        }
-
-        VBoxLayout root = {visuGroup, VSpacer()};
-        RemoteGui_createTab(getName(), root, &tab);
-    }
-
-    void ObjectPoseObserver::RemoteGui_update()
-    {
-        // Non-atomic variables need to be guarded by a mutex if accessed by multiple threads
-        std::scoped_lock lock(dataMutex);
-
-        visu.enabled = tab.visuEnabled.getValue();
-        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();
-    }
-
-
     void ObjectPoseObserver::reportProviderAvailable(const std::string& providerName, const objpose::ProviderInfo& info, const Ice::Current&)
     {
         updateProviderInfo(providerName, info);
@@ -159,7 +120,45 @@ namespace armarx
     {
         ARMARX_VERBOSE << "Received object poses from '" << providerName << "'.";
 
-        objpose::ObjectPoseSeq objectPoses;
+        std::optional<IceUtil::Time> discardUpdatesUntil;
+        bool discardAll = false;
+        {
+            std::scoped_lock lock(robotHeadMutex);
+            if (robotHead.checkHeadVelocity)
+            {
+                if (robotHead.isMoving())
+                {
+                    robotHead.movementStarts(robotHead.discardIntervalAfterMoveMS);
+                    // ARMARX_IMPORTANT << "Ignoring pose update because robot head is moving! until " << robotHead.discardUpdatesUntil;
+                    discardAll = true;
+                }
+                else if (TimeUtil::GetTime() < robotHead.discardUpdatesUntil)
+                {
+                    discardAll = true;
+                    // ARMARX_IMPORTANT << "Ignoring pose update because robot head has moved until: " << robotHead.discardUpdatesUntil;
+                }
+                else
+                {
+                    discardUpdatesUntil = robotHead.discardUpdatesUntil;
+                }
+            }
+        }
+        if (debugObserver)
+        {
+            StringVariantBaseMap map =
+            {
+                { "Discarding All Updates", new Variant(discardAll ? 1.f : 0.f) },
+            };
+            if (discardAll)
+            {
+                map["Proportion Updated Poses"] = new Variant(0.f);
+            }
+            debugObserver->setDebugChannel(getName(), map);
+        }
+        if (discardAll)
+        {
+            return;
+        }
         {
             std::scoped_lock lock(dataMutex);
             RobotState::synchronizeLocalClone(robot);
@@ -171,28 +170,80 @@ namespace armarx
                 robotNode->setJointValue(value + calibration.offset);
             }
 
+            // This stays empty on the first report.
+            objpose::ObjectPoseSeq previousPoses;
+            if (auto it = this->objectPoses.find(providerName); it != this->objectPoses.end())
+            {
+                previousPoses = it->second;
+            }
+
+            // Build new poses.
+            objpose::ObjectPoseSeq newObjectPoses;
+            int numUpdated = 0;
             for (const objpose::data::ProvidedObjectPose& provided : providedPoses)
             {
-                objpose::ObjectPose& pose = objectPoses.emplace_back();
-                pose.fromProvidedPose(provided, robot);
-                if (pose.objectID.dataset().empty())
+                IceUtil::Time timestamp = IceUtil::Time::microSeconds(provided.timestampMicroSeconds);
+
+                // Check whether we have an old pose for this object.
+                std::optional<objpose::ObjectPose> previousPose;
+                for (const objpose::ObjectPose& prev : previousPoses)
                 {
-                    // Try to find the data set. (It might be good to cache this.)
-                    if (std::optional<ObjectInfo> objectInfo = objectFinder.findObject(pose.objectID))
+                    if (prev.objectID == fromIce(provided.objectID))
+                    {
+                        previousPose = prev;
+                    }
+                }
+
+                if (discardUpdatesUntil && timestamp < *discardUpdatesUntil)
+                {
+                    if (previousPose)
+                    {
+                        // Keep the old one
+                        newObjectPoses.push_back(*previousPose);
+                    }
+                    else
                     {
-                        pose.objectID = { objectInfo->dataset(), pose.objectID.className(), pose.objectID.instanceName() };
+                        // Discard the new pose.
+                        // ARMARX_IMPORTANT << "Ignoring update of object " << provided.objectID << " because robot head is moved.\n"
+                        //                  << "timestamp " << timestamp << " < " << robotHead.discardUpdatesUntil;
                     }
                 }
-                if (!provided.localOOBB)
+                else if (previousPose && timestamp == previousPose->timestamp)
                 {
-                    pose.localOOBB = getObjectOOBB(pose.objectID);
+                    // Keep the old one.
+                    newObjectPoses.push_back(*previousPose);
+                }
+                else
+                {
+                    numUpdated++;
+                    objpose::ObjectPose& newPose = newObjectPoses.emplace_back();
+                    newPose.fromProvidedPose(provided, robot);
+                    if (newPose.objectID.dataset().empty())
+                    {
+                        // Try to find the data set. (It might be good to cache this.)
+                        if (std::optional<ObjectInfo> objectInfo = objectFinder.findObject(newPose.objectID))
+                        {
+                            newPose.objectID = { objectInfo->dataset(), newPose.objectID.className(), newPose.objectID.instanceName() };
+                        }
+                    }
+                    if (!provided.localOOBB)
+                    {
+                        // Try to load oobb from disk.
+                        newPose.localOOBB = getObjectOOBB(newPose.objectID);
+                    }
                 }
             }
-        }
 
-        {
-            std::scoped_lock lock(dataMutex);
-            this->objectPoses[providerName] = objectPoses;
+            if (debugObserver)
+            {
+                debugObserver->setDebugChannel(getName(),
+                {
+                    { "Discarding All Updates", new Variant(discardAll ? 1 : 0) },
+                    { "Proportion Updated Poses", new Variant(static_cast<float>(numUpdated) / providedPoses.size()) }
+                });
+            }
+
+            this->objectPoses[providerName] = newObjectPoses;
             handleProviderUpdate(providerName);
         }
     }
@@ -553,6 +604,28 @@ namespace armarx
         return output;
     }
 
+    objpose::SignalHeadMovementOutput
+    ObjectPoseObserver::signalHeadMovement(const objpose::SignalHeadMovementInput& input, const Ice::Current&)
+    {
+        std::scoped_lock lock(robotHeadMutex);
+        objpose::SignalHeadMovementOutput output;
+        switch (input.action)
+        {
+            case objpose::HeadMovementAction::HeadMovementAction_Starting:
+                robotHead.movementStarts(input.discardUpdatesIntervalMilliSeconds < 0
+                                         ? robotHead.discardIntervalAfterMoveMS
+                                         : input.discardUpdatesIntervalMilliSeconds);
+                break;
+            case objpose::HeadMovementAction::HeadMovementAction_Stopping:
+                robotHead.movementStops(input.discardUpdatesIntervalMilliSeconds);
+                break;
+            default:
+                ARMARX_UNEXPECTED_ENUM_VALUE(objpose::HeadMovementaction, input.action);
+                break;
+        }
+        output.discardUpdatesUntilMilliSeconds = robotHead.discardUpdatesUntil.toMilliSeconds();
+        return output;
+    }
 
 
     void ObjectPoseObserver::handleProviderUpdate(const std::string& providerName)
@@ -760,5 +833,177 @@ namespace armarx
         }
     }
 
+    void ObjectPoseObserver::RobotHeadMovement::fetchDatafields()
+    {
+        if (kinematicUnitObserver)
+        {
+            for (const std::string& jointName : jointNames)
+            {
+                std::string error = "";
+                try
+                {
+                    DatafieldRefBasePtr datafield = kinematicUnitObserver->getDatafieldRefByName(jointVelocitiesChannelName, jointName);
+                    DatafieldRefPtr casted = DatafieldRefPtr::dynamicCast(datafield);
+                    if (casted)
+                    {
+                        jointVelocitiesDatafields.push_back(casted);
+                    }
+                }
+                catch (const InvalidDatafieldException& e)
+                {
+                    error = e.what();
+                }
+                catch (const InvalidChannelException& e)
+                {
+                    error = e.what();
+                }
+                if (error.size() > 0)
+                {
+                    ARMARX_WARNING << "Could not get datafield for joint '" << jointName << "' in channel '" << jointVelocitiesChannelName << "': \n "
+                                   << error;
+                }
+            }
+        }
+        else
+        {
+            ARMARX_INFO << "Cannot fetch joint velocity datafields because there is no kinematic unit observer.";
+        }
+    }
+
+    bool ObjectPoseObserver::RobotHeadMovement::isMoving() const
+    {
+        for (DatafieldRefPtr df : jointVelocitiesDatafields)
+        {
+            if (df)
+            {
+                float jointVelocity = df->getFloat();
+                // ARMARX_IMPORTANT << "Is " << df->datafieldName << " " << VAROUT(std::abs(jointVelocity)) << " > " << VAROUT(maxJointVelocity) << "?";
+                if (std::abs(jointVelocity) > maxJointVelocity)
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    void ObjectPoseObserver::RobotHeadMovement::movementStarts(long discardIntervalMs)
+    {
+        return movementStarts(IceUtil::Time::milliSeconds(discardIntervalMs));
+    }
+    void ObjectPoseObserver::RobotHeadMovement::movementStarts(IceUtil::Time discardInterval)
+    {
+        discardUpdatesUntil = TimeUtil::GetTime() + discardInterval;
+    }
+    void ObjectPoseObserver::RobotHeadMovement::movementStops(long discardIntervalMs)
+    {
+        return movementStops(IceUtil::Time::milliSeconds(discardIntervalMs));
+    }
+    void ObjectPoseObserver::RobotHeadMovement::movementStops(IceUtil::Time discardInterval)
+    {
+        if (discardInterval.toMilliSeconds() < 0)
+        {
+            //  Stop discarding.
+            discardUpdatesUntil = IceUtil::Time::milliSeconds(-1);
+        }
+        else
+        {
+            // Basically the same as starting.
+            discardUpdatesUntil = TimeUtil::GetTime() + discardInterval;
+        }
+    }
+
+
+    void ObjectPoseObserver::createRemoteGuiTab()
+    {
+        using namespace armarx::RemoteGui::Client;
+
+        GroupBox visuGroup;
+        {
+            tab.visu.enabled.setValue(visu.enabled);
+            tab.visu.inGlobalFrame.setValue(visu.inGlobalFrame);
+            tab.visu.alpha.setRange(0, 1.0);
+            tab.visu.alpha.setValue(visu.alpha);
+            tab.visu.oobbs.setValue(visu.oobbs);
+            tab.visu.objectFrames.setValue(visu.objectFrames);
+            {
+                float max = 10000;
+                tab.visu.objectFramesScale.setRange(0, max);
+                tab.visu.objectFramesScale.setDecimals(2);
+                tab.visu.objectFramesScale.setSteps(int(10 * max));
+                tab.visu.objectFramesScale.setValue(visu.objectFramesScale);
+            }
+
+            GridLayout grid;
+            int row = 0;
+            grid.add(Label("Enabled"), {row, 0}).add(tab.visu.enabled, {row, 1});
+            row++;
+            grid.add(Label("Global Frame"), {row, 0}).add(tab.visu.inGlobalFrame, {row, 1});
+            row++;
+            grid.add(Label("Alpha"), {row, 0}).add(tab.visu.alpha, {row, 1}, {1, 3});
+            row++;
+            grid.add(Label("OOBB"), {row, 0}).add(tab.visu.oobbs, {row, 1});
+            row++;
+            grid.add(Label("Object Frames"), {row, 0}).add(tab.visu.objectFrames, {row, 1});
+            grid.add(Label("Scale:"), {row, 2}).add(tab.visu.objectFramesScale, {row, 3});
+            row++;
+
+            visuGroup.setLabel("Visualization");
+            visuGroup.addChild(grid);
+        }
+
+        GroupBox robotHeadGroup;
+        {
+            tab.robotHead.checkHeadVelocity.setValue(robotHead.checkHeadVelocity);
+            {
+                float max = 10.0;
+                tab.robotHead.maxJointVelocity.setRange(0, max);
+                tab.robotHead.maxJointVelocity.setDecimals(3);
+                tab.robotHead.maxJointVelocity.setSteps(int(100 * max));  // = 0.01 steps
+                tab.robotHead.maxJointVelocity.setValue(robotHead.maxJointVelocity);
+            }
+            {
+                int max = 10 * 1000;
+                tab.robotHead.discardIntervalAfterMoveMS.setRange(0, max);
+                tab.robotHead.discardIntervalAfterMoveMS.setValue(robotHead.discardIntervalAfterMoveMS);
+            }
+
+            GridLayout grid;
+            int row = 0;
+            grid.add(Label("Check Head Motion"), {row, 0}).add(tab.robotHead.checkHeadVelocity, {row, 1});
+            row++;
+            grid.add(Label("Max Joint Velocity"), {row, 0}).add(tab.robotHead.maxJointVelocity, {row, 1});
+            row++;
+            grid.add(Label("Discard Interval after Move [ms]"), {row, 0}).add(tab.robotHead.discardIntervalAfterMoveMS, {row, 1});
+            row++;
+
+            robotHeadGroup.setLabel("Robot Head Motion");
+            robotHeadGroup.addChild(grid);
+        }
+
+        VBoxLayout root = {visuGroup, robotHeadGroup, VSpacer()};
+        RemoteGui_createTab(getName(), root, &tab);
+    }
+
+    void ObjectPoseObserver::RemoteGui_update()
+    {
+        // Non-atomic variables need to be guarded by a mutex if accessed by multiple threads
+        {
+            std::scoped_lock lock(dataMutex);
+
+            visu.enabled = tab.visu.enabled.getValue();
+            visu.inGlobalFrame = tab.visu.inGlobalFrame.getValue();
+            visu.alpha = tab.visu.alpha.getValue();
+            visu.oobbs = tab.visu.oobbs.getValue();
+            visu.objectFrames = tab.visu.objectFrames.getValue();
+            visu.objectFramesScale = tab.visu.objectFramesScale.getValue();
+        }
+        {
+            std::scoped_lock lock(robotHeadMutex);
+            robotHead.checkHeadVelocity = tab.robotHead.checkHeadVelocity.getValue();
+            robotHead.maxJointVelocity = tab.robotHead.maxJointVelocity.getValue();
+            robotHead.discardIntervalAfterMoveMS = tab.robotHead.discardIntervalAfterMoveMS.getValue();
+        }
+    }
 }
 
diff --git a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h
index 70a0e80abdb6983e58205c44d3a3c3ccc2ea3de3..5022afbf2c92b7cba7a7c2455dbe26d0e3c1f88f 100644
--- a/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h
+++ b/source/RobotAPI/components/ObjectPoseObserver/ObjectPoseObserver.h
@@ -26,11 +26,14 @@
 
 #include <SimoxUtility/caching/CacheMap.h>
 
+#include <ArmarXCore/core/logging/Logging.h>
 #include <ArmarXCore/observers/Observer.h>
+#include <ArmarXCore/observers/variant/DatafieldRef.h>
 
 #include <ArmarXGui/libraries/ArmarXGuiComponentPlugins/LightweightRemoteGuiComponentPlugin.h>
 
 #include <RobotAPI/interface/objectpose/ObjectPoseObserver.h>
+#include <RobotAPI/interface/observers/KinematicUnitObserverInterface.h>
 #include <RobotAPI/libraries/RobotAPIComponentPlugins/ArVizComponentPlugin.h>
 #include <RobotAPI/libraries/RobotAPIComponentPlugins/RobotStateComponentPlugin.h>
 #include <RobotAPI/libraries/ArmarXObjects/ObjectFinder.h>
@@ -72,7 +75,6 @@ namespace armarx
         , virtual public armarx::RobotStateComponentPluginUser
         , virtual public armarx::LightweightRemoteGuiComponentPluginUser
         , virtual public armarx::ArVizComponentPluginUser
-
     {
     public:
         using RobotState = armarx::RobotStateComponentPluginUser;
@@ -114,6 +116,11 @@ namespace armarx
 
         objpose::AgentFramesSeq getAttachableFrames(ICE_CURRENT_ARG) override;
 
+        // HEAD MOVEMENT SIGNALS
+
+        objpose::SignalHeadMovementOutput signalHeadMovement(const objpose::SignalHeadMovementInput& input, ICE_CURRENT_ARG) override;
+
+
 
         // Remote GUI
         void createRemoteGuiTab();
@@ -122,13 +129,14 @@ namespace armarx
 
     protected:
 
+        armarx::PropertyDefinitionsPtr createPropertyDefinitions() override;
+
         void onInitObserver() override;
         void onConnectObserver() override;
 
         void onDisconnectComponent() override;
         void onExitObserver() override;
 
-        armarx::PropertyDefinitionsPtr createPropertyDefinitions() override;
 
     private:
 
@@ -151,12 +159,12 @@ namespace armarx
         void toIceWithAttachments(objpose::ObjectPoseSeq& objectPoses, VirtualRobot::RobotPtr agent,
                                   objpose::data::ObjectPoseSeq& result, bool& synchronized);
 
-
     private:
 
         std::mutex dataMutex;
 
         VirtualRobot::RobotPtr robot;
+        DebugObserverInterfacePrx debugObserver;
 
 
         objpose::ProviderInfoMap providers;
@@ -171,6 +179,35 @@ namespace armarx
         /// Caches results of attempts to retrieve the OOBB from ArmarXObjects.
         simox::caching::CacheMap<ObjectID, std::optional<simox::OrientedBoxf>> oobbCache;
 
+
+        class RobotHeadMovement : public armarx::Logging
+        {
+        public:
+
+            bool checkHeadVelocity = true;
+
+            std::string jointVelocitiesChannelName = "jointvelocities";
+            std::vector<std::string> jointNames = {"Neck_1_Yaw", "Neck_2_Pitch"};
+            float maxJointVelocity = 0.05f;
+            int discardIntervalAfterMoveMS = 100;
+
+            KinematicUnitObserverInterfacePrx kinematicUnitObserver;
+            std::vector<DatafieldRefPtr> jointVelocitiesDatafields;
+            IceUtil::Time discardUpdatesUntil = IceUtil::Time::seconds(-1);
+
+            DebugObserverInterfacePrx debugObserver;
+
+            void fetchDatafields();
+            bool isMoving() const;
+
+            void movementStarts(long discardIntervalMs);
+            void movementStarts(IceUtil::Time discardInterval);
+            void movementStops(long discardIntervalMs);
+            void movementStops(IceUtil::Time discardInterval);
+        };
+        RobotHeadMovement robotHead;
+        std::mutex robotHeadMutex;
+
         struct Visu
         {
             bool enabled = false;
@@ -192,12 +229,24 @@ namespace armarx
 
         struct RemoteGuiTab : RemoteGui::Client::Tab
         {
-            RemoteGui::Client::CheckBox visuEnabled;
-            RemoteGui::Client::CheckBox visuInGlobalFrame;
-            RemoteGui::Client::FloatSlider visuAlpha;
-            RemoteGui::Client::CheckBox visuOOBBs;
-            RemoteGui::Client::CheckBox visuObjectFrames;
-            RemoteGui::Client::FloatSpinBox visuObjectFramesScale;
+            struct Visu
+            {
+                RemoteGui::Client::CheckBox enabled;
+                RemoteGui::Client::CheckBox inGlobalFrame;
+                RemoteGui::Client::FloatSlider alpha;
+                RemoteGui::Client::CheckBox oobbs;
+                RemoteGui::Client::CheckBox objectFrames;
+                RemoteGui::Client::FloatSpinBox objectFramesScale;
+            };
+            Visu visu;
+
+            struct RobotHead
+            {
+                RemoteGui::Client::CheckBox checkHeadVelocity;
+                RemoteGui::Client::FloatSpinBox maxJointVelocity;
+                RemoteGui::Client::IntSpinBox discardIntervalAfterMoveMS;
+            };
+            RobotHead robotHead;
         };
         RemoteGuiTab tab;
 
diff --git a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp
index 60672d5da0b446160b6e0a075a52303a44d10c79..950edbf74b1bdc1516614b7dfc8ac5c9a9598845 100644
--- a/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp
+++ b/source/RobotAPI/components/units/ObstacleAvoidingPlatformUnit/ObstacleAvoidingPlatformUnit.cpp
@@ -359,8 +359,8 @@ armarx::ObstacleAvoidingPlatformUnit::get_velocities()
     ARMARX_CHECK(modulated_vel.allFinite()) << "Velocity contains non-finite values.";
     ARMARX_CHECK(std::isfinite(target_rot_vel)) << "Rotation velocity is non-finite.";
 
-    ARMARX_DEBUG << "Target velocity:  " << target_vel.transpose() << "; " << target_rot_vel;
-    ARMARX_DEBUG << "Modulated velocity:  " << modulated_vel.transpose();
+    ARMARX_DEBUG << "Target velocity:  " << target_vel.transpose() << "; norm: " << target_vel.norm() << "; " << target_rot_vel;
+    ARMARX_DEBUG << "Modulated velocity:  " << modulated_vel.transpose() << modulated_vel.norm();
 
     const auto r = Eigen::Rotation2D(m_control_data.agent_ori).toRotationMatrix().inverse();
 
@@ -802,14 +802,16 @@ armarx::ObstacleAvoidingPlatformUnit::createPropertyDefinitions()
 
     // Settings.
     def->optional(m_control_data.adaptive_max_vel_exp, "adaptive_max_vel_exponent",
-                  "Adaptive max vel exponent.  0 = disable.");
+                  "Adaptive max vel exponent.  This throttles the max_vel adaptively "
+                  "depending on the angle between target velocity and modulated velocity.  "
+                  "0 = disable.");
 
     // Control parameters.
     def->optional(m_control_data.kp, "control.pos.kp");
     def->optional(m_rot_pid_controller.Kp, "control.rot.kp");
     def->optional(m_rot_pid_controller.Ki, "control.rot.ki");
     def->optional(m_rot_pid_controller.Kd, "control.rot.kd");
-    def->optional(m_control_loop.cycle_time, "control.pose.cycle_time");
+    def->optional(m_control_loop.cycle_time, "control.pose.cycle_time", "Control loop cycle time.");
 
     // Obstacle avoidance parameter.
     def->optional(m_control_data.agent_safety_margin, "doa.agent_safety_margin");
diff --git a/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidget.ui b/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidget.ui
index 85819912107bc3006fb94cec8a71428f57e0481f..fef69f4366e204f0a27fa3857e0221d7d7dba2bb 100644
--- a/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidget.ui
+++ b/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidget.ui
@@ -135,6 +135,13 @@
              </property>
             </spacer>
            </item>
+           <item>
+            <widget class="QPushButton" name="pushButtonCopyCurrentPose">
+             <property name="text">
+              <string>copy current pose</string>
+             </property>
+            </widget>
+           </item>
            <item>
             <widget class="QLabel" name="labelParsingSuccess">
              <property name="text">
@@ -524,6 +531,16 @@
            </item>
           </layout>
          </item>
+         <item row="7" column="2" colspan="6">
+          <widget class="QCheckBox" name="checkBoxSkipWaypoints">
+           <property name="text">
+            <string>Skip waypoints</string>
+           </property>
+           <property name="checked">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
         </layout>
        </widget>
       </item>
diff --git a/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidgetController.cpp b/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidgetController.cpp
index 7f245db4be10b20612849486bb388071c1504875..303aa4ecdeabc7030e74b432858cb9c063b7f5bc 100644
--- a/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidgetController.cpp
+++ b/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidgetController.cpp
@@ -23,6 +23,8 @@
 #include <random>
 #include <string>
 
+#include <QClipboard>
+
 #include <SimoxUtility/json.h>
 
 #include <ArmarXCore/util/CPPUtility/container.h>
@@ -38,13 +40,14 @@ namespace armarx
         using T = CartesianWaypointControlGuiWidgetController;
         std::lock_guard g{_allMutex};
         _ui.setupUi(getWidget());
-        connect(_ui.radioButtonWPJson, &QPushButton::clicked, this, &T::triggerParsing);
-        connect(_ui.radioButton4f,     &QPushButton::clicked, this, &T::triggerParsing);
-        connect(_ui.textEditWPs,       &QTextEdit::textChanged, this, &T::triggerParsing);
+        connect(_ui.radioButtonWPJson,         &QPushButton::clicked,   this, &T::triggerParsing);
+        connect(_ui.radioButton4f,             &QPushButton::clicked,   this, &T::triggerParsing);
+        connect(_ui.textEditWPs,               &QTextEdit::textChanged, this, &T::triggerParsing);
 
-        connect(_ui.pushButtonExecute,      &QPushButton::clicked, this, &T::on_pushButtonExecute_clicked);
-        connect(_ui.pushButtonZeroFT,       &QPushButton::clicked, this, &T::on_pushButtonZeroFT_clicked);
-        connect(_ui.pushButtonSendSettings, &QPushButton::clicked, this, &T::on_pushButtonSendSettings_clicked);
+        connect(_ui.pushButtonExecute,         &QPushButton::clicked,   this, &T::on_pushButtonExecute_clicked);
+        connect(_ui.pushButtonZeroFT,          &QPushButton::clicked,   this, &T::on_pushButtonZeroFT_clicked);
+        connect(_ui.pushButtonSendSettings,    &QPushButton::clicked,   this, &T::on_pushButtonSendSettings_clicked);
+        connect(_ui.pushButtonCopyCurrentPose, &QPushButton::clicked,   this, &T::copyCurrentPose);
 
         connectCreateAcivateDeactivateDelete(
             _ui.pushButtonCreateController,
@@ -88,6 +91,28 @@ namespace armarx
         }
     }
 
+    void CartesianWaypointControlGuiWidgetController::copyCurrentPose()
+    {
+        std::lock_guard g{_allMutex};
+        if (!_robot)
+        {
+            return;
+        }
+        synchronizeLocalClone(_robot);
+        const auto rns = _robot->getRobotNodeSet(_ui.comboBoxChain->currentText().toStdString());
+        if (!rns)
+        {
+            return;
+        }
+        const auto tcp = rns->getTCP();
+        if (!tcp)
+        {
+            return;
+        }
+        const auto str = simox::json::eigen4f2posquatJson(tcp->getPoseInRootFrame());
+        QApplication::clipboard()->setText(QString::fromStdString(str));
+    }
+
     void CartesianWaypointControlGuiWidgetController::on_pushButtonZeroFT_clicked()
     {
         std::lock_guard g{_allMutex};
@@ -148,26 +173,27 @@ namespace armarx
         std::lock_guard g{_allMutex};
         NJointCartesianWaypointControllerRuntimeConfig cfg;
 
-        cfg.wpCfg.maxPositionAcceleration     = static_cast<float>(_ui.doubleSpinBoxMaxAccPos->value());
-        cfg.wpCfg.maxOrientationAcceleration  = static_cast<float>(_ui.doubleSpinBoxMaxAccOri->value());
-        cfg.wpCfg.maxNullspaceAcceleration    = static_cast<float>(_ui.doubleSpinBoxMaxAccNull->value());
+        cfg.wpCfg.maxPositionAcceleration       = static_cast<float>(_ui.doubleSpinBoxMaxAccPos->value());
+        cfg.wpCfg.maxOrientationAcceleration    = static_cast<float>(_ui.doubleSpinBoxMaxAccOri->value());
+        cfg.wpCfg.maxNullspaceAcceleration      = static_cast<float>(_ui.doubleSpinBoxMaxAccNull->value());
 
-        cfg.wpCfg.kpJointLimitAvoidance       = static_cast<float>(_ui.doubleSpinBoxLimitAvoidKP->value());
-        cfg.wpCfg.jointLimitAvoidanceScale    = static_cast<float>(_ui.doubleSpinBoxLimitAvoidScale->value());
+        cfg.wpCfg.kpJointLimitAvoidance         = static_cast<float>(_ui.doubleSpinBoxLimitAvoidKP->value());
+        cfg.wpCfg.jointLimitAvoidanceScale      = static_cast<float>(_ui.doubleSpinBoxLimitAvoidScale->value());
 
-        cfg.wpCfg.thresholdOrientationNear    = static_cast<float>(_ui.doubleSpinBoxOriNear->value());
-        cfg.wpCfg.thresholdOrientationReached = static_cast<float>(_ui.doubleSpinBoxOriReached->value());
-        cfg.wpCfg.thresholdPositionNear       = static_cast<float>(_ui.doubleSpinBoxPosNear->value());
-        cfg.wpCfg.thresholdPositionReached    = static_cast<float>(_ui.doubleSpinBoxPosReached->value());
+        cfg.wpCfg.thresholdOrientationNear      = static_cast<float>(_ui.doubleSpinBoxOriNear->value());
+        cfg.wpCfg.thresholdOrientationReached   = static_cast<float>(_ui.doubleSpinBoxOriReached->value());
+        cfg.wpCfg.thresholdPositionNear         = static_cast<float>(_ui.doubleSpinBoxPosNear->value());
+        cfg.wpCfg.thresholdPositionReached      = static_cast<float>(_ui.doubleSpinBoxPosReached->value());
 
-        cfg.wpCfg.maxOriVel                   = static_cast<float>(_ui.doubleSpinBoxOriMaxVel->value());
-        cfg.wpCfg.maxPosVel                   = static_cast<float>(_ui.doubleSpinBoxPosMaxVel->value());
-        cfg.wpCfg.kpOri                       = static_cast<float>(_ui.doubleSpinBoxOriKP->value());
-        cfg.wpCfg.kpPos                       = static_cast<float>(_ui.doubleSpinBoxPosKP->value());
+        cfg.wpCfg.maxOriVel                     = static_cast<float>(_ui.doubleSpinBoxOriMaxVel->value());
+        cfg.wpCfg.maxPosVel                     = static_cast<float>(_ui.doubleSpinBoxPosMaxVel->value());
+        cfg.wpCfg.kpOri                         = static_cast<float>(_ui.doubleSpinBoxOriKP->value());
+        cfg.wpCfg.kpPos                         = static_cast<float>(_ui.doubleSpinBoxPosKP->value());
 
-        cfg.forceThreshold                    = static_cast<float>(_ui.doubleSpinBoxFTLimit->value());
+        cfg.forceThreshold                      = static_cast<float>(_ui.doubleSpinBoxFTLimit->value());
         cfg.optimizeNullspaceIfTargetWasReached = _ui.checkBoxKeepOptimizing->isChecked();
         cfg.forceThresholdInRobotRootZ          = _ui.checkBoxLimitinZ->isChecked();
+        cfg.skipToClosestWaypoint               = _ui.checkBoxSkipWaypoints->isChecked();
 
         return cfg;
     }
diff --git a/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidgetController.h b/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidgetController.h
index f5358b49ea1dfbb097abd2d9432b403ee823f46c..01c59683c8dd3081e50ac58aa180d9c1a1d7ced0 100644
--- a/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidgetController.h
+++ b/source/RobotAPI/gui-plugins/CartesianWaypointControlGui/CartesianWaypointControlGuiWidgetController.h
@@ -76,6 +76,7 @@ namespace armarx
         void on_pushButtonZeroFT_clicked();
         void on_pushButtonSendSettings_clicked();
         void createController() override;
+        void copyCurrentPose();
 
         void triggerParsing();
 
diff --git a/source/RobotAPI/interface/objectpose/ObjectPoseObserver.ice b/source/RobotAPI/interface/objectpose/ObjectPoseObserver.ice
index 602043ab3b17e3afbdfbbd87b835dda80f264f29..850647b279975f4f79adfb95d89b46c10ebb5c8e 100644
--- a/source/RobotAPI/interface/objectpose/ObjectPoseObserver.ice
+++ b/source/RobotAPI/interface/objectpose/ObjectPoseObserver.ice
@@ -104,6 +104,35 @@ module armarx
         };
         sequence<AgentFrames> AgentFramesSeq;
 
+
+        enum HeadMovementAction
+        {
+            /// Head movement is about to start.
+            HeadMovementAction_Starting,
+            /// Head movement is stopping.
+            HeadMovementAction_Stopping
+        };
+        struct SignalHeadMovementInput
+        {
+            HeadMovementAction action;
+
+            /**
+             * If starting, for how long (after this call) new poses shall be discarded
+             * due to head movement.
+             * When set to -1 (default), the configured value in the component is used.
+             *
+             * If stopping, for how long (after this call) new poses shall be discarded
+             * due to remaining head movement.
+             * When set to -1 (default), resume processing updates immediately.
+             */
+            long discardUpdatesIntervalMilliSeconds = -1;
+        };
+        struct SignalHeadMovementOutput
+        {
+            /// The time until new updates will be discarded.
+            long discardUpdatesUntilMilliSeconds = -1;
+        };
+
         interface ObjectPoseObserverInterface extends ObserverInterface, ObjectPoseTopic
         {
             // Object poses
@@ -133,6 +162,15 @@ module armarx
 
             AgentFramesSeq getAttachableFrames();
 
+
+            /*
+             * Notifying about head/camera movement.
+             * While the head is moving (and a short time afterwards), pose updates are discarded
+             * as they can be bad due to camera movement.
+             */
+
+            SignalHeadMovementOutput signalHeadMovement(SignalHeadMovementInput input);
+
         };
     };
 
diff --git a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateHelper.cpp b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateHelper.cpp
index 3533eb02cc11c3598813f330e202e9b673155136..c4f221549de4e12a04e1a495e7a8448fa9c64b3e 100644
--- a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateHelper.cpp
+++ b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateHelper.cpp
@@ -38,8 +38,7 @@ GraspCandidateHelper::GraspCandidateHelper(const grasping::GraspCandidatePtr& ca
 
 Eigen::Matrix4f GraspCandidateHelper::getGraspPoseInRobotRoot() const
 {
-    const Eigen::Matrix4f curRobotPose = robot->getGlobalPose();
-    const Eigen::Matrix4f graspPose = curRobotPose.inverse() * getGraspPoseInGlobal();
+    const Eigen::Matrix4f graspPose = getFramedPose().toRootEigen(robot);
     return graspPose;
 }
 
@@ -58,11 +57,18 @@ Eigen::Vector3f GraspCandidateHelper::getGraspPositionInRobotRoot() const
     return math::Helpers::GetPosition(getGraspPoseInRobotRoot());
 }
 
+armarx::FramedPose GraspCandidateHelper::getFramedPose() const
+{
+    return armarx::FramedPose(candidate->graspPose->position,
+                              candidate->graspPose->orientation,
+                              candidate->sourceFrame,
+                              robot->getName());
+}
+
 Eigen::Matrix4f GraspCandidateHelper::getGraspPoseInGlobal() const
 {
-    const Eigen::Matrix4f oldGraspPose = defrost(candidate->graspPose);
-    const Eigen::Matrix4f oldRobotPose = defrost(candidate->robotPose);
-    return oldRobotPose * oldGraspPose;
+    auto grasp_pose = getFramedPose();
+    return grasp_pose.toGlobalEigen(robot);
 }
 Eigen::Matrix3f GraspCandidateHelper::getGraspOrientationInGlobal() const
 {
diff --git a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateHelper.h b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateHelper.h
index 050958f91cb13e5ad34f1a87110e759bc5402b1e..6c9ba617bc511278c77bc147eabb2ac18abc3b19 100644
--- a/source/RobotAPI/libraries/GraspingUtility/GraspCandidateHelper.h
+++ b/source/RobotAPI/libraries/GraspingUtility/GraspCandidateHelper.h
@@ -30,6 +30,7 @@
 #include <VirtualRobot/Robot.h>
 
 #include <RobotAPI/libraries/core/Pose.h>
+#include <RobotAPI/libraries/core/FramedPose.h>
 
 namespace armarx
 {
@@ -50,6 +51,7 @@ namespace armarx
         Eigen::Matrix3f getGraspOrientationInRobotRoot() const;
         Eigen::Vector3f getGraspPositionInRobotRoot() const;
 
+        armarx::FramedPose getFramedPose() const;
         Eigen::Matrix4f getGraspPoseInGlobal() const;
         Eigen::Matrix3f getGraspOrientationInGlobal() const;
         Eigen::Vector3f getGraspPositionInGlobal() const;
diff --git a/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.cpp b/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.cpp
index 27525c42d19079f8fbd3db620f03ad1710f09897..03ed255120ccf8a5084ea824f7d8e2d790c1c485 100644
--- a/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.cpp
+++ b/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.cpp
@@ -89,3 +89,10 @@ const
     return target_dist < m_pos_reached_threshold and
            std::fabs(target_angular_dist) < m_ori_reached_threshold;
 }
+
+
+void
+armarx::ObstacleAvoidingPlatformUnitHelper::setMaxVelocities(float max_vel, float max_angular_vel)
+{
+    m_platform_unit->setMaxVelocities(max_vel, max_angular_vel);
+}
diff --git a/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.h b/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.h
index 96bd461e7f6194de38283839eaaff0d5ec135967..12d7100f997eeb818f6cf0aff989e42a92059df5 100644
--- a/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.h
+++ b/source/RobotAPI/libraries/RobotStatechartHelpers/ObstacleAvoidingPlatformUnitHelper.h
@@ -61,6 +61,9 @@ namespace armarx
         isFinalTargetReached()
         const;
 
+        void
+        setMaxVelocities(float max_vel, float max_angular_vel);
+
     private:
 
         struct target
diff --git a/source/RobotAPI/libraries/RobotStatechartHelpers/VelocityControllerHelper.cpp b/source/RobotAPI/libraries/RobotStatechartHelpers/VelocityControllerHelper.cpp
index d4d9a88466d407b83d72d9a5db48f2cade93aa42..456671e79cc764fc577121082967fcc52e341361 100644
--- a/source/RobotAPI/libraries/RobotStatechartHelpers/VelocityControllerHelper.cpp
+++ b/source/RobotAPI/libraries/RobotStatechartHelpers/VelocityControllerHelper.cpp
@@ -50,7 +50,7 @@ namespace armarx
         }
         else
         {
-            ctrl = robotUnit->createNJointController("NJointCartesianVelocityControllerWithRamp", controllerName, config);
+            ctrl = robotUnit->createOrReplaceNJointController("NJointCartesianVelocityControllerWithRamp", controllerName, config);
             controllerCreated = true;
         }
         controller = NJointCartesianVelocityControllerWithRampInterfacePrx::checkedCast(ctrl);