예제 #1
0
    def test_tf_funcs(self):
        v = Vector(1, 3, 2)
        # Create from angle
        tf = Transform(v, math.pi / 2)

        self.assertAlmostEqual(tf.get_angle(), math.pi / 2)

        # Extensively test the angle function:
        for _ in range(0, 100):
            angle = random.randrange(-100, 100)
            self.assert_equal_angles(Transform(v, angle).get_angle(), angle)

        g_tf = g_msgs.Transform()
        g_tf.translation = v.to_geometry_msg()
        g_tf.rotation = g_msgs.Quaternion(0, 0, math.sqrt(1 / 2),
                                          math.sqrt(1 / 2))

        self.geom_tf_almost_eq(g_tf, tf.to_geometry_msg())

        # Should return 90 degrees
        tf2 = Transform(Vector(1, 0, 0), Quaternion(1, 0, 0, 1).normalised)
        self.assertEqual(tf2.get_angle(), math.pi / 2)

        # Multiply transforms
        self.assertEqual(tf * tf2, Transform(Vector(1, 4, 2), math.pi))
예제 #2
0
    def interpolate_curvature(self, *, arc_length: float) -> float:
        """Interpolate the curvature at a given arc_length.

        The curvature is approximated by calculating the Menger curvature as defined \
        and described here:
        https://en.wikipedia.org/wiki/Menger_curvature#Definition

        Args:
            arc_length (float): Length along the line starting from the first point

        Raises:
            ValueError: If the arc_length is <0 or more than the length of the line.

        Returns:
            Corresponding curvature.
        """
        p = Vector(
            self.interpolate(arc_length -
                             CURVATURE_APPROX_DISTANCE))  # Previous point
        c = Vector(self.interpolate(arc_length))  # Point at current arc_length
        n = Vector(self.interpolate(arc_length +
                                    CURVATURE_APPROX_DISTANCE))  # Next point

        # Area of the triangle spanned by p, c, and n.
        # The triangle's area can be computed by the cross product of the vectors.
        cross = (n - c).cross(p - c)

        sign = 1 - 2 * (
            cross.z < 0
        )  # The z-coord sign determines whether the curvature is positive or negative

        return sign * 2 * abs(cross) / (abs(p - c) * abs(n - c) * abs(p - n))
 def assert_approx_equal_pose(pose1, pose2):
     """Substitute self.assertAlmostEqual because pose - pose is not implemented."""
     self.assertAlmostEqual(Vector(pose1.position),
                            Vector(pose2.position))
     self.assertAlmostEqual(pose1.get_angle(),
                            pose2.get_angle(),
                            delta=math.radians(0.02))
예제 #4
0
    def __init__(self, *args, frame=None):
        """Transform initialization."""

        # Due to recursive calling of the init function, the frame should be set
        # in the first call within the recursion only.
        if not hasattr(self, "_frame"):
            self._frame = frame

        # Attempt initialization from Vector like and Quaternion like objects
        with suppress(Exception):
            args = (args[0], Quaternion(*args[1]))

        with suppress(Exception):
            if isinstance(args[1], numbers.Number):
                args = (args[0], Quaternion(axis=[0, 0, 1], radians=args[1]))
            pass

        # Attempt default init
        with suppress(IndexError, NotImplementedError, TypeError):
            if type(args[1]) == Quaternion:
                self.rotation = args[1]
                self.translation = Vector(args[0])
                return

        # Try to initialize geometry pose
        with suppress(Exception):
            # Call this function with values extracted
            t = Vector(args[0].position)
            g_quaternion = args[0].orientation
            q = Quaternion(g_quaternion.w, g_quaternion.x, g_quaternion.y,
                           g_quaternion.z)
            self.__init__(t, q)
            return

        # Try to initialize geometry transform
        with suppress(Exception):
            # Call this function with values extracted
            t = Vector(args[0].translation)
            g_quaternion = args[0].rotation
            q = Quaternion(g_quaternion.w, g_quaternion.x, g_quaternion.y,
                           g_quaternion.z)
            self.__init__(t, q)
            return

        with suppress(Exception):
            # Try to initialize with two vectors translation+rotation
            t = args[0]
            rotation_vec = args[1].to_numpy()
            angle = (-1 if rotation_vec[1] < 0 else 1) * math.acos(
                np.dot([1, 0, 0], rotation_vec) / np.linalg.norm(rotation_vec))

            self.__init__(t, angle)
            return

        # None of the initializations worked
        raise NotImplementedError(
            f"Transform initialization not implemented for {type(args[0])}")
