Robot Ratatouille: Culinary Robot Project

ME C106A · Final Project Report

Robot Ratatouille: Culinary Robot Project

Culinary plating assistant made with an Intel RealSense camera paired with a UR7e Robotic Arm. Detects, scans, and memorizes plated dishes to replicate on command.

Authors: Nathaniel Chan, Nathan Lam, Christopher-Michael Garcia, Alan Tran, and William Ou UC Berkeley · ME C106A
Robot Ratatouille — the UR7e arm and a plated dish

1 Project Background

What if a robot could plate a dish as well as a human? Robotic culinary plating requires:

This project presents a robotic culinary plating assistant that uses a UR7e arm with an attached camera to capture a completed dish from multiple viewpoints and reconstruct a 3D model of the plated arrangement. The system identifies food items, estimates their poses relative to varying frames, and generates a pick-and-place plan to recreate the dish. Such a robot would be able to quickly, accurately, and consistently recreate dishes, allowing for shorter waiting times at restaurants.

2 Project Overview

Our project is split into two different phases: an inspection phase to scan each component on the plate, and an execution phase to begin plating food.

01 Inspection

The robot arm detects the plate and proceeds to orbit around the plate twice, taking multiple photos at different angles throughout the process. The photos are saved to a folder ready for segmentation.

More on Inspection →

02 Execution

The photos during the inspection phase are segmented and approximate positions and orientations for each object on the plate are recorded. Then, upon being presented with various ingredients in view, the robot begins plating.

More on Execution →

3 Design/Implementation

The pipeline splits into two ROS 2 packages — perception and planning — talking through topics, with MoveIt providing IK and motion planning underneath.

The perception package owns everything between the camera and a labeled 3D point: it runs YOLO on every RGB frame, lifts each bounding box into a 3D centroid using the RealSense depth pointcloud, and publishes detections in the robot’s base frame. The planning package owns the motion: it orbits the arm for inspection, accepts segmented poses from an offline 6D pose pipeline, and drives the pick-and-place state machine. The hardware layer — the RealSense camera, the custom-trained YOLO weights, and the UR7e arm — feeds frames into perception and is commanded by planning.

Read top-to-bottom, the data flow is:

  1. 01
    the camera publishes RGB plus a depth pointcloud
  2. 02
    DetectionNode pairs YOLO bounding boxes with depth points to produce world-frame centroids
  3. 03
    ArmCircler executes a 41-waypoint orbit and saves images for offline processing
  4. 04
    Commander runs SAM2 segmentation and 6D pose triangulation
  5. 05
    UR7e_CubeGrasp consumes the resulting object list and runs the hover → descend → grip → lift → place sequence.

MoveIt underlies the whole thing, providing inverse kinematics through /compute_ik and collision-aware trajectory planning through /plan_kinematic_path.

The diagram below mirrors that layout: hardware at the top sources data, perception extracts 3D world-frame detections, planning orchestrates the orbit and pick-and-place, and MoveIt sits underneath providing IK and motion planning. Click any block to jump straight to its detailed write-up in the Details section below.

Hardware
Perception
Planning

4 Details

The full deep-dive: a demo video showing the end-to-end pipeline, followed by a per-component explanation of the whole pipeline as well as critical design choices.

End-to-end demo run — orbit inspection → segmentation → pick-and-place sequence.

Hardware

UR7e Arm + Gripper

6-DoF Universal Robots UR7e with a parallel gripper. The arm is driven through the scaled_joint_trajectory_controller's FollowJointTrajectory action.

  • Gripper toggle: /toggle_gripper Triggers the gripper to open and close. It's assumed to be open at the startup.
  • Joint feedback: /joint_states /joint_states is the topic that contains the current joint states in radians. It's used in the IK solver, armcircler, and the pick and place.
Hardware

Intel RealSense D-series

Wrist-mounted depth camera providing aligned RGB, depth, and intrinsics over the standard RealSense ROS 2 topics.

  • /camera/.../color/image_raw — RGB stream used by YOLO to determine bounding boxes and centroids.
  • /camera/.../depth/color/points — This is the pointcloud used detection_node to find the centroid and depths of objects detected with YOLO.
  • /camera/.../color/camera_info — intrinsics used by pixel_to_world to calculate the position of objects based on where they appear on the image plane (camera).

Note — the camera launch is intentionally external to the bringup so we can restart it without tearing down the planner.

perception

Custom-Trained YOLO Model

