Inspection

Robotic quality inspection systems utilize robots to examine and assess products on a production line according to predefined criteria, such as size, shape, color, and surface defects. These systems have gained popularity in manufacturing, warehousing, and distribution facilities due to their ability to provide faster and more consistent inspections, thereby improving overall product quality and reducing human error. However, the increasing demand for 6 or 7-axis robots, while offering enhanced flexibility in handling complex inspections, also introduces greater complexity in programming and optimization, leading to higher costs for engineering, onsite support, and reprogramming.

In this tutorial, we introduce a short program created using the Jacobi platform, designed to be easily configurable for robotic quality inspection.

Video

Application Code

from pathlib import Path
import time

import numpy as np

from jacobi import Frame, Planner, JacobiError, PathFollowingMotion, LinearPath, CircularPath, BlendedPath, Studio


class InspectionProject:
    def __init__(self, planner, studio):
        self.planner = planner
        self.studio = studio
        self.object_list = [planner.environment.get_obstacle(o) for o in ['laptop', 'console', 'phone']]

    def go_home(self):
        """Move the robot to the home position."""

        home = [1.347, -1.208, 1.759, 4.16, -1.571, -0.224]
        traj = self.planner.plan(self.studio.get_joint_position(), home)
        self.studio.run_trajectory(traj)
        time.sleep(0.1)

    def visualize_path(self, path_type):
        """Visualize the calculated path of the robot tool with the attached camera in Studio."""

        path = path_type.calculate_path(0.3, 0.01)
        p = [path[i].translation for i in range(len(path))]
        self.studio.add_robot_path(p)

    def inspect_laptop(self):
        """Inspect the laptop object using a blended path."""

        # Move from the home position to the inspection start position
        start = Frame(x=-0.16, y=0.44, z=0.89, a=-2.3)
        traj = self.planner.plan(self.studio.get_joint_position(), start)
        self.studio.run_trajectory(traj)

        # Define the waypoints and robot tool velocity for the inspection path
        velocity = 0.3  # [m/s]
        waypoints = [
            start,
            Frame(x=0.17, y=0.44, z=0.89, a=-2.3),
            Frame(x=0.17, y=0.48, z=0.89, a=-2.5),
            Frame(x=-0.16, y=0.48, z=0.89, a=-2.5),
            Frame(x=-0.16, y=0.52, z=0.89, a=-2.7),
            Frame(x=0.17, y=0.52, z=0.89, a=-2.7),
            Frame(x=0.17, y=0.56, z=0.89, a=-2.9),
            Frame(x=-0.16, y=0.56, z=0.89, a=-2.9),
            Frame(x=-0.16, y=0.60, z=0.89, a=-3.10),
            Frame(x=0.17, y=0.60, z=0.89, a=-3.10),
            Frame(x=0.17, y=0.64, z=0.89, a=-3.3),
            Frame(x=-0.16, y=0.64, z=0.89, a=-3.3),
            Frame(x=-0.16, y=0.68, z=0.89, a=-3.5),
            Frame(x=0.17, y=0.68, z=0.89, a=-3.5),
            Frame(x=0.17, y=0.72, z=0.89, a=-3.7),
            Frame(x=-0.16, y=0.72, z=0.89, a=-3.7),
            Frame(x=-0.16, y=0.76, z=0.89, a=-3.9),
            Frame(x=0.17, y=0.76, z=0.89, a=-3.9),
        ]

        # Create the blended path and visualize it in Studio
        path = BlendedPath(waypoints, 0.01)
        self.visualize_path(path)

        # Create the motion object and set the reference configuration
        motion = PathFollowingMotion(path, velocity)
        motion.reference_config = self.studio.get_joint_position()

        # Plan the trajectory and run it in Studio
        traj = self.planner.plan(motion)
        self.studio.run_trajectory(traj)
        self.studio.reset()
        time.sleep(0.4)

    def inspect_console(self):
        """Inspect the console object using a circular path."""

        # Move from the home position to the inspection start position
        start = Frame(y=0.45, z=1.0, a=-2.6)
        traj = self.planner.plan(self.studio.get_joint_position(), start)
        self.studio.run_trajectory(traj)

        # Define the first circular path parameters and robot tool velocity
        velocity = 0.12  # [m/s]
        theta = 2 * np.pi
        center = [0.0, 0.61, 1.0]
        normal = [0.0, 0.0, 1.0]

        # Create the circular path and visualize it in Studio
        path = CircularPath(start, theta, center, normal, True)
        self.visualize_path(path)

        # Create the motion object, set the reference configuration and plan the first motion
        motion = PathFollowingMotion(path, velocity)
        motion.reference_config = self.studio.get_joint_position()
        traj = self.planner.plan(motion)

        # Create the linear path to move the robot tool to the next inspection position and visualize it
        pos = self.planner.environment.get_robot().calculate_tcp(traj.positions[-1])
        path = LinearPath(pos, Frame(y=0.45, z=0.85, a=-2.4))
        self.visualize_path(path)

        # Create the motion object, set the reference configuration and plan the second motion
        move_to_next = PathFollowingMotion(path, velocity)
        move_to_next.reference_config = traj.positions[-1]
        traj += self.planner.plan(move_to_next)

        # Define the second circular path parameters
        start = Frame(y=0.45, z=0.85, a=-2.4)
        center = [0.0, 0.61, 0.85]
        normal = [0.0, 0.0, -1.0]

        # Create the circular path and visualize it in Studio
        path = CircularPath(start, theta, center, normal, True)
        self.visualize_path(path)

        # Create the motion object, set the reference configuration and plan the third motion
        motion = PathFollowingMotion(path, velocity)
        motion.reference_config = traj.positions[-1]
        traj += self.planner.plan(motion)

        # Run the full trajectory in Studio
        self.studio.run_trajectory(traj)
        self.studio.reset()
        time.sleep(0.4)

    def inspect_phone(self):
        """Inspect the phone case object using a blended path."""

        # Move from the home position to the inspection start position
        start = Frame(x=-0.05, y=0.55, z=0.85, a=3.14)
        traj = self.planner.plan(self.studio.get_joint_position(), start)
        self.studio.run_trajectory(traj)

        # Define the waypoints and robot tool velocity for the inspection path
        velocity = 0.05  # [m/s]
        waypoints = [
            start,
            Frame(x=0.05, y=0.55, z=0.85, a=3.14),
            Frame(x=0.05, y=0.6, z=0.85, a=3.14),
            Frame(x=-0.05, y=0.6, z=0.85, a=3.14),
            Frame(x=-0.05, y=0.65, z=0.85, a=3.14),
            Frame(x=0.05, y=0.65, z=0.85, a=3.14),
        ]

        # Create the blended path and visualize it in Studio
        path = BlendedPath(waypoints, 0.01)
        self.visualize_path(path)

        # Create the motion object and set the reference configuration
        motion = PathFollowingMotion(path, velocity)
        motion.reference_config = self.studio.get_joint_position()

        # Plan the trajectory and run it in Studio
        traj = self.planner.plan(motion)
        self.studio.run_trajectory(traj)
        self.studio.reset()
        time.sleep(0.4)

    def switch_obstacle(self, name_visible):
        """Switch the view and collision checking to the specified obstacle."""

        for o in self.object_list:
            if o.name == name_visible:
                o.for_visual = True
                self.planner.environment.add_obstacle(o)
            else:
                try:  # noqa: SIM105
                    self.planner.environment.remove_obstacle(self.planner.environment.get_obstacle(o.name))
                except JacobiError:
                    pass
                o.for_visual = False
            self.studio.update_obstacle(o)
        time.sleep(0.5)

    def run(self):
        """Run the inspection project."""

        self.go_home()

        self.switch_obstacle('laptop')
        self.inspect_laptop()
        self.go_home()

        self.switch_obstacle('console')
        self.inspect_console()
        self.go_home()

        self.switch_obstacle('phone')
        self.inspect_phone()
        self.go_home()


if __name__ == '__main__':
    # Load project from Studio Download
    project_path = Path.home() / 'Downloads' / 'Inspection-UR5e.jacobi-project'

    # Run the inspection project
    inspection = InspectionProject(
        planner=Planner.load_from_project_file(project_path),
        studio=Studio(),
    )
    inspection.run()