예제 #5
0
    def __init__(self, *args, frame=None):
        """Pose initialization."""

        # Due to recursive calling of the init function, the frame should be set
        # in the first call within the recursion only.
        if not hasattr(self, "_frame"):
            self._frame = frame

        with suppress(Exception):
            args = (args[0], Quaternion(*args[1]))

        with suppress(Exception):
            if isinstance(args[1], numbers.Number):
                args = (args[0], Quaternion(axis=[0, 0, 1], radians=args[1]))

        # Attempt default init
        with suppress(IndexError, NotImplementedError, TypeError):
            if type(args[1]) == Quaternion:
                self.position = Point(args[0])
                self.orientation = args[1]
                return

        # Try to initialize geometry pose
        with suppress(Exception):
            # Call this function with values extracted
            t = Point(args[0].position)
            g_quaternion = args[0].orientation
            q = Quaternion(g_quaternion.w, g_quaternion.x, g_quaternion.y,
                           g_quaternion.z)
            self.__init__(t, q)
            return

        # Try to initialize geometry transform
        with suppress(Exception):
            # Call this function with values extracted
            t = Point(args[0].translation)
            g_quaternion = args[0].rotation
            q = Quaternion(g_quaternion.w, g_quaternion.x, g_quaternion.y,
                           g_quaternion.z)
            self.__init__(t, q)
            return

        with suppress(Exception):
            # Try to initialize with two points translation+orientation
            t = args[0]
            orientation_vec = Vector(args[1])
            angle = (-1 if orientation_vec.y < 0 else 1) * math.acos(
                (Vector(1, 0, 0) * orientation_vec) / abs(orientation_vec))

            self.__init__(t, angle)
            return

        # None of the initializations worked
        raise NotImplementedError(
            f"{self.__class__} initialization not implemented for {type(args[0])}"
        )
예제 #6
0
    def get_angle(self, axis=Vector(0, 0, 1)) -> float:
        """Angle of orientation.

        Returns:
            The angle that a vector is rotated, when this transformation is applied.
        """
        # Project the rotation axis onto the z axis to get the amount of the rotation \
        # that is in z direction!
        # Also the quaternions rotation axis is sometimes (0,0,-1) at which point \
        # the angles flip their sign,
        # taking the scalar product of the axis and z fixes that as well
        return Vector(self.orientation.axis) * axis * self.orientation.radians
예제 #7
0
    def __rmul__(self, obj: Transform):
        """Right multiplication of a point. Only defined for transformations."""
        if type(obj) == Transform:
            # Transform * self
            return self.__class__(obj * Vector(self))

        raise InvalidPointOperationError("A point cannot be scaled.")
예제 #8
0
    def get_angle(self, axis=Vector(0, 0, 1)) -> float:
        """Angle of rotation.

        Args:
            axis: Axis the rotation is projected onto.

        Returns:
            The angle that a vector is rotated, when this transformation is applied.
        """

        # Project the rotation axis onto the rotation axis to get the amount of the rotation
        # that is in the axis' direction!
        # Also the quaternions rotation axis is sometimes flipped at which point
        # the angles flip their sign,
        # taking the scalar product with the axis fixes that as well
        return Vector(self.rotation.axis) * axis * self.rotation.radians
    def test_polygon_init(self):
        points = self.create_points()
        poly = Polygon(points)

        # Check that polygon contains points
        # Shapely adds first point at last index again
        self.assertListEqual(poly.get_points()[:-1], points)

        # Geometry msgs
        g_polygon = g_msgs.Polygon()
        g_polygon.points = [p.to_geometry_msg() for p in points]
        poly_g = Polygon(g_polygon)
        self.assertEqual(poly, poly_g)

        # Numpy
        array = np.array([p.to_numpy() for p in points])
        poly_np = Polygon(array)
        self.assertEqual(poly, poly_np)

        # Check if polygon can be created correctly from array of two dimensional points
        points_2d = [[2, 1], [3, 2], [34, 5], [3242, 1]]
        points_3d = [[*p, 0] for p in points_2d]
        self.assertEqual(Polygon(points_2d), Polygon(points_3d))

        # Two lines
        points_left = points
        points_right = [(p + Vector(1, 0)) for p in points]

        all_points = points_right.copy()
        all_points.extend(reversed(points_left))  # Left side in reverse

        poly1 = Polygon(Line(points_left), Line(points_right))
        poly2 = Polygon(all_points)
        self.assertEqual(poly1, poly2)