A custom YOLO model was trained on images of 3D-printed food replicas for real-time object detection within the workspace. Done separately from the main design.

  • Dataset: Approximately 800 labeled images created using Label Studio.
  • Training diversity: Images were collected under varying conditions including different heights, viewing angles, lighting conditions, object distances, partial occlusions, and proximity to similarly-colored objects.
  • Iterative training: The model was retrained in batches of roughly 50 new images at a time, typically for around 50 epochs after each dataset expansion.
  • Assisted labeling: Once the dataset reached roughly 500 images, the partially-trained model was used to generate predicted labels to accelerate annotation and dataset growth.
  • Final training: The completed dataset was used for a final full training pass of approximately 50 epochs.
  • Weights: updated.pt (fine-tuned) is the primary model; previous models are retained as fallback checkpoints.
YOLO detection on plated foods
Every object class detected at 0.90+ confidence.
YOLO detection on a dense scene with overlapping food classes
YOLO model handles dense images with occlusions.

Note — food wasn't allowed in the lab; existing YOLO models trained on real food couldn't be utilized, requiring us to train one ourselves.

Design Choices

We chose YOLO as our object detector because it was fast, practical, and well-suited to our setup. Since the camera is constantly moving around the dish, it needs to reliably identify objects from many different angles and lighting conditions. YOLO handles this well with its single-stage architecture, which detects and classifies objects in one forward pass rather than multiple stages, keeping inference fast enough to run alongside the orbit in real time.

A custom model was necessary because our fruits are 3D-printed props with different colors and surface textures from real fruit, meaning no pretrained detector worked out of the box. We collected our own dataset of the props and fine-tuned YOLO from pretrained weights, which converged well even with a relatively small number of training images.

The alternatives we considered each had drawbacks. Classical computer vision techniques like color thresholding are too brittle under changing viewpoints and lighting, while heavier two-stage detectors like Faster R-CNN offer marginal accuracy gains at the cost of much slower training and inference. Since bounding boxes are sufficient to identify and localize each item on the plate, YOLO gave the best balance of accuracy, speed, and practicality within the time constraints of the project.

perception

DetectionNode

The main perception node. Runs YOLOv8 on every RGB frame, lifts each bbox into a 3D centroid through the depth pointcloud, and publishes the result in base_link.

  1. YOLO inference. YOLODetector.detect() returns class IDs, confidence, bbox, and 2D center.
  2. Depth lift. get_centroid_from_cloud_bbox() reprojects every cloud point into image coords, keeps the ones inside the bbox, and averages them.
  3. TF lift. transform_point() resolves camera_link → base_link via the static TF + UR kinematic chain.
  4. Plate gate. Any candidate within plate_radius = 0.14 m of the latest plate centroid is rejected.
  5. Publish. Highest-confidence survivor on /detected_pick_point; plate always published on /detected_plate_point.

Topic ordering matters — /detected_class must arrive before /detected_pick_point, otherwise per-class offsets silently fall back to defaults.

Design Choices

For plate exclusion, we chose to filter by radius from the plate centroid rather than by the plate's bounding box. A bounding box would incorrectly exclude objects sitting near the corners of the box but outside the actual plate boundary, since the plate itself is circular. Using a fixed radius from the centroid more closely matches the plate's true shape, so only objects genuinely on or overlapping the plate are dropped.

perception

YOLODetector

Thin stateless wrapper around Ultralytics YOLOv8. detect(image) returns a list of {class_id, class_name, confidence, bbox, center} dicts.

  • Weights: updated.pt (fine-tuned) is the default; best.pt and yolov8n.pt are kept as fallbacks.
  • Threshold: conf_threshold parameter on DetectionNode (default 0.5).
  • No state, no temporal smoothing — every frame is independent.
perception

pixel_to_world

Helper module that converts 2D detections into 3D centroids in base_link.

  • get_centroid_from_cloud_bbox() — projects every depth point into camera image coords, averages the ones inside the YOLO bbox.
  • transform_point() — TF lookup chained from camera_link through the static TF and the UR kinematic chain.
  • Returns a PointStamped in base_link, ready for IK.

Design Choices

A key challenge was getting YOLO's 2D bounding boxes to interface correctly with the 3D coordinates from the RealSense camera. Our initial approach was to take the bounding box centroid from YOLO and pair it with the depth value from the RealSense to produce a 3D point. In testing, this caused the arm to move to locations far from the detected object.

