def load_trajectories(self):
        """Returns list of trajectories from file_name"""

        # Initialize data lists
        trajectories = []
        data = {}

        # Read file
        with open(self.file_name, "r") as f:
            data = json.load(f)

        # Convert data from dicts/lists to Trajectories
        # Structure:
        # "{"Name1": [
        #       [reverse, start_velocity, end_velocity, max_velocity, max_abs_acceleration, max_centr_acceleration],
        #       [[p1x, p1y, p1theta], [p2x, p2y, p2theta], ... ]
        #       ],
        #   "Name2": [...],
        # }"
        for name in data.keys():
            #Easier to work with
            tmp_props = data[name][0]
            tmp_poses = data[name][1]

            poses = []
            for p in tmp_poses:
                poses.append(to_pose(p[0], p[1], p[2]))

            trajectories.append(
                Trajectory(name, poses, tmp_props[0], tmp_props[1],
                           tmp_props[2], tmp_props[3], tmp_props[4],
                           tmp_props[5], tmp_props[6]))

        return trajectories
    def test_twist(self):
        """Tests Twist methods"""

        # Test constructor
        twist = Twist(1.0, 0.0, 0.0)
        pose = Pose()
        pose = pose.exp(twist)
        self.assertAlmostEqual(1.0, pose.translation.x)
        self.assertAlmostEqual(0.0, pose.translation.y)
        self.assertAlmostEqual(0.0, pose.rotation.get_degrees())

        # Test scaled
        pose = pose.exp(twist.scaled(2.5))
        self.assertAlmostEqual(2.5, pose.translation.x)
        self.assertAlmostEqual(0.0, pose.translation.y)
        self.assertAlmostEqual(0.0, pose.rotation.get_degrees())

        # Test logarithm
        pose = to_pose(2.0, 2.0, 90.0)
        twist = pose.log(pose)
        self.assertAlmostEqual(math.pi, twist.dx)
        self.assertAlmostEqual(0.0, twist.dy)
        self.assertAlmostEqual(math.pi / 2, twist.dtheta)

        # Test exponentiation: inverse of logarithm
        pose1 = pose.exp(twist)
        self.assertAlmostEqual(pose1.translation.x, pose.translation.x)
        self.assertAlmostEqual(pose1.translation.y, pose.translation.y)
        self.assertAlmostEqual(pose1.rotation.get_degrees(), pose.rotation.get_degrees())
    def test_save_load(self):
        """Tests saving and loading: Trajectory should be the same"""

        poses = []
        poses.append(to_pose())
        poses.append(to_pose(100, 300, 40))
        poses.append(to_pose(320, 350, 135))
        poses.append(to_pose(90, -100, -180))
        poses.append(to_pose(400, 50, 90))

        trajectory = Trajectory("Trajectory1", poses, True, True, 10, 20, 30,
                                40, 50)
        tmp = Trajectory("White_Line_Shot", poses, False, True, 10, 20, 30, 40,
                         50)

        trajectories = [tmp, trajectory]

        IO = JsonIO(path="Test/", name="jsonIO_unittest.json")

        IO.save_trajectories(trajectories)

        trajectory2 = IO.load_trajectories()[0]

        self.assertEqual(trajectory.name, trajectory2.name)
        self.assertEqual(trajectory.reverse, trajectory2.reverse)
        self.assertEqual(trajectory.current, trajectory2.current)

        self.assertAlmostEqual(trajectory.start_velocity,
                               trajectory2.start_velocity)
        self.assertAlmostEqual(trajectory.end_velocity,
                               trajectory2.end_velocity)
        self.assertAlmostEqual(trajectory.max_velocity,
                               trajectory2.max_velocity)
        self.assertAlmostEqual(trajectory.max_abs_acceleration,
                               trajectory2.max_abs_acceleration)
        self.assertAlmostEqual(trajectory.max_centr_acceleration,
                               trajectory2.max_centr_acceleration)

        for i in range(len(trajectory.poses)):
            with self.subTest(i=i):
                self.assertAlmostEqual(trajectory.poses[i].translation.x,
                                       trajectory2.poses[i].translation.x)
                self.assertAlmostEqual(trajectory.poses[i].translation.y,
                                       trajectory2.poses[i].translation.y)
                self.assertAlmostEqual(
                    trajectory.poses[i].rotation.get_degrees(),
                    trajectory2.poses[i].rotation.get_degrees())
    def random_points(self):
        """Returns list if multiple of pseudo-random poses, else 1 pose"""
        theta_range = [-20.0, 20.0]
        x_range = [15.0, 120.0]
        y_range = [-100.0, 100.0]
        x_delta = [60.0, 100.0]
        y_delta = [-30.0, 30.0]

        theta1 = uniform(theta_range[0], theta_range[1])
        theta2 = uniform(theta_range[0], theta_range[1])
        x = uniform(x_range[0], x_range[1])
        y = uniform(y_range[0], y_range[1])
    
        dx = uniform(x_delta[0], x_delta[1])
        dy = uniform(y_delta[0], y_delta[1])

        return [to_pose(x, y, theta1), to_pose(x + dx, y + dy, theta2)]