예제 #10
0
    def test_tf_init(self):
        """Test if tf class can be initialized as expected."""
        # Create from quaternion
        v = Vector(1, 3, 2)
        o = Quaternion(math.sqrt(1 / 2), 0, 0, math.sqrt(1 / 2))
        tf = Transform(v, o)

        # Create from angle
        tf2 = Transform(v, math.pi / 2)

        self.assertEqual(tf, tf2)

        # Create from geometry_msgs/transform
        g_tf = g_msgs.Transform()
        g_tf.translation = v.to_geometry_msg()
        g_tf.rotation = g_msgs.Quaternion(0, 0, math.sqrt(1 / 2),
                                          math.sqrt(1 / 2))

        self.assertEqual(Transform(g_tf), tf)
예제 #11
0
    def test_transformations(self):
        """Test if frame transformations work as expected.

        Note: This is only tested for vectors because the behavior must be the same
        for all classes that are transformable.
        """
        frame_1 = Frame("frame_1")
        frame_2 = Frame("frame_2")

        frame_1.connect_to(frame_2,
                           transformation_to_frame=Transform([0, 0],
                                                             math.radians(90)))

        vec_frame_1 = Vector(1, 0, 0, frame=frame_1)
        vec_frame_2 = frame_2(vec_frame_1)

        self.assertEqual(vec_frame_2, Vector(0, 1, 0, frame=frame_2))

        vec_frame_2 = frame_2(vec_frame_2)
        self.assertEqual(vec_frame_2, Vector(0, 1, 0, frame=frame_2))
예제 #12
0
    def __mul__(self, tf: "Transform") -> "Transform":
        """Multiplication of transformations.

        The product has to be understood as a single transformation consisting of
        the right hand transformation applied first and then the left hand transformation.

        Example:
            Easily modify a vector multiple times:

            :math:`(\\text{Tf}_1*\\text{Tf}_2)*\\vec{v} = \\text{Tf}_1*( \\text{Tf}_2*\\vec{v})`

        Returns:
            The product transformation.
        """
        if tf.__class__ == self.__class__:
            return self.__class__(
                Vector(self) + Vector(tf).rotated(self.get_angle()),
                self.get_angle() + tf.get_angle(),
            )

        return NotImplemented
예제 #13
0
    def interpolate_direction(self, *, arc_length: float) -> Vector:
        """Interpolate the direction of the line as a vector.

        Approximate by calculating difference vector of a point slightly further
        and a point slightly before along the line.

        Args:
            arc_length (float): Length along the line starting from the first point

        Raises:
            ValueError: If the arc_length is <0 or more than the length of the line.

        Returns:
            Corresponding direction as a normalised vector.
        """
        n = Vector(self.interpolate(arc_length + APPROXIMATION_DISTANCE))
        p = Vector(self.interpolate(arc_length - APPROXIMATION_DISTANCE))

        d = n - p

        return 1 / abs(d) * d
예제 #14
0
    def test_pose_funcs(self):
        p = Point(1, 3, 2)
        # Create from angle
        pose = Pose(p, math.pi / 2)

        self.assertAlmostEqual(pose.get_angle(), math.pi / 2)

        g_pose = g_msgs.Pose()
        g_pose.position = p.to_geometry_msg()
        g_pose.orientation = g_msgs.Quaternion(0, 0, math.sqrt(1 / 2),
                                               math.sqrt(1 / 2))

        self.geom_pose_almost_eq(g_pose, pose.to_geometry_msg())

        # Should return 90 degrees
        pose2 = Pose(Point(1, 0, 0), Quaternion(-1, 0, 0, -1).normalised)
        self.assertEqual(pose2.get_angle(), math.pi / 2)

        # Apply transformations
        tf2 = Transform(Vector(1, 0, 0), Quaternion(1, 0, 0, 1).normalised)

        # Multiply transforms
        self.assertEqual(tf2 * pose, Pose(Vector(1, 4, 2), math.pi))