We identified two likely causes:

  • No grounded reference frame. The bounding box centroids were in a 2D image coordinate frame with no clear tie to the robot's base frame. As the camera moved, we had no reliable way to know where the image origin was in the world, making the transform from pixel coordinates to base frame unreliable.
  • Incorrect static transform. We had been using the same camera-to-wrist transform from a previous lab, but when we visualized the point cloud in RViz, the frames were visibly skewed — meaning the transform did not reflect our actual camera mount geometry.

With too many unknowns in the frame chain, we decided this approach would take too long to debug and moved to a more principled method using the camera intrinsic matrix.

Rather than starting from 2D pixel coordinates, we instead projected the RealSense's 3D point cloud into image space using the pinhole camera equations:

u = fx * X/Z + cx v = fy * Y/Z + cy

This let us identify which 3D points fell inside each YOLO bounding box, take the mean X, Y, and Z of those points, and publish that as the object's 3D centroid. Combined with correcting the static transform to match our actual camera mount, this approach worked reliably.

planning

ArmCircler — Orbit Inspection

The ArmCircler node is responsible for executing a sequence of joint trajectories using an action client and taking pictures at each waypoint. It uses the FollowJointTrajectory action type, which handles trajectory goals, feedback, and results.

Arm circling the plate during orbit inspection
  1. Plate detection: Uses YOLO to find the plate and extracts the bounding box centerpoint as the reference point for trajectory planning.
  2. Waypoint generation: Two nested for loops generate waypoints at two height levels—each tracing an arc around the plate—plus one point above facing down.
  3. Pose storage: Stores the position and quaternion of each waypoint in poses.npy ([x,y,z,qx,qy,qz,qw]).
  4. IK solving: Uses the IK planner from Lab 5 to compute joint angles for each waypoint and places them in the job queue.
  5. Trajectory execution: Executes trajectories one-by-one, taking an RGB picture with the RealSense camera at each waypoint. Images stored in captured_images/.
  6. Completion signal: Sends a True boolean value to the commander node to signify orbit completion.
  • Look-at orientation: rot_z · rot_y · rot_x always points the camera at the plate.
  • Heights: row 1 at 0.30 m, row 2 reversed at 0.20 m.
  • Total waypoints: 2 rows × 20 waypoints + 1 overhead shot = 41 frames.

These frames are captured as a multi-view orbit around the plate. Drag the scrubber below to step through all 41 waypoints. The full set lives in captured_images/ for the downstream segmentation and triangulation pipeline.

Captured orbit frame 1
Frame 1 / 41

Why orbit? Single-frame depth centroids degrade near the edge of the camera frame. Forty one views give the offline pipeline enough parallax for stable triangulation.

planning

Commander

  1. The Commander node orchestrates the entire pipeline, stitching together all the individual components and running in parallel with main.py via the bringup file. It gives the user visibility into what stage the robot is currently in — whether it is orbiting, computing masks, or executing pick and place.

  2. Commander also runs the filtering and planning logic before execution begins. It drops any triangulated position whose Z falls outside the range [-0.03, 0.15] m or whose confidence is at or below 0.6, then keeps only the single highest-confidence instance per class. From the surviving detections it computes the pick order and publishes the final task list to main.py.

  3. Beyond orchestration, Commander was also a significant debugging aid throughout development. We could easily skip sections of the pipeline, such as the orbit phase, to jump directly to the parts we were actively testing. In early stages, before the automatic planning algorithm was complete, we could manually specify which object to pick, which let us tune the per-class grasp offsets independently of the rest of the system. Overall, Commander served as a quality-of-life node that made the project easier to iterate on and helped the final demo run smoothly.

planning

6D Poses — Offline Position Reconstruction

segment_batch.py — The commander node tells this node to run after the armcircler node is done. This node is responsible for detecting the foods in each image using YOLO and SAM2. It takes in the images folder and outputs a folder of images called "segmented" with the YOLO bounding boxes and SAM2 masks. → segmented/<stem>/<stem>_<class>_<i>_mask.png.

Live segment window — YOLO boxes on the left, SAM2 masks on the right

This is an image from our tests with live masking, running YOLO detection and SAM2 masking live from a webcam. This gave us performance issues on our laptops, and we later ran out of time to integrate it into our live demo on the lab hardware. Ignore the bounding YOLO labels as our model was still in progress.

Background and Motivation