Пример #5
0
class RootWidget(GridLayout):
    #points = ListProperty([(500, 500),
    #                      [500, 100, 200, 0],
    #                      [300, 300, 400, 700]])
    points = ListProperty([500, 500, 500, 100, 200, 0, 300, 300, 400, 700])
    poses = [
        to_pose(300, 300, 45),
        to_pose(400, 500, 90),
        to_pose(600, 500, 0.0)
    ]
    trajectory = Trajectory(poses=poses)

    def trajectory_to_list(self, trajectory):
        point_list = []
        for point in trajectory.points:
            point_list.append(point.pose.translation.x)
            point_list.append(point.pose.translation.y)
        return point_list
Пример #6
0
    def test_trajectory_iterator(self):
        """Tests Trajectory iterator class"""

        poses = []
        poses.append(to_pose())
        poses.append(to_pose(36.0, 0.0, 0.0))
        poses.append(to_pose(60.0, 100.0, 0.0))
        poses.append(to_pose(160.0, 100.0, 0.0))
        poses.append(to_pose(200.0, 70.0, 45.0))

        traj = Trajectory(poses=poses)
        traj.time_parameterize_splines()
        iterator = TrajectoryIterator(traj)
        print(iterator.end_t)
        with open("Trajectory_Iterator_Test.csv", "w") as file:
            t = 0.1
            while not iterator.is_done():
                sample = iterator.advance(t)
                file.write(sample.__str__() + "\n")
Пример #7
0
    def test_trajectory(self):
        """Tests Trajectory class"""

        poses = []
        poses.append(to_pose())
        poses.append(to_pose(50, 100, 90))
        poses.append(to_pose(100, 150, 0))

        traj = Trajectory(poses=poses, reverse=False)

        self.assertAlmostEqual(3, len(traj.poses))
        self.assertAlmostEqual(2, len(traj.splines))

        with open("Trajectory_Test.csv", "w") as file:
            traj.update_pose(to_pose(50, 75, 135), 1)
            traj.time_parameterize_splines()
            for t in traj.points:
                file.write(t.__str__() + "\n")

        with open("Trajectory_Test.csv", "a") as file:
            traj.optimize_splines()
            traj.time_parameterize_splines()
            for t in traj.points:
                file.write(t.__str__() + "\n")
    def add_point(self):
        """Adds a point to current trajectory"""
        dx = [50.0, 120.0]
        dy = [-30.0, 30.0]
        dt = [-20.0, 20.0]
        # Generate semi - random point (Previous point + dx, dy, dt)
        pose = self.current_trajectory.poses[len(self.current_trajectory.poses) - 1].transform(to_pose(uniform(dx[0], dx[1]), uniform(dy[0], dy[1]), uniform(dt[0], dt[1])))
        pose.translation.x = limit2(pose.translation.x, 15, 614.25)
        pose.translation.y = limit2(pose.translation.y, -146.625, 146.625)

        # Add new point to trajectory
        self.current_trajectory.add_pose(pose)

        # Add new point to GUI
        self.screen.ids.point_list.add_widget(PointDrawer())
        self.screen.ids.point_list.children[0].ids.left_container.initial_update(pose.translation.x, pose.translation.y, pose.rotation.get_degrees())

        
        # Re-draw points to include new pose
        self.update_points()