예제 #15
0
    def __rmul__(self, tf: "Transform"):
        """Apply transformation.

        Args:
            tf (Transform): Transformation to apply.

        Returns:
            Pose transformed by tf.
        """
        with suppress(NotImplementedError, AttributeError):
            return self.__class__(tf * Vector(self),
                                  self.get_angle() + tf.get_angle())

        return NotImplemented
예제 #16
0
    def __rmul__(self, tf: "Transform"):
        """Apply transformation.

        Args:
            tf (Transform): Transformation to apply.

        Returns:
            Pose transformed by tf.
        """
        with suppress(NotImplementedError, AttributeError):
            return self.__class__(
                tf * Vector(self.position),
                tf.rotation * self.orientation,
                frame=self._frame,
            )

        return NotImplemented
예제 #17
0
    def pointcloud_cb(self, pc: PointCloud2):
        """Process new sensor information of depth camera."""

        out_msg = Range()

        out_msg.field_of_view = self.param.tof.field_of_view
        out_msg.min_range = self.param.tof.min_range
        out_msg.max_range = self.param.tof.max_range
        out_msg.header.frame_id = self.param.frame_id

        vecs = (Vector(p) for p in sensor_msgs.point_cloud2.read_points(
            pc, field_names=("x", "y", "z")))
        out_msg.range = min(v.norm for v in vecs)

        rospy.logdebug(f"Pointcloud received in {rospy.get_name()}:{vecs}")
        rospy.logdebug(f"Publishing range: {out_msg.range}")

        self.publisher.publish(out_msg)
예제 #18
0
    def test_point_func(self):
        p1 = Point(1, 2, 3)
        vec = Vector(2, 1, 3)
        p3 = Point(3, 3, 6)

        self.assertEqual(p1 + vec, p3)
        self.assertEqual(p3 - vec, p1)

        # The following should raise exception
        with self.assertRaises(InvalidPointOperationError):
            p1.rotated(0)

        with self.assertRaises(InvalidPointOperationError):
            p1 + p3

        with self.assertRaises(InvalidPointOperationError):
            p1 - p3

        with self.assertRaises(InvalidPointOperationError):
            3 * p3
예제 #19
0
    def test_transformations(self):

        tf = Transform(Vector(2, 2), math.pi / 2)

        self.assertEqual(Vector(1, 3), tf * Vector(1, 1))
        self.assertEqual(Point(1, 3), tf * Point(1, 1))

        # Rotation around x-axis by 90 degrees
        tf2 = Transform(Vector(0, 0),
                        Quaternion(axis=(1.0, 0.0, 0.0), radians=math.pi / 2))
        self.assertEqual(Vector(1, 2, 3), tf2 * Vector(1, 3, -2))

        # Check if line and polygon are transformed correctly
        points = self.create_points()
        transformed_points = [tf * p for p in points]

        self.assertEqual(tf * Line(points), Line(transformed_points))
        self.assertEqual(tf * Polygon(points), Polygon(transformed_points))

        # Check to transform a pose
        pose = Pose(Point(2, 2), math.pi)
        self.assertEqual(tf * pose, Pose([0, 4], math.pi * 3 / 2))