The original plan for localizing food items on the plate was to use COLMAP to reconstruct a 3D point cloud from the 41 images captured during the arm's orbital inspection pass. This was abandoned for two reasons:

  • Too slow. COLMAP's feature matching and bundle adjustment pipeline is computationally expensive — waiting several minutes between the orbit phase and the pick phase was not acceptable at demo timescales.
  • Too sparse. Food items are small, visually uniform objects on a reflective plate surface, which frustrates feature-based reconstruction. There are few distinctive keypoints, and the ones that exist don't triangulate reliably across views. The resulting point cloud had little to no geometry in the regions that actually mattered.

The solution was to abandon general-purpose reconstruction entirely and exploit the structure of the problem instead. Two facts made this possible:

  • Because the robot arm is the image capture platform, every camera pose is already known precisely from forward kinematics — there is no need to solve the structure-from-motion problem at all.
  • Because YOLO already identifies objects with bounding boxes and instance masks, 3D localization reduces to a much simpler sub-problem: given a set of known camera poses and 2D observations of an object, find its 3D position. This is classical multi-view triangulation, and it runs in milliseconds.

What the Pipeline Does

The offline pose estimation pipeline takes three inputs:

Poses
The 40 end-effector poses recorded during the orbit (poses.npy)
Images
The 40 corresponding images (captured_images/)
Masks
The per-image YOLO+SAM2 instance masks (segmented/)

It outputs results/object_poses.npy — a list of detected objects with their estimated 3D position in the robot's base_link frame and a confidence score.

The pipeline runs as a subprocess under the SAM2 virtual environment's Python interpreter rather than the ROS Python, because it depends on PyTorch, SAM2, trimesh, and pyrender — none of which are installed in the ROS workspace. This keeps the two dependency trees cleanly separated while still allowing the Commander node to trigger the pipeline and consume its output.

How Position-Only Mode Works

The pipeline runs in position_only=True mode, which skips the full 6DoF render-and-compare path and estimates only 3D position. It does this in three steps.

Per-object masks and centroids projected back onto a captured frame
Algorithm input: SAM2 masks and triangulated centroids projected back onto a captured frame.
3D scatter of estimated food positions with camera frustum
Algorithm output: final per-object positions in base_link, with camera frustum for scale.
  1. 01

    Step 1: Camera pose computation. For each waypoint, the known end-effector pose is converted into a camera-to-world transform via a chain of three operations:

    • End-effector pose (position + quaternion) → 4x4 homogeneous transform world_T_ee
    • Invert → ee_T_world
    • Compose with a fixed camera-mount offset T_CAM_FROM_EE (camera sits +3 cm forward and 13.5 cm above the wrist) → cam_T_world

    This transform chain is what makes the approach work: because the robot arm is the camera rig, known kinematics replace the bundle adjustment step entirely.

  2. 02

    Step 2: Per-view 2D observation. For each image, the pipeline loads the SAM2 masks and processes each detected object:

    • Centroid: computed as the median pixel coordinate (median rather than mean, to be robust against edge clipping)
    • Apparent radius: estimated from mask area as r_px = sqrt(area / pi)
    • Depth: recovered from apparent size using z_m = fx * (diameter_m / 2) / r_px, where diameter_m is the known physical diameter of the object class stored in constants.py
    • X and Y in camera frame: unprojected from the centroid using the standard pinhole inverse

    The depth estimate is essentially monocular depth recovery using known object size as a scale reference — a simple but effective stand-in for a depth camera at this scale.

  3. 03

    Step 3: RANSAC triangulation. Each object accumulates observations across all views in which it appears. These are passed to a RANSAC-based weighted Direct Linear Transform (DLT) triangulator:

    • The RANSAC outer loop randomly samples pairs of views, triangulates a candidate 3D position from each pair, and counts inliers by reprojection error (threshold: 8 pixels)
    • The best inlier set is then used for a final weighted DLT, where each view's contribution is weighted by its mask area — larger, more clearly visible instances get more influence
    • This robustly rejects views where the object was partially occluded, seen at a glancing angle, or near the image boundary

    Confidence is computed as the fraction of views in which the object was visible, capped at 1.0. The Commander uses this downstream to drop detections at or below 0.6 confidence and keep only the single highest-confidence instance per class.

Why Not Full 6DoF Orientation?

Estimated poses with X/Y/Z axes overlaid on a captured frame plus the per-object pose table
Render-and-compare result: red/green/blue axes show each object's estimated orientation, with the terminal table giving (Z°, Y°, X°) Euler angles, translation, and per-object confidence.

