diff --git a/GeometricPlanning/ArticulatedObjectGeometricPlanningHelper.cpp b/GeometricPlanning/ArticulatedObjectGeometricPlanningHelper.cpp
index 92b02612dd327bc9b80a09f5ec166e047139ccf4..d732c36477545bf42585ca586c074e9898f75058 100644
--- a/GeometricPlanning/ArticulatedObjectGeometricPlanningHelper.cpp
+++ b/GeometricPlanning/ArticulatedObjectGeometricPlanningHelper.cpp
@@ -124,22 +124,23 @@ namespace simox::geometric_planning
                 .toRotationMatrix());
         // ARMARX_DEBUG << VAROUT(joint_T_joint_ref.linear());
 
-        const Pose global_T_joint =
-            global_T_joint_orig *
-            joint_T_joint_ref; // * Pose(Eigen::Quaternionf::FromTwoVectors(Eigen::Vector3f::UnitZ(), revoluteJoint->getJointRotationAxis()).toRotationMatrix());
+        const Pose global_T_joint_ref = global_T_joint_orig * joint_T_joint_ref;
+        // * Pose(Eigen::Quaternionf::FromTwoVectors(Eigen::Vector3f::UnitZ(), revoluteJoint->getJointRotationAxis()).toRotationMatrix());
 
         const Pose global_T_node(node->getGlobalPose());
 
         // this is the node within the joint frame
-        const Pose joint_T_node = global_T_joint.inverse() * global_T_node;
+        // joint_ref_T_node = joint_ref_T_global * global_T_node
+        const Pose joint_ref_T_node = global_T_joint_ref.inverse() * global_T_node;
+
         // ARMARX_DEBUG << "relative position: " << joint_T_node.translation();
 
-        // the plane in which the node moves is given by the following
+        // the plane in which the node moves is given by the following (x, y coordinates = 0 for movement plane)
         const Pose global_T_joint_plane =
-            global_T_joint * Eigen::Translation3f{0, 0, joint_T_node.translation().z()};
+            global_T_joint_ref * Eigen::Translation3f{0, 0, joint_ref_T_node.translation().z()};
 
         // and the radius of the movement is given by the xy coordinate
-        const float radius = joint_T_node.translation().head<2>().norm();
+        const float radius = joint_ref_T_node.translation().head<2>().norm();
         // ARMARX_DEBUG << "Radius: " << radius;
 
         REQUIRE_MESSAGE(joint->getJointLimitHigh() > joint->getJointLimitLow(),
@@ -158,7 +159,8 @@ namespace simox::geometric_planning
 
         const std::string nodeJointReference = node->getName() + "_joint_reference";
 
-        const Pose joint_T_joint_plane = global_T_joint.inverse() * global_T_joint_plane;
+        // joint_T_joint_plane = joint_ref_T_global * global_t_joint_plane
+        const Pose joint_ref_T_joint_plane = global_T_joint_ref.inverse() * global_T_joint_plane;
 
         // now we make sure that the joint coordinate system is oriented such that the x axis points towards the initial node position
         const Eigen::Vector3f global__joint_plane_P_node_initial =
@@ -178,9 +180,9 @@ namespace simox::geometric_planning
 
         const Pose global_T_root(subpart->getGlobalPose());
         const Pose root_T_global = global_T_root.inverse();
-        const Pose root_T_joint = root_T_global * global_T_joint;
+        const Pose root_T_joint_ref = root_T_global * global_T_joint_ref;
 
-        const Pose root_T_joint_reference(root_T_joint * joint_T_joint_plane *
+        const Pose root_T_joint_reference(root_T_joint_ref * joint_ref_T_joint_plane *
                                           joint_plane_T_joint_reference);
 
         const auto jointReferenceNode = std::make_shared<VirtualRobot::RobotNodeFixed>(
@@ -197,10 +199,18 @@ namespace simox::geometric_planning
         // reset joint state
         joint->setJointValue(initialJointValue);
 
+        // we need the transformation from root_T_joint_reference to node, so that the orientations match up.
+        // without the orientation adaption, this will output a coordinate system with x pointing in the
+        // direction from global_T_plane to node, and z pointing upward.
+        // therefore:
+        // global_T_root * root_T_joint_reference = global_T_joint_reference
+        // we just need the linear transformation though (i think), because the translation was already covered before
+        Pose joint_reference_T_node(
+            ((global_T_root * root_T_joint_reference).inverse() * global_T_node).linear());
 
         return {jointReferenceNode,
                 std::make_unique<CircleSegment>(radius, parameterRange),
-                Pose::Identity()};
+                joint_reference_T_node};
     }
 
     ParametricPath
@@ -266,11 +276,16 @@ namespace simox::geometric_planning
         subpart->registerRobotNode(jointReferenceNode);
         CHECK(jointReferenceNode->initialize(subpart->getRootNode()));
 
+
         // ARMARX_DEBUG << "registered robot node `" << jointReferenceNode->getName() << "`";
+        const Eigen::Isometry3f global_T_node(node->getGlobalPose());
+        const Eigen::Isometry3f global_T_jointReference(jointReferenceNode->getGlobalPose());
+
+        const Eigen::Isometry3f jointReference_T_node = global_T_jointReference.inverse() * global_T_node;
+        // jointRef_T_node = jointRef_T_global * global_T_node
 
-        return ParametricPath(jointReferenceNode,
-                              std::make_unique<Line>(parameterRange),
-                              Pose::Identity());
+        return ParametricPath(
+            jointReferenceNode, std::make_unique<Line>(parameterRange), jointReference_T_node);
     }
 
     ParametricPath