예제 #20
0
class Transform:
    """Transformation class consisting of a translation and a rotation.

    A Transform object can be used to easily interchange between multiple coordinate systems
    (which can be transformed in one another via a rotation and a translation.

    Initialization can be done in one of the following ways:

    Args:
        1 (geometry_msgs/Transformation): initialize from geometry_msgs.
        2 (Vector, float): Second argument is the transformation's rotation angle in radian.
        3 (Vector, pyquaternion.Quaternion): Vector and quaternion.


    Attributes:
        tranlation (Vector)
        rotation (pyquaternion.Quaternion)
    """
    def __init__(self, *args, frame=None):
        """Transform initialization."""

        # Due to recursive calling of the init function, the frame should be set
        # in the first call within the recursion only.
        if not hasattr(self, "_frame"):
            self._frame = frame

        # Attempt initialization from Vector like and Quaternion like objects
        with suppress(Exception):
            args = (args[0], Quaternion(*args[1]))

        with suppress(Exception):
            if isinstance(args[1], numbers.Number):
                args = (args[0], Quaternion(axis=[0, 0, 1], radians=args[1]))
            pass

        # Attempt default init
        with suppress(IndexError, NotImplementedError, TypeError):
            if type(args[1]) == Quaternion:
                self.rotation = args[1]
                self.translation = Vector(args[0])
                return

        # Try to initialize geometry pose
        with suppress(Exception):
            # Call this function with values extracted
            t = Vector(args[0].position)
            g_quaternion = args[0].orientation
            q = Quaternion(g_quaternion.w, g_quaternion.x, g_quaternion.y,
                           g_quaternion.z)
            self.__init__(t, q)
            return

        # Try to initialize geometry transform
        with suppress(Exception):
            # Call this function with values extracted
            t = Vector(args[0].translation)
            g_quaternion = args[0].rotation
            q = Quaternion(g_quaternion.w, g_quaternion.x, g_quaternion.y,
                           g_quaternion.z)
            self.__init__(t, q)
            return

        with suppress(Exception):
            # Try to initialize with two vectors translation+rotation
            t = args[0]
            rotation_vec = args[1].to_numpy()
            angle = (-1 if rotation_vec[1] < 0 else 1) * math.acos(
                np.dot([1, 0, 0], rotation_vec) / np.linalg.norm(rotation_vec))

            self.__init__(t, angle)
            return

        # None of the initializations worked
        raise NotImplementedError(
            f"Transform initialization not implemented for {type(args[0])}")

    @property
    @validate_and_maintain_frames
    def inverse(self) -> "Transform":
        """Inverse transformation."""
        return Transform(
            -1 * Vector(self.translation).rotated(self.rotation.inverse),
            self.rotation.inverse,
        )

    def get_angle(self, axis=Vector(0, 0, 1)) -> float:
        """Angle of rotation.

        Args:
            axis: Axis the rotation is projected onto.

        Returns:
            The angle that a vector is rotated, when this transformation is applied.
        """

        # Project the rotation axis onto the rotation axis to get the amount of the rotation
        # that is in the axis' direction!
        # Also the quaternions rotation axis is sometimes flipped at which point
        # the angles flip their sign,
        # taking the scalar product with the axis fixes that as well
        return Vector(self.rotation.axis) * axis * self.rotation.radians

    def to_geometry_msg(self) -> geometry_msgs.Transform:
        """Convert transform to ROS geometry_msg.

        Returns:
            This transformation as a geometry_msgs/Transform.
        """
        vector = self.translation.to_geometry_msg()
        rotation = geometry_msgs.Quaternion(self.rotation.x, self.rotation.y,
                                            self.rotation.z, self.rotation.w)

        tf = geometry_msgs.Transform()
        tf.translation = vector
        tf.rotation = rotation

        return tf

    def to_affine_matrix(self) -> np.ndarray:
        """Get transformation as an affine matrix."""
        return np.column_stack((
            self.rotation.rotation_matrix,
            self.translation.to_numpy(),
        ))

    @validate_and_maintain_frames
    def __mul__(self, tf: "Transform") -> "Transform":
        """Multiplication of transformations.

        The product has to be understood as a single transformation consisting of
        the right hand transformation applied first and then the left hand transformation.

        Example:
            Easily modify a vector multiple times:

            :math:`(\\text{Tf}_1*\\text{Tf}_2)*\\vec{v} =
                \\text{Tf}_1*( \\text{Tf}_2*\\vec{v})`

        Returns:
            The product transformation.
        """
        if tf.__class__ == self.__class__:
            return self.__class__(
                Vector(self.translation) +
                Vector(tf.translation).rotated(self.rotation),
                self.rotation * tf.rotation,
                frame=self._frame,
            )

        return NotImplemented

    @validate_and_maintain_frames
    def __eq__(self, tf) -> bool:
        if self.__class__ != tf.__class__:
            return NotImplemented
        return tf.rotation.normalised == self.rotation.normalised and Vector(
            self.translation) == Vector(tf.translation)

    def __repr__(self) -> str:
        return (
            f"Transform(translation={repr(self.translation)}," +
            f"rotation={repr(self.rotation)}" +
            (f",frame={self._frame.name}" if self._frame is not None else "") +
            ")")

    def __hash__(self):
        return NotImplemented
예제 #21
0
 def inverse(self) -> "Transform":
     """Inverse transformation."""
     return Transform(-1 * Vector(self).rotated(-self.get_angle()),
                      -1 * self.get_angle())
    def test_line_interpolation_func(self):
        line = Line([Point(0, 0), Point(1, 1)])

        # Test if line direction function works
        self.assertAlmostEqual(
            line.interpolate_direction(arc_length=0),
            Vector(r=1, phi=math.pi / 4),
            delta=TOLERANCE,
        )
        self.assertAlmostEqual(
            line.interpolate_direction(arc_length=line.length / 2),
            Vector(r=1, phi=math.pi / 4),
            delta=TOLERANCE,
        )
        self.assertAlmostEqual(
            line.interpolate_direction(arc_length=line.length),
            Vector(r=1, phi=math.pi / 4),
            delta=TOLERANCE,
        )

        self.assertAlmostEqual(line.interpolate_curvature(arc_length=0),
                               0,
                               delta=TOLERANCE)
        self.assertAlmostEqual(
            line.interpolate_curvature(arc_length=line.length / 4),
            0,
            delta=TOLERANCE)

        self.assertEqual(line.interpolate_pose(arc_length=0),
                         Pose(Point(0, 0), Vector(1, 1, 0)))

        circle = Line(
            reversed([
                p for p in Point(-1, 0).buffer(1,
                                               resolution=4096).exterior.coords
            ]))

        # Test if line direction function works
        self.assertAlmostEqual(circle.interpolate_direction(arc_length=0),
                               Vector(0, 1),
                               delta=TOLERANCE)
        self.assertAlmostEqual(
            circle.interpolate_direction(arc_length=circle.length / 2),
            Vector(0, -1),
            delta=TOLERANCE,
        )
        self.assertAlmostEqual(
            circle.interpolate_direction(arc_length=circle.length),
            Vector(0, 1),
            delta=TOLERANCE,
        )
        self.assertAlmostEqual(
            circle.interpolate_direction(arc_length=circle.length * 3 / 4),
            Vector(1, 0))
        self.assertAlmostEqual(
            circle.interpolate_direction(arc_length=circle.length / 4),
            Vector(-1, 0))

        self.assertAlmostEqual(
            circle.interpolate_curvature(arc_length=circle.length / 2),
            1,
            delta=TOLERANCE)
        self.assertAlmostEqual(
            circle.interpolate_curvature(arc_length=circle.length / 4),
            1,
            delta=TOLERANCE)

        def assert_approx_equal_pose(pose1, pose2):
            self.assertAlmostEqual(Vector(pose1), Vector(pose2))
            self.assertAlmostEqual(pose1.get_angle(),
                                   pose2.get_angle(),
                                   delta=math.radians(0.02))

        assert_approx_equal_pose(circle.interpolate_pose(arc_length=0),
                                 Pose(Point(0, 0), Vector([0, 1, 0])))
        assert_approx_equal_pose(
            circle.interpolate_pose(arc_length=circle.length / 4),
            Pose(Point(-1, 1), Vector([-1, 0, 0])),
        )
        assert_approx_equal_pose(
            circle.interpolate_pose(arc_length=circle.length / 2),
            Pose(Point(-2, 0), Vector([0, -1, 0])),
        )
        assert_approx_equal_pose(
            circle.interpolate_pose(arc_length=circle.length * 3 / 4),
            Pose(Point(-1, -1), Vector([1, 0, 0])),
        )
        assert_approx_equal_pose(
            circle.interpolate_pose(arc_length=circle.length),
            Pose(Point(0, 0), Vector([0, 1, 0])),
        )
예제 #23
0
 def __eq__(self, tf) -> bool:
     if self.__class__ != tf.__class__:
         return NotImplemented
     return tf.rotation.normalised == self.rotation.normalised and Vector(
         self.translation) == Vector(tf.translation)
 def assert_approx_equal_pose(pose1, pose2):
     self.assertAlmostEqual(Vector(pose1), Vector(pose2))
     self.assertAlmostEqual(pose1.get_angle(),
                            pose2.get_angle(),
                            delta=math.radians(0.02))