The full pipeline (when position_only=False) would additionally estimate object orientation using DINOv2 visual features and rendered STL reference views — essentially a render-and-compare approach where the object's pose is refined until the rendered appearance matches the observed one. This was left unused for the demo for the same reasons COLMAP was abandoned:

  • It requires STL mesh models for every object class
  • It requires a GPU for DINOv2 inference
  • It adds significant computation per object

For a pick-and-place task on roughly symmetric food items, orientation is largely unnecessary anyway. The gripper approaches from above, and the contact geometry is forgiving enough that a position-only estimate is sufficient for a reliable grasp.

The orientation that does matter — the end-effector's approach angle relative to each object — is handled not by the pose estimator but by the per-class grasp offset table in main.py. This encodes empirically tuned x/y corrections for camera-to-gripper misalignment and z heights for the pre-grasp, grasp, and lift phases. This is simpler and more reliable than estimating object orientation from images, because it captures the actual systematic error of the specific robot and camera mount rather than inferring object geometry from appearance.

planning

UR7e_CubeGrasp — Pick-and-Place State Machine

Once segmentation and position estimation are complete, the commander node waits for user input before beginning pick and place. After the user presses Enter, the commander node sends a JSON-encoded list containing the sorted objects and their estimated positions to main.py (the pick-and-place node), and execution begins.

Arm performing pick and place on the plate

The pick-and-place pipeline is organized as a state machine that manages the busy semaphore, the job_queue, and the full grasp sequence. Every arm motion is executed asynchronously through trajectory actions, while gripper commands are handled synchronously through service calls. The node uses the same action design as the arm circler to fill a queue of joint trajectories and execute them sequentially.

All operations begin from the observation frame. After arm circler finishes and the user proceeds with pick and place, the arm moves to the observation frame before starting the first cycle. After every pick and place operation, the arm returns to this pose so the camera can view the entire workspace and recalculate object positions. Re-observing the workspace prevents error buildup that would occur if the arm relied only on predetermined coordinates.

  1. Home — observation frame (idle pose); gripper open. Camera has a full view of the workspace for object detection and position recalculation.
  2. Object selection — the pick-and-place node uses the sorted list of foods and estimated positions from the JSON payload to determine pick order. YOLO detections provide object classes and centroids for both foods and the plate.
  3. Pre-pregrasp — IK solution generated at (cx, cy, cz + 0.5) so the camera can observe the object from a centered viewpoint, reducing fisheye distortion.
  4. Refine — a second detection pass recalculates the centroid from the centered viewpoint. This improves consistency when objects are partially visible and rebuilds the queue using the full PICK_OFFSETS configuration for the detected object class.
  5. Hover → descend → grip → lift — chained IK solutions reuse the previous joint solution as the seed to avoid discontinuities such as elbow flips. Calibrated hardcoded offsets compensate for object height and form factor differences between fruits while gripping.
  6. Over plate → lower → release — translate relative to the plate centroid detected by YOLO using the placement positions from the JSON task list. The arm moves to (plate_x, plate_y, drop_z + 0.20), descends using hardcoded z and y offsets, releases the object, and retracts upward. The z-offset compensates for imperfect table-depth estimation and gripper height, while the y-offset improves lateral placement alignment.
  7. Return home — _going_home clears busy once the arm settles at the observation frame, allowing the next pick cycle to begin safely. This repeats until no objects remain in the task list and the module declares the process complete.
Class x y pre_grasp_z grasp_z lift_z
apple 0.01 0.005 0.16 0.125 0.185
tomato 0.015 0.005 0.16 0.14 0.185
cake 0.02 0.005 0.20 0.15 0.20
strawberry 0.02 0.005 0.20 0.14 0.20
default 0.02 0.005 0.16 0.14 0.185

State Management Flags

The pipeline uses boolean state flags to coordinate perception, trajectory execution, and grasp planning. These flags prevent overlapping commands and ensure the robot only reacts to detections when it is in a safe observation state.

Flag Purpose
busy Indicates that the arm is currently moving or an active pick cycle is running. New detections are ignored until the arm returns home or a trajectory failure occurs.
_refining Enabled while the arm moves toward the pre-pregrasp pose. During this phase, live detections are temporarily suppressed until refined centroid estimation is completed.
_at_pre_pregrasp Signals that the arm has reached the pre-pregrasp waypoint and that refined centroid detection should begin.
_going_home Indicates that the home waypoint is currently queued or the arm is actively returning to the observation frame.
pick_place_enabled Enables the full pick-and-place pipeline after activation from the commander node.
_pick_triggered Prevents duplicate pick attempts by ensuring only one live detection can trigger a pick operation at a time.

Detection filtering: The detection node filters out objects within a specified radius of the plate so the pick-and-place node does not attempt to pick foods that have already been placed.

Design Choices

One challenge was determining the correct grasp offsets for each object. Due to varying heights and geometries, the calculated centroid was skewed differently depending on the object, so we introduced per-class hardcoded offsets that let us reuse the same centroid calculation algorithm while correcting for each object's specific error. A similar approach was used for placing, though here a single shared offset was sufficient to correct the constant positional error in the calculated drop pose.

A deeper issue was that even for the same object, the offsets were inconsistent depending on where the object fell in the camera's field of view from the home position. Objects near the edges were distorted by lens curvature, and objects partially outside the frame had their centroid calculated from only the visible portion. To address this, we implemented a two-phase picking approach. Rather than descending directly to the centroid calculated at the home position, the arm first moves to that centroid with an additional 0.5 m in Z, placing the camera directly above the object at a height where it appears near the image center with minimal distortion. The centroid is then recalculated from this more controlled viewpoint before the arm descends to grasp. By always computing the final centroid from a roughly fixed overhead frame, the offsets became significantly more consistent.

For the placing order, we needed a planning algorithm to decide which objects to plate first. We chose to sort by ascending Z coordinate, picking the lowest object first. For our use case this worked well, though we recognize that a more complex plating arrangement — layered components or radial layouts — would call for a more sophisticated ordering strategy, such as bottom-to-top or center-outward.

5 Results

INDIVIDUAL OBJECT MASKING

Each object is individually segmented, even when stacked on top of each other.

FINAL PLATING — BEFORE / AFTER

Drag the handle to wipe between the original plate and the robot’s recreation.

6 Conclusion

Overall, the project was successful in accordance to our original proposal. With our computer vision and placement for rigid objects working, we firmly believe our project can continue to evolve for even more complex culinary situations. While the video demonstration was long in duration, in practical use, the robot will only need to scan a dish once. After that, the robot can recreate that dish as many times as necessary.

Working

  • Circular arm movement for scanning
  • Live YOLO bounding boxes + object classification
  • SAM2 batch segmentation to estimate poses
  • RANSAC triangulation
  • Automated pick and place, including stacked ingredients

Future Improvements

  • A more organized GitHub repository and file directory. 4+ hours spent debugging related issues
  • Custom gripper to allow for larger objects and to not crush soft objects
  • Allow operation with any UR7e robotic arm without requiring hard-coded offsets
  • Utilizing real food objects (thus allowing usage of existing comprehensive YOLO models)

Difficulties

  • RealSense camera quality downgraded to 680x480p: using higher resolution (as done in lab originally) results in errors
  • Lack of consistency → loss of time. Project is typically consistent for one UR7e arm at a time due to hard-coded offsets
  • YOLO model training takes quite a lot of time, required many hours of training and almost 1000 labeled images for accurate performance

7 AI Use Disclosure

Throughout the project creation process, ChatGPT, Gemini, and Claude Code were used in supporting roles for debugging, code integration, logic design, and website development.

Bug Fixes

Although we were able to independently resolve most issues, several sections of the codebase contained logical errors, particularly during integration of the UR7e arm. These issues often resulted in unintended robot behavior or runtime errors that prevented execution. AI was used to suggest edits, including adding, removing, and modifying lines of code to resolve these bugs efficiently.

Code Integration

The project was developed in modular components (arm circling, bounding boxes, pixel masking, etc), allowing parallel development across team members. However, while each module functioned independently, integrating them into a unified system often resulted in code that would often fail after minor adjustments. AI was used to consolidate these components into a cohesive pipeline, including merging scripts and restructuring packages, to allow the packagers to work together.

Logic and Implementation Support

Although the intended system behavior was well understood, AI was used to assist in translating high-level ideas into implementation details. This included generating pseudocode, as well as adjusting logic as necessary to allow the pipeline to work as intended.

Website Development

The formatting, layout, and visual design of this website were assisted using AI tools. However, all content—text descriptions, images, and videos— were manually created and curated.

Additional Note

The Lab 5 starter code was used as the baseline for portions of this project.