diff --git a/GeometricPlanning/CMakeLists.txt b/GeometricPlanning/CMakeLists.txt
index 77d85645ef611b144f628c8fd8649fcbbb9b27dc..fe29714155848bea33a1112148a7b8c007d2f224 100644
--- a/GeometricPlanning/CMakeLists.txt
+++ b/GeometricPlanning/CMakeLists.txt
@@ -116,9 +116,10 @@ endif()
 
 if(BUILD_TESTING)
     # include unit tests
-    # ADD_SUBDIRECTORY(tests/)
+    ADD_SUBDIRECTORY(tests)
 endif()
 
+
 #######################################################################################
 ############################ Setup for installation ###################################
 #######################################################################################
diff --git a/GeometricPlanning/tests/AOGPHelper.cpp b/GeometricPlanning/tests/AOGPHelper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5c92dc75cbd30b585c21db9f4c33634e28c446e1
--- /dev/null
+++ b/GeometricPlanning/tests/AOGPHelper.cpp
@@ -0,0 +1,176 @@
+
+#include <filesystem>
+
+#include "VirtualRobot/XML/RobotIO.h"
+#define BOOST_TEST_MODULE GeometricPlanning_AOGPHelper
+
+#include <boost/test/unit_test.hpp>
+
+#include "../ArticulatedObjectGeometricPlanningHelper.h"
+
+BOOST_AUTO_TEST_SUITE(ArticulatedObjectGeometricPlanningHelper)
+
+static constexpr const char* scDataDir =
+    "/common/homes/students/hoefer/workspace/base/simox-control/data/";
+
+static constexpr float precision = 0.00001;
+static constexpr size_t nSamples = 50;
+
+static std::filesystem::path
+getPath(const std::string& model)
+{
+    return std::filesystem::path(scDataDir) / "Model" / "mobile_kitchen" / model /
+           (model + "_articulated.xml");
+}
+
+VirtualRobot::RobotPtr
+load_robot(const std::string& model)
+{
+    // TODO(Hawo): somehow input a simple robot model
+    const auto path = getPath(model);
+    BOOST_REQUIRE(std::filesystem::exists(path));
+
+    auto robot = VirtualRobot::RobotIO::loadRobot(path);
+    BOOST_REQUIRE(robot != nullptr);
+
+    return robot;
+}
+
+using AOGPHelper = simox::geometric_planning::ArticulatedObjectGeometricPlanningHelper;
+
+static void
+printMat(const Eigen::MatrixXf& mat)
+{
+    for (int row = 0; row < mat.rows(); ++row)
+    {
+        for (int col = 0; col < mat.cols(); ++col)
+        {
+            float val = mat(row, col);
+            val = std::fabs(val) < precision ? 0 : val;
+            std::cout << val << ",";
+        }
+        std::cout << "\n";
+    }
+}
+BOOST_AUTO_TEST_CASE(AOGPHelper_circle_first_path_element)
+{
+    const std::string articulatedObjectName = "mobile-fridge";
+    const std::string handleName = "Fridge_handle";
+    auto robot = load_robot(articulatedObjectName);
+
+    auto helper = AOGPHelper(robot);
+    auto path = helper.getPathForNode(handleName);
+    auto range = path.parameterRange();
+
+    Eigen::Matrix4f pathPose = path.getPose(range.min).matrix();
+    Eigen::Matrix4f objectPose = robot->getRobotNode(handleName)->getGlobalPose();
+
+    Eigen::Matrix4f diff = objectPose.inverse() * pathPose - Eigen::Matrix4f::Identity();
+    float sse = diff.array().square().sum();
+    if (sse > precision)
+    {
+        std::cout << "Object Pose (target):\n";
+        std::cout << objectPose << "\n";
+        std::cout << "Path Pose:\n";
+        std::cout << pathPose << "\n";
+        printMat(diff);
+    }
+    BOOST_REQUIRE(sse < precision);
+}
+
+
+BOOST_AUTO_TEST_CASE(AOGPHelper_circle_path_whole_around_z)
+{
+    const std::string articulatedObjectName = "mobile-fridge";
+    const std::string handleName = "Fridge_handle";
+    const std::string jointName = "Fridge_joint";
+    auto robot = load_robot(articulatedObjectName);
+
+    auto helper = AOGPHelper(robot);
+    auto path = helper.getPathForNode(handleName);
+    auto range = path.parameterRange();
+
+
+    for (size_t i = 0; i < nSamples; ++i)
+    {
+        float t = range.min + (range.max - range.min) / nSamples * i;
+        Eigen::Matrix4f pathPose = path.getPose(t).matrix();
+
+        robot->setJointValue(jointName, t);
+        Eigen::Matrix4f objectPose = robot->getRobotNode(handleName)->getGlobalPose();
+
+        Eigen::Matrix4f diff = objectPose.inverse() * pathPose - Eigen::Matrix4f::Identity();
+        float sse = diff.array().square().sum();
+        if (sse > precision)
+        {
+            std::cout << "SSE at " << t << ": " << sse << "\n";
+            printMat(diff);
+        }
+        BOOST_REQUIRE(sse < precision);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(AOGPHelper_circle_path_whole_around_y)
+{
+    const std::string articulatedObjectName = "mobile-dishwasher";
+    const std::string handleName = "Dishwasher->Dishwasher_handle";
+    const std::string jointName = "Dishwasher_joint";
+    auto robot = load_robot(articulatedObjectName);
+
+    auto helper = AOGPHelper(robot);
+    auto path = helper.getPathForNode(handleName);
+    auto range = path.parameterRange();
+
+
+    for (size_t i = 0; i < nSamples; ++i)
+    {
+        float t = range.min + (range.max - range.min) / nSamples * i;
+        Eigen::Matrix4f pathPose = path.getPose(t).matrix();
+
+        robot->setJointValue(jointName, t);
+        Eigen::Matrix4f objectPose = robot->getRobotNode(handleName)->getGlobalPose();
+
+        Eigen::Matrix4f diff = objectPose.inverse() * pathPose - Eigen::Matrix4f::Identity();
+        float sse = diff.array().square().sum();
+        if (sse > precision)
+        {
+            std::cout << "SSE at " << t << ": " << sse << "\n";
+            printMat(diff);
+        }
+        BOOST_REQUIRE(sse < precision);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(AOGPHelper_linear_path_whole)
+{
+    const std::string articulatedObjectName = "mobile-kitchen-counter";
+    const std::string handleName = "DrawerMiddle_handle";
+    const std::string jointName = "DrawerMiddle_joint";
+    auto robot = load_robot(articulatedObjectName);
+
+    auto helper = AOGPHelper(robot);
+    auto path = helper.getPathForNode(handleName);
+    auto range = path.parameterRange();
+
+
+    for (size_t i = 0; i < nSamples; ++i)
+    {
+        float t = range.min + (range.max - range.min) / nSamples * i;
+        Eigen::Matrix4f pathPose = path.getPose(t).matrix();
+
+        robot->setJointValue(jointName, t);
+        Eigen::Matrix4f objectPose = robot->getRobotNode(handleName)->getGlobalPose();
+
+        Eigen::Matrix4f diff = objectPose.inverse() * pathPose - Eigen::Matrix4f::Identity();
+        float sse = diff.array().square().sum();
+        if (sse > precision)
+        {
+            std::cout << "SSE at " << t << ": " << sse << "\n";
+            printMat(diff);
+        }
+        BOOST_REQUIRE(sse < precision);
+    }
+}
+
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/GeometricPlanning/tests/CMakeLists.txt b/GeometricPlanning/tests/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..59f9b89478a8eabe52426b20ca55ae9ebe6505a1
--- /dev/null
+++ b/GeometricPlanning/tests/CMakeLists.txt
@@ -0,0 +1,3 @@
+# FIXME the following test must be adapted. It requires a model that is not available in simox
+
+# ADD_GEOMETRIC_PLANNING_TEST(AOGPHelper)
diff --git a/VirtualRobot/Manipulability/AbstractManipulability.cpp b/VirtualRobot/Manipulability/AbstractManipulability.cpp
index 8e6725d0e62a1158ec840da3615a4f3382cdbab6..fa39669db21ca4354e9953e370c6ee8abe82b539 100644
--- a/VirtualRobot/Manipulability/AbstractManipulability.cpp
+++ b/VirtualRobot/Manipulability/AbstractManipulability.cpp
@@ -108,26 +108,50 @@ VisualizationNodePtr AbstractManipulability::getManipulabilityVis(const Eigen::M
     return vis;
 }
 
-void AbstractManipulability::getEllipsoidOrientationAndScale(const Eigen::MatrixXd &manipulability, Eigen::Quaternionf &orientation, Eigen::Vector3d &scale) {
-    Eigen::MatrixXd reduced_manipulability = (mode != Mode::Orientation || manipulability.rows() == 3) ?  manipulability.block(0, 0, 3, 3) : manipulability.block(3, 3, 3, 3);
-    Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigenSolver(reduced_manipulability);
-    Eigen::MatrixXd eigenVectors = eigenSolver.eigenvectors();
-    Eigen::VectorXd eigenValues = eigenSolver.eigenvalues();
-    Eigen::VectorXd v1 = eigenVectors.col(eigenVectors.cols() - 1);
-    Eigen::VectorXd v2 = eigenVectors.col(eigenVectors.cols() - 2);
-
-    orientation = Eigen::Quaterniond::FromTwoVectors(v1, v2).cast<float>();
+void AbstractManipulability::getEllipsoidOrientationAndScale(const Eigen::MatrixXd& manipulability, Eigen::Quaternionf& orientation, Eigen::Vector3d& scale) {
+    
+    constexpr std::size_t POSITION_DIMS = 3;
+    constexpr std::size_t ORIENTATION_DIMS = 3;
+    constexpr std::size_t POSE_DIMS = POSITION_DIMS + ORIENTATION_DIMS;
+    
+    const Eigen::MatrixXd reduced_manipulability =
+        (mode != Mode::Orientation || manipulability.rows() == 3)
+            ? manipulability.block(0, 0, POSITION_DIMS, POSITION_DIMS)
+            : manipulability.block(
+                    POSITION_DIMS, POSITION_DIMS, ORIENTATION_DIMS, ORIENTATION_DIMS);
+    const Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> eigenSolver(reduced_manipulability);
+
+    // Eigenvectors are the columns of the matrix 'eigenVectors'
+    // they are already normalized to have length 1
+    const Eigen::MatrixXd& eigenVectors = eigenSolver.eigenvectors();
+
+    // Eigen values are sorted in increasing order
+    const Eigen::Vector3d& eigenValues = eigenSolver.eigenvalues();
+
+    // We create a ortho-normal basis of the eigen vectors.
+    // Here, we use the eigen vectors with the eigen values in decreasing order.
+    // To ensure a right-handed coordinate system, the third basis vector is computed
+    // by using the cross product.
+    Eigen::Matrix3d rotationMatrix;
+    rotationMatrix.col(0) = eigenVectors.col(2);
+    rotationMatrix.col(1) = eigenVectors.col(1);
+    rotationMatrix.col(2) = rotationMatrix.col(0).cross(rotationMatrix.col(1));
+
+    orientation = rotationMatrix;
+
+    scale = eigenValues.reverse();
 
     // normalize singular values for scaling
-    eigenValues /= eigenValues.sum();
-    for (int i = 0; i < eigenValues.rows(); i++) {
-        if (eigenValues(i) < 0.005) // 5mm
-            eigenValues(i) = 0.005;
-    }
+    scale /= scale.sum();
+    for (int i = 0; i < eigenValues.rows(); i++)
+    {
+        constexpr double minEigenVal = 0.005; // [mm]
 
-    scale(0) = eigenValues(eigenValues.rows() - 1);
-    scale(1) = eigenValues(eigenValues.rows() - 2);
-    scale(2) = eigenValues(eigenValues.rows() - 3);
+        if (scale(i) < minEigenVal)
+        {
+            scale(i) = minEigenVal;
+        }
+    }
 }
 
 void AbstractManipulability::getEllipsoidOrientationAndScale(Eigen::Quaternionf &orientation, Eigen::Vector3d &scale) {