예제 #25
0
 def inverse(self) -> "Transform":
     """Inverse transformation."""
     return Transform(
         -1 * Vector(self.translation).rotated(self.rotation.inverse),
         self.rotation.inverse,
     )
    def test_line_interpolation_func(self):
        line = Line([Point(0, 0), Point(1, 1)])

        # Test if line direction function works
        self.assertAlmostEqual(
            line.interpolate_direction(arc_length=0),
            Vector(r=1, phi=math.pi / 4),
            delta=TOLERANCE,
        )
        self.assertAlmostEqual(
            line.interpolate_direction(arc_length=line.length / 2),
            Vector(r=1, phi=math.pi / 4),
            delta=TOLERANCE,
        )
        self.assertAlmostEqual(
            line.interpolate_direction(arc_length=line.length),
            Vector(r=1, phi=math.pi / 4),
            delta=TOLERANCE,
        )

        self.assertAlmostEqual(line.interpolate_curvature(arc_length=0),
                               0,
                               delta=TOLERANCE)
        self.assertAlmostEqual(
            line.interpolate_curvature(arc_length=line.length / 4),
            0,
            delta=TOLERANCE)

        self.assertEqual(line.interpolate_pose(arc_length=0),
                         Pose(Point(0, 0), Vector(1, 1, 0)))

        circle = Line(
            reversed([
                p for p in Point(-1, 0).buffer(1,
                                               resolution=4096).exterior.coords
            ]))

        # Test if line direction function works
        self.assertAlmostEqual(circle.interpolate_direction(arc_length=0),
                               Vector(0, 1),
                               delta=TOLERANCE)
        self.assertAlmostEqual(
            circle.interpolate_direction(arc_length=circle.length / 2),
            Vector(0, -1),
            delta=TOLERANCE,
        )
        self.assertAlmostEqual(
            circle.interpolate_direction(arc_length=circle.length),
            Vector(0, 1),
            delta=TOLERANCE,
        )
        self.assertAlmostEqual(
            circle.interpolate_direction(arc_length=circle.length * 3 / 4),
            Vector(1, 0))
        self.assertAlmostEqual(
            circle.interpolate_direction(arc_length=circle.length / 4),
            Vector(-1, 0))

        self.assertAlmostEqual(circle.interpolate_curvature(arc_length=0),
                               1,
                               delta=TOLERANCE)
        self.assertAlmostEqual(
            circle.interpolate_curvature(arc_length=circle.length),
            1,
            delta=TOLERANCE)

        self.assertAlmostEqual(
            circle.interpolate_curvature(arc_length=circle.length / 2),
            1,
            delta=TOLERANCE)
        self.assertAlmostEqual(
            circle.interpolate_curvature(arc_length=circle.length / 4),
            1,
            delta=TOLERANCE)

        short_line = Line([
            [0, 0],
            [line_module.CURVATURE_APPROX_DISTANCE * 0.5, 0],
            [
                line_module.CURVATURE_APPROX_DISTANCE * 0.5,
                line_module.CURVATURE_APPROX_DISTANCE * 0.5,
            ],
        ])
        with self.assertRaises(ValueError):
            short_line.interpolate_curvature(0)

        just_long_enough_line = Line(
            [[0, 0], [2 * line_module.CURVATURE_APPROX_DISTANCE, 0]])
        self.assertEqual(just_long_enough_line.interpolate_curvature(0), 0)

        def assert_approx_equal_pose(pose1, pose2):
            """Substitute self.assertAlmostEqual because pose - pose is not implemented."""
            self.assertAlmostEqual(Vector(pose1.position),
                                   Vector(pose2.position))
            self.assertAlmostEqual(pose1.get_angle(),
                                   pose2.get_angle(),
                                   delta=math.radians(0.02))

        assert_approx_equal_pose(circle.interpolate_pose(arc_length=0),
                                 Pose(Point(0, 0), Vector([0, 1, 0])))
        assert_approx_equal_pose(
            circle.interpolate_pose(arc_length=circle.length / 4),
            Pose(Point(-1, 1), Vector([-1, 0, 0])),
        )
        assert_approx_equal_pose(
            circle.interpolate_pose(arc_length=circle.length / 2),
            Pose(Point(-2, 0), Vector([0, -1, 0])),
        )
        assert_approx_equal_pose(
            circle.interpolate_pose(arc_length=circle.length * 3 / 4),
            Pose(Point(-1, -1), Vector([1, 0, 0])),
        )
        assert_approx_equal_pose(
            circle.interpolate_pose(arc_length=circle.length),
            Pose(Point(0, 0), Vector([0, 1, 0])),
        )