class MainApp(MDApp):
    """Main app class"""

    poses = [to_pose(300, 800, 45), to_pose(400, 900, -45), to_pose(200, 500, 180.0)]
    trajectory = Trajectory(poses=poses)
    points = ListProperty()
    control_points = ListProperty()
    reverse = StringProperty("Forward Trajectory")
    jsonIO = JsonIO()

    trajectories = []
    current_trajectory = Trajectory()
    iterator = TrajectoryIterator(current_trajectory)

    timeout = .5
    max_vel = TimeDelayedBoolean(0, timeout)
    max_accel = TimeDelayedBoolean(0, timeout)
    max_centr_accel = TimeDelayedBoolean(0, timeout)
    start_vel = TimeDelayedBoolean(0, timeout)
    end_vel = TimeDelayedBoolean(0, timeout)

    prev_reverse = False
    prev_time = 0.0
    animation_active = False
    current_time = 0
    dt = .04

    s = 4.7
    origin = ListProperty([500,500])
    angle = NumericProperty(0)
    scalar = NumericProperty(s)
    wheel = ListProperty([2.5, 1.25])
    pos_scalar = NumericProperty(4)
    radius = ListProperty([s / 5, s / 5])
    vel_length = NumericProperty(0)
    accel_length = NumericProperty(0)
    accel_angle = NumericProperty(0)
    vector_scalar = NumericProperty(.6)
    triangle_scalar = NumericProperty(2)
    center_scalar = NumericProperty(2)


    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.screen = Builder.load_string(KV)

    def build(self):
        """Builds app and sets theme"""
        self.theme_cls.primary_palette = "Teal"
        self.theme_cls.accent_palette = "Cyan"
        self.theme_cls.theme_style = "Dark"
        return self.screen

    def close_application(self):
        """Callback for closing window"""

        #https://stackoverflow.com/questions/32425831/how-to-exit-a-kivy-application-using-a-button
        self.get_running_app().stop()

    def on_start(self):
        """Runs at start of application: Initializes stuff"""

        # Load Trajectories
        self.trajectories = self.jsonIO.load_trajectories()
        
        # Set Current Trajectory
        update = True
        for t in self.trajectories:
            if t.current:
                self.current_trajectory = t
                update = False

        if update and len(self.trajectories) > 0:
            self.current_trajectory = self.trajectories[0]

        self.iterator = TrajectoryIterator(self.current_trajectory)
        self.reset_animation()
        
        # Add each trajectory
        for trajectory in self.trajectories:
            self.screen.ids.content_drawer.ids.md_list.add_widget(
                ItemDrawer(icon="chart-bell-curve-cumulative", text=trajectory.name)
            )

        # Highlight correct Trajectory in drawerlist
        length = len(self.screen.ids.content_drawer.ids.md_list.children) - 1
        item = self.screen.ids.content_drawer.ids.md_list.children[length - self.get_current_index()]
        self.screen.ids.content_drawer.ids.md_list.set_color_item(item)
        
        self.screen.ids.title.text = self.current_trajectory.name

        self.set_constraints()
        self.set_reverse()
        # Sync GUI and Mathematical Poses
        self.update_poses()

        # Schedule saving trajectories every 500 ms
        Clock.schedule_interval(self.save_trajectories, .5)

        # Schedule updating constraints
        Clock.schedule_interval(self.update_constraints, self.dt)


    def on_stop(self):
        """Save Settings before leaving"""
        self.jsonIO.save_trajectories(self.trajectories)

    def save_trajectories(self, dt):
        """Save Trajectories to JSON Periodically"""
        self.jsonIO.save_trajectories(self.trajectories)

    def update_points(self):
        """Updates trajectory lines"""
        point_list = []
        for point in self.current_trajectory.points:
            point_list.append(self.translate_x(point.pose.translation.x))
            point_list.append(self.translate_y(point.pose.translation.y))
        self.points = point_list
        
        with self.screen.ids.control_points.canvas.after:
            #Rotate(angle=-self.angle)
            self.screen.ids.control_points.canvas.after.clear()
            Color(self.theme_cls.primary_dark[0],self.theme_cls.primary_dark[1],self.theme_cls.primary_dark[2],self.theme_cls.primary_dark[3])
            radius = 50 / 8
            for pose in self.current_trajectory.poses:
                
                Line(circle=(self.translate_x(pose.translation.x), self.translate_y(pose.translation.y), radius), width= 1.1, color=self.theme_cls.primary_color)
                
                
                Ellipse(size=(radius,radius), pos=(self.translate_x(pose.translation.x) - radius/2, self.translate_y(pose.translation.y) - radius/2))
        

        self.reset_animation()
        self.update_stats()

    def translate_x(self, x):
        """Returns x translation from inches to pixels"""
        return x * 1.5 + 174
    
    def translate_y(self, y):
        """Returns y translation from inches to pixels"""
        return y * 1.5 + 689.5

    def update_name(self, instance):
        """Updates name of current trajectory"""
        # Update trajectory name
        self.current_trajectory.name = instance.text

        # Update GUI name
        length = len(self.screen.ids.content_drawer.ids.md_list.children) - 1
        self.screen.ids.content_drawer.ids.md_list.children[length - self.get_current_index()].text = instance.text

    def optimize_trajectory(self):
        """Callback for optimizing current trajectory"""
        self.current_trajectory.optimize_splines()

        self.update_stats()
        # Update points to match optimization
        self.update_points()
    
    def mirror_trajectory(self):
        """Mirrors current Trajectory and adds it as a new trajectory"""
        self.add_trajectory(mirror_trajectory(self.current_trajectory))

    def animate_trajectory(self):
        if self.iterator.end_t < self.current_time:
            self.reset_animation()
        self.animation_active = not self.animation_active

    def add_point(self):
        """Adds a point to current trajectory"""
        dx = [50.0, 120.0]
        dy = [-30.0, 30.0]
        dt = [-20.0, 20.0]
        # Generate semi - random point (Previous point + dx, dy, dt)
        pose = self.current_trajectory.poses[len(self.current_trajectory.poses) - 1].transform(to_pose(uniform(dx[0], dx[1]), uniform(dy[0], dy[1]), uniform(dt[0], dt[1])))
        pose.translation.x = limit2(pose.translation.x, 15, 614.25)
        pose.translation.y = limit2(pose.translation.y, -146.625, 146.625)

        # Add new point to trajectory
        self.current_trajectory.add_pose(pose)

        # Add new point to GUI
        self.screen.ids.point_list.add_widget(PointDrawer())
        self.screen.ids.point_list.children[0].ids.left_container.initial_update(pose.translation.x, pose.translation.y, pose.rotation.get_degrees())

        
        # Re-draw points to include new pose
        self.update_points()

    def add_trajectory(self, trajectory = None):
        """Adds a trajectory to the trajectory list"""
        
        if trajectory == None:
            # Find a valid trajectory name (Format: Trajectory1, Trajectory2, ... Trajectory100)
            name = "Trajectory" + str(1)
            valid = False
            for i in range(100):
                name = "Trajectory" + str(i + 1)
                valid = False
                for t in self.trajectories:
                    if t.name == name:
                        valid = False
                        break
                    valid = True
                if valid:
                    break
            
            trajectory = Trajectory(name=name, poses=self.random_points(), 
                current=False, reverse=self.current_trajectory.reverse, start_velocity=self.current_trajectory.start_velocity, 
                end_velocity=self.current_trajectory.end_velocity, max_velocity=self.current_trajectory.max_velocity, 
                max_abs_acceleration=self.current_trajectory.max_abs_acceleration, 
                max_centr_acceleration=self.current_trajectory.max_centr_acceleration)

        # Add Trajectory to graphical list    
        item = ItemDrawer(icon="chart-bell-curve-cumulative", text=trajectory.name)
        self.screen.ids.content_drawer.ids.md_list.add_widget(
                item
            )

        # Add Trajectory to mathematical list
        self.trajectories.append(trajectory)

        self.set_active_trajectory(item)

    def random_points(self):
        """Returns list if multiple of pseudo-random poses, else 1 pose"""
        theta_range = [-20.0, 20.0]
        x_range = [15.0, 120.0]
        y_range = [-100.0, 100.0]
        x_delta = [60.0, 100.0]
        y_delta = [-30.0, 30.0]

        theta1 = uniform(theta_range[0], theta_range[1])
        theta2 = uniform(theta_range[0], theta_range[1])
        x = uniform(x_range[0], x_range[1])
        y = uniform(y_range[0], y_range[1])
    
        dx = uniform(x_delta[0], x_delta[1])
        dy = uniform(y_delta[0], y_delta[1])

        return [to_pose(x, y, theta1), to_pose(x + dx, y + dy, theta2)]

    
    def down(self, instance):
        """Callback that moves a trajectory pose down one"""
        index = instance.get_index()

        if index == len(self.current_trajectory.poses) - 1:
            return
        
        self.current_trajectory.move_pose(index, -1)
        self.update_poses()
    
    def up(self, instance):
        """Callback that moves a trajectory pose up one"""
        index = instance.get_index()

        if index == 0:
            return
        
        self.current_trajectory.move_pose(index, 1)
        self.update_poses()
    
    def delete_point(self, instance):
        """Callback to delete trajectory point"""

        if len(self.current_trajectory.poses) == 2:
            return
        self.current_trajectory.remove_pose(instance.get_index())
        self.update_poses()
        
    
    def update_reverse(self, checkbox, value):
        """https://kivymd.readthedocs.io/en/latest/components/selection-controls/"""
        
        slider_state = checkbox.active

        self.reverse = "Reverse Trajectory" if slider_state else "Forward Trajectory"
        
        if self.prev_reverse != slider_state:
            self.current_trajectory.update_reverse(slider_state)

        self.prev_reverse = slider_state

        self.update_stats()
    
    def set_reverse(self):
        """Sets reverse slider"""
        self.screen.ids.reverse.active = self.current_trajectory.reverse



    def set_active_trajectory(self, selected_instance):
        """Updates Current Trajectory"""
        self.screen.ids.content_drawer.ids.md_list.set_color_item(selected_instance)

        # Set all to False
        for t in self.trajectories:
            t.current = False

        # Update Current Trajectory
        self.current_trajectory = self.trajectories[selected_instance.get_index()]
        self.current_trajectory.current = True
        self.screen.ids.title.text = self.current_trajectory.name

        # Update Trajectory Iterator
        self.iterator = TrajectoryIterator(self.current_trajectory)
        self.reset_animation()
        self.update_poses()

        # Update Constraints
        self.set_constraints()
        self.set_reverse()

        # Update Time Slider
        self.screen.ids.time.min = self.iterator.start_t
        self.screen.ids.time.max = self.iterator.end_t
        

    def get_current_index(self):
        """Returns index of current trajectory"""
        i = 0
        for t in self.trajectories:
            if t == self.current_trajectory:
                return i
            i += 1
    
    def update_poses(self):
        """Updates displayed poses to match current trajectory""" 
        
        # Remove Children
        self.screen.ids.point_list.clear_widgets()
        
        # Re-add Children
        for p in self.current_trajectory.poses:
            self.screen.ids.point_list.add_widget(PointDrawer())
        
        # Update values
        for i in range(len(self.screen.ids.point_list.children)):
            c = self.screen.ids.point_list.children[len(self.screen.ids.point_list.children) - i - 1]
            c.ids.left_container.initial_update(self.current_trajectory.poses[i].translation.x, self.current_trajectory.poses[i].translation.y, self.current_trajectory.poses[i].rotation.get_degrees())
        
        self.update_stats()
        self.update_points()

    def update_stats(self):
        """Updates Generation, drive, and length"""
        self.screen.ids.generation.value = "{:.7f}".format(self.current_trajectory.generation_time)
        self.screen.ids.drive.value = "{:.3f}".format(self.current_trajectory.drive_time)
        self.screen.ids.length.value = "{:.3f}".format(self.current_trajectory.length)
        self.screen.ids.current_vel.value = "{:.3f}".format(self.iterator.current_sample.velocity)

    def update_constraints(self, dt):
        """Periodically called to update trajectory Constraints"""

        # Temporarily Save Values
        max_velocity = self.screen.ids.max_vel.ids.slider.value
        max_acceleration = self.screen.ids.max_accel.ids.slider.value
        max_centr_acceleration = self.screen.ids.max_centr_accel.ids.slider.value
        start_velocity = self.screen.ids.start_vel.ids.slider.value
        end_velocity = self.screen.ids.end_vel.ids.slider.value

        # Check and Update if Necessary
        updated = False
        if self.max_vel.update(max_velocity):
            self.current_trajectory.update_constraint(max_velocity, 0)
            updated = True
        if self.max_accel.update(max_acceleration):
            self.current_trajectory.update_constraint(max_acceleration, 1)
            updated = True
        if self.max_centr_accel.update(max_centr_acceleration):
            self.current_trajectory.update_constraint(max_centr_acceleration, 2)
            updated = True
        if self.start_vel.update(start_velocity):
            self.current_trajectory.update_constraint(start_velocity, 3)
            updated = True
        if self.end_vel.update(end_velocity):
            self.current_trajectory.update_constraint(end_velocity, 4)
            updated = True

        if updated:
            self.update_stats()
            self.reset_animation()

        if self.animation_active:
            self.update_animation(dt)
            self.update_stats()
        elif not (self.animation_active or epsilon_equals(self.prev_time, self.screen.ids.time.value)):
            self.calculate_model(self.screen.ids.time.value)
            self.update_stats()
        
        self.prev_time = self.screen.ids.time.value



    def set_constraints(self):
        """Sets constraints"""
        max_velocity = self.current_trajectory.max_velocity
        max_acceleration = self.current_trajectory.max_abs_acceleration
        max_centr_acceleration = self.current_trajectory.max_centr_acceleration
        start_velocity = self.current_trajectory.start_velocity
        end_velocity = self.current_trajectory.end_velocity

        self.max_vel = TimeDelayedBoolean(max_velocity, self.timeout)
        self.max_accel = TimeDelayedBoolean(max_acceleration, self.timeout)
        self.max_centr_accel = TimeDelayedBoolean(max_centr_acceleration, self.timeout)
        self.start_vel = TimeDelayedBoolean(start_velocity, self.timeout)
        self.end_vel = TimeDelayedBoolean(end_velocity, self.timeout)

        self.screen.ids.max_vel.ids.slider.value = max_velocity
        self.screen.ids.max_accel.ids.slider.value = max_acceleration
        self.screen.ids.max_centr_accel.ids.slider.value = max_centr_acceleration
        self.screen.ids.start_vel.ids.slider.value = start_velocity
        self.screen.ids.end_vel.ids.slider.value = end_velocity

    def reset_animation(self):
        """Resets Trajectory Animation"""
        self.calculate_model(0.0)
        self.animation_active = False
        self.iterator.reset()
        self.screen.ids.time.min = self.iterator.start_t
        self.screen.ids.time.max = self.iterator.end_t

    def update_animation(self, dt):
        """Updates Trajectory Animation"""
        self.calculate_model(self.current_time + dt)
        if self.current_time > self.iterator.end_t:
            self.animation_active = False
        
    
    def calculate_model(self, t):
        """Calculates parameters for the model and updates drawing"""
        
        # Updates Time and gets next trajectory point
        self.current_time  = t
        self.screen.ids.time.value = t
        point = self.iterator.advance(t)
        
        # Update Velocity
        self.origin = [self.translate_x(point.pose.translation.x), self.translate_y(point.pose.translation.y)]
        
        self.angle = point.pose.rotation.get_degrees()
        self.vel_length = 5.4 * abs(point.velocity) / self.current_trajectory.max_velocity
        

        # Update Acceleration
        centr = point.velocity ** 2 * point.pose.curvature / self.current_trajectory.max_centr_acceleration
        linear = point.acceleration / self.current_trajectory.max_abs_acceleration

        # If zero width, it leaves previous shape, so have small width
        self.accel_length = 5.4 * math.hypot(centr, linear)
        if epsilon_equals(self.accel_length, 0):
            self.accel_length = .01
        self.accel_angle = math.degrees(math.atan2(centr, linear))
    def test_trajectory_point(self):
        """Tests TrajectoryPoint"""

        # Test constructor
        traj = TrajectoryPoint()
        self.assertAlmostEqual(0, traj.pose.translation.x)
        self.assertAlmostEqual(0, traj.pose.translation.y)
        self.assertAlmostEqual(0, traj.pose.rotation.get_degrees())
        self.assertAlmostEqual(0, traj.pose.curvature)
        self.assertAlmostEqual(0, traj.pose.dCurvature)
        self.assertAlmostEqual(0, traj.t)
        self.assertAlmostEqual(0, traj.velocity)
        self.assertAlmostEqual(0, traj.acceleration)
        self.assertAlmostEqual(0, traj.index_floor)
        self.assertAlmostEqual(0, traj.index_ceil)

        traj1 = TrajectoryPoint(to_pose(0, 0, 0), 0, 0, 1, 6, 6)
        self.assertAlmostEqual(0, traj1.pose.translation.x)
        self.assertAlmostEqual(0, traj1.pose.translation.y)
        self.assertAlmostEqual(0, traj1.pose.rotation.get_degrees())
        self.assertAlmostEqual(0, traj1.pose.curvature)
        self.assertAlmostEqual(0, traj1.pose.dCurvature)
        self.assertAlmostEqual(0, traj1.t)
        self.assertAlmostEqual(0, traj1.velocity)
        self.assertAlmostEqual(1, traj1.acceleration)
        self.assertAlmostEqual(6, traj1.index_floor)
        self.assertAlmostEqual(6, traj1.index_ceil)

        traj2 = TrajectoryPoint(to_pose(.5, 0, 0), 1, 1, 0, 7, 7)
        self.assertAlmostEqual(.5, traj2.pose.translation.x)
        self.assertAlmostEqual(0, traj2.pose.translation.y)
        self.assertAlmostEqual(0, traj2.pose.rotation.get_degrees())
        self.assertAlmostEqual(0, traj2.pose.curvature)
        self.assertAlmostEqual(0, traj2.pose.dCurvature)
        self.assertAlmostEqual(1, traj2.t)
        self.assertAlmostEqual(1, traj2.velocity)
        self.assertAlmostEqual(0, traj2.acceleration)
        self.assertAlmostEqual(7, traj2.index_floor)
        self.assertAlmostEqual(7, traj2.index_ceil)

        traj3 = traj1.interpolate(traj2, .5)
        self.assertAlmostEqual(.125, traj3.pose.translation.x)
        self.assertAlmostEqual(0, traj3.pose.translation.y)
        self.assertAlmostEqual(0, traj3.pose.rotation.get_degrees())
        self.assertAlmostEqual(0, traj3.pose.curvature)
        self.assertAlmostEqual(0, traj3.pose.dCurvature)
        self.assertAlmostEqual(.5, traj3.t)
        self.assertAlmostEqual(.5, traj3.velocity)
        self.assertAlmostEqual(1, traj3.acceleration)
        self.assertAlmostEqual(6, traj3.index_floor)
        self.assertAlmostEqual(7, traj3.index_ceil)

        # Test Reverse interpolation
        traj3 = traj2.interpolate(traj1, .5)
        self.assertAlmostEqual(.125, traj3.pose.translation.x)
        self.assertAlmostEqual(0, traj3.pose.translation.y)
        self.assertAlmostEqual(0, traj3.pose.rotation.get_degrees())
        self.assertAlmostEqual(0, traj3.pose.curvature)
        self.assertAlmostEqual(0, traj3.pose.dCurvature)
        self.assertAlmostEqual(.5, traj3.t)
        self.assertAlmostEqual(.5, traj3.velocity)
        self.assertAlmostEqual(1, traj3.acceleration)
        self.assertAlmostEqual(6, traj3.index_floor)
        self.assertAlmostEqual(7, traj3.index_ceil)
    def test_pose(self):
        """Tests pose methods"""

        # Tests Constructor        
        pose = Pose()
        self.assertAlmostEqual(0, pose.translation.x)
        self.assertAlmostEqual(0, pose.translation.y)
        self.assertAlmostEqual(0, pose.rotation.get_degrees())
        self.assertAlmostEqual(0, pose.curvature)
        self.assertAlmostEqual(0, pose.dCurvature)

        pose = Pose(Translation(3.0, 4.0), from_degrees(45), .5, .1)
        self.assertAlmostEqual(3, pose.translation.x)
        self.assertAlmostEqual(4, pose.translation.y)
        self.assertAlmostEqual(45, pose.rotation.get_degrees())
        self.assertAlmostEqual(.5, pose.curvature)
        self.assertAlmostEqual(.1, pose.dCurvature)

        pose = to_pose(4.0, 3.0, -45, .4, -.2)
        self.assertAlmostEqual(4, pose.translation.x)
        self.assertAlmostEqual(3, pose.translation.y)
        self.assertAlmostEqual(-45, pose.rotation.get_degrees())
        self.assertAlmostEqual(.4, pose.curvature)
        self.assertAlmostEqual(-.2, pose.dCurvature)

        # Test transform
        pose1 = to_pose(3.0, 4.0, 90.0, .4, .2)
        pose2 = to_pose(1.0, 0.0, 0.0)
        pose3 = pose1.transform(pose2)
        self.assertAlmostEqual(3, pose3.translation.x)
        self.assertAlmostEqual(5, pose3.translation.y)
        self.assertAlmostEqual(90, pose3.rotation.get_degrees())
        self.assertAlmostEqual(.4, pose3.curvature)
        self.assertAlmostEqual(.2, pose3.dCurvature)

        pose1 = to_pose(3.0, 4.0, 90.0)
        pose2 = to_pose(1.0, 0.0, -90.0)
        pose3 = pose1.transform(pose2)
        self.assertAlmostEqual(3, pose3.translation.x)
        self.assertAlmostEqual(5, pose3.translation.y)
        self.assertAlmostEqual(0, pose3.rotation.get_degrees())
        self.assertAlmostEqual(0, pose3.curvature)
        self.assertAlmostEqual(0, pose3.dCurvature)

        # Test inverse
        identity = Pose()
        pose1 = to_pose(3.12123424, 8.286395, 93.1235, .5, .3)
        pose2 = pose1.transform(pose1.inverse())
        self.assertAlmostEqual(identity.translation.x, pose2.translation.x)
        self.assertAlmostEqual(identity.translation.y, pose2.translation.y)
        self.assertAlmostEqual(identity.rotation.get_degrees(), pose3.rotation.get_degrees())
        self.assertAlmostEqual(.5, pose2.curvature)
        self.assertAlmostEqual(.3, pose2.dCurvature)

        # Test interpolation
        # Movement from pose1 to pose2 along a circle with radius 10 centered at (3, -6)
        pose1 = to_pose(3.0, 4.0, 90.0, .5, .1)
        pose2 = to_pose(13.0, -6.0, 0.0, 1.0, .2)
        pose3 = pose1.interpolate(pose2, .5)
        expected_angle_radians = math.pi / 4.0
        self.assertAlmostEqual(3 + 10.0 * math.cos(expected_angle_radians), pose3.translation.x)
        self.assertAlmostEqual(-6 + 10.0 * math.sin(expected_angle_radians), pose3.translation.y)
        self.assertAlmostEqual(expected_angle_radians, pose3.rotation.get_radians())
        self.assertAlmostEqual(.75, pose3.curvature)
        self.assertAlmostEqual(.15, pose3.dCurvature)

        pose1 = to_pose(3.0, 4.0, 90.0)
        pose2 = to_pose(13.0, -6.0, 0.0)
        pose3 = pose1.interpolate(pose2, .75)
        expected_angle_radians = math.pi / 8.0
        self.assertAlmostEqual(3 + 10.0 * math.cos(expected_angle_radians), pose3.translation.x)
        self.assertAlmostEqual(-6 + 10.0 * math.sin(expected_angle_radians), pose3.translation.y)
        self.assertAlmostEqual(expected_angle_radians, pose3.rotation.get_radians())
        self.assertAlmostEqual(0.0, pose3.curvature)
        self.assertAlmostEqual(0.0, pose3.dCurvature)

        # Test distance
        self.assertAlmostEqual(math.pi * 5, pose1.distance(pose2))

        # Test mirror
        pose = to_pose(4.0, 3.0, -45, .4, -.2)
        pose1 = pose.mirror()
        self.assertAlmostEqual(4, pose1.translation.x)
        self.assertAlmostEqual(-3, pose1.translation.y)
        self.assertAlmostEqual(45, pose1.rotation.get_degrees())
        self.assertAlmostEqual(-.4, pose1.curvature)
        self.assertAlmostEqual(.2, pose1.dCurvature)

        # Test is_collinear
        pose1 = to_pose(3.0, 4.0, 90.0)
        pose2 = to_pose(13.0, -6.0, 0.0)
        self.assertFalse(pose1.is_collinear(pose2))

        pose1 = to_pose(3.0, 4.0, 90.0)
        pose2 = to_pose(3.0, 6.0, 90.0)
        self.assertTrue(pose1.is_collinear(pose2))
    def test_spline_construction(self):
        """Tests Quintic Hermite Spline construction"""

        s = create_quintic_spline()

        self.assertAlmostEqual(0, s.get_start_pose().translation.x)
        self.assertAlmostEqual(0, s.get_start_pose().translation.y)
        self.assertAlmostEqual(0, s.get_start_pose().rotation.get_degrees())

        self.assertAlmostEqual(0, s.get_end_pose().translation.x)
        self.assertAlmostEqual(0, s.get_end_pose().translation.y)
        self.assertAlmostEqual(0, s.get_end_pose().rotation.get_degrees())

        self.assertAlmostEqual(0, s.get_velocity(0))
        self.assertAlmostEqual(0, s.get_curvature(0))
        self.assertAlmostEqual(0, s.get_dCurvature(0))
        self.assertAlmostEqual(0, s.get_heading(0).get_degrees())
        self.assertAlmostEqual(0, s.get_point(0).x)
        self.assertAlmostEqual(0, s.get_point(0).y)
        self.assertAlmostEqual(0, s.get_pose(0).translation.x)
        self.assertAlmostEqual(0, s.get_pose(0).translation.y)
        self.assertAlmostEqual(0, s.get_pose(0).rotation.get_degrees())

        self.assertAlmostEqual(0, s.get_velocity(1))
        self.assertAlmostEqual(0, s.get_curvature(1))
        self.assertAlmostEqual(0, s.get_dCurvature(1))
        self.assertAlmostEqual(0, s.get_heading(1).get_degrees())
        self.assertAlmostEqual(0, s.get_point(1).x)
        self.assertAlmostEqual(0, s.get_point(1).y)
        self.assertAlmostEqual(0, s.get_pose(1).translation.x)
        self.assertAlmostEqual(0, s.get_pose(1).translation.y)
        self.assertAlmostEqual(0, s.get_pose(1).rotation.get_degrees())

        s = create_quintic_spline(p1=to_pose(3.0, 4.0, 90.0))
        self.assertAlmostEqual(0, s.get_start_pose().translation.x)
        self.assertAlmostEqual(0, s.get_start_pose().translation.y)
        self.assertAlmostEqual(0, s.get_start_pose().rotation.get_degrees())

        self.assertAlmostEqual(3.0, s.get_end_pose().translation.x)
        self.assertAlmostEqual(4.0, s.get_end_pose().translation.y)
        self.assertAlmostEqual(90.0, s.get_end_pose().rotation.get_degrees())

        self.assertAlmostEqual(6.0, s.get_velocity(0))
        self.assertAlmostEqual(0, s.get_curvature(0))
        #self.assertAlmostEqual(0, s.get_dCurvature(0))
        self.assertAlmostEqual(0, s.get_heading(0).get_degrees())
        self.assertAlmostEqual(0, s.get_point(0).x)
        self.assertAlmostEqual(0, s.get_point(0).y)
        self.assertAlmostEqual(0, s.get_pose(0).translation.x)
        self.assertAlmostEqual(0, s.get_pose(0).translation.y)
        self.assertAlmostEqual(0, s.get_pose(0).rotation.get_degrees())

        self.assertAlmostEqual(6.0, s.get_velocity(1))
        self.assertAlmostEqual(0, s.get_curvature(1))
        #self.assertAlmostEqual(0, s.get_dCurvature(1))
        self.assertAlmostEqual(90, s.get_heading(1).get_degrees())
        self.assertAlmostEqual(3, s.get_point(1).x)
        self.assertAlmostEqual(4, s.get_point(1).y)
        self.assertAlmostEqual(3, s.get_pose(1).translation.x)
        self.assertAlmostEqual(4, s.get_pose(1).translation.y)
        self.assertAlmostEqual(90, s.get_pose(1).rotation.get_degrees())
    def test_optimization(self):
        """Tests Spline optimization"""

        # Optimization test 1
        p1 = to_pose(0.0, 100.0, 270.0)
        p2 = to_pose(50.0, 0.0, 0.0)
        p3 = to_pose(100.0, 100.0, 90.0)

        splines = []
        splines.append(create_quintic_spline(p1, p2))
        splines.append(create_quintic_spline(p2, p3))

        start_time = time.perf_counter()
        self.assertTrue(optimize_spline(splines) < 0.014)
        print("Optimization time: {:.6}s".format(time.perf_counter() -
                                                 start_time))

        # Optimization test 2
        p4 = to_pose(degrees=90.0)
        p5 = to_pose(0.0, 50.0, 0.0)
        p6 = to_pose(100.0, 0.0, 90.0)
        p7 = to_pose(100.0, 100.0, 0.0)
        splines1 = []
        splines1.append(create_quintic_spline(p4, p5))
        splines1.append(create_quintic_spline(p5, p6))
        splines1.append(create_quintic_spline(p6, p7))

        start_time = time.perf_counter()
        self.assertTrue(optimize_spline(splines1) < 0.16)
        print("Optimization time: {:.6}s".format(time.perf_counter() -
                                                 start_time))

        # Optimization test 3
        p8 = to_pose()
        p9 = to_pose(50.0, 0.0, 0.0)
        p10 = to_pose(100.0, 50.0, 45.0)
        p11 = to_pose(150.0, 0.0, 270.0)
        p12 = to_pose(150.0, -50.0, 270.0)

        splines2 = []
        splines2.append(create_quintic_spline(p8, p9))
        splines2.append(create_quintic_spline(p9, p10))
        splines2.append(create_quintic_spline(p10, p11))
        splines2.append(create_quintic_spline(p11, p12))

        start_time = time.perf_counter()
        self.assertTrue(optimize_spline(splines1) < 0.16)
        print("Optimization time: {:.6}s".format(time.perf_counter() -
                                                 start_time))
    def properties_to_json(self, trajectory):
        """Helper method to convert Trajectory properties to list"""
        return [
            trajectory.current, trajectory.reverse, trajectory.start_velocity,
            trajectory.end_velocity, trajectory.max_velocity,
            trajectory.max_abs_acceleration, trajectory.max_centr_acceleration
        ]

    def points_to_json(self, trajectory):
        """Helper method to convert Trajectory points to list"""

        data = []

        # Add each point as a list
        for p in trajectory.poses:
            tmp = [p.translation.x, p.translation.y, p.rotation.get_degrees()]
            data.append(tmp)

        return data


if __name__ == "__main__":
    poses = []
    poses.append(to_pose())
    poses.append(to_pose(100, 300, 40))
    poses.append(to_pose(320, 350, 135))
    poses.append(to_pose(90, -100, -180))
    poses.append(to_pose(400, 50, 90))

    trajectory = Trajectory