Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Documentation
on:
push:
branches:
- master
- main
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/configure-pages@v5
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
with:
python-version: 3.x
- run: pip install zensical
- run: zensical build --clean
- uses: actions/upload-pages-artifact@v4
with:
path: site
- uses: actions/deploy-pages@v4
id: deployment
53 changes: 53 additions & 0 deletions docs/demos_gallery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Demos Gallery

Explore our robot demonstrations and visual content through this interactive carousel.

<div class="carousel-container">
<div class="carousel">
<div class="carousel-slide active">
<img src="../media/franka.gif" alt="Franka Robot Demo">
<div class="carousel-caption">Franka Robot - Following a moving target while base joint follows a sine curve</div>
</div>
<div class="carousel-slide">
<img src="../media/kinova.gif" alt="Kinova Robot Demo">
<div class="carousel-caption">Kinova Robot - Simulated robot with continuous joints and nullspace control</div>
</div>
<div class="carousel-slide">
<img src="../media/iiwa.gif" alt="IIWA Robot Demo">
<div class="carousel-caption">IIWA Robot - Simulated robot demonstration</div>
</div>
<div class="carousel-slide">
<img src="../media/franka_eight_reduced.gif" alt="Franka Eight Pattern">
<div class="carousel-caption">Franka Robot - Following target and being disturbed (contact)</div>
</div>
<div class="carousel-slide">
<img src="../media/franka_ns_reduced.gif" alt="Franka Nullspace Control">
<div class="carousel-caption">Franka Robot - Null space control demonstration</div>
</div>
<div class="carousel-slide">
<img src="../media/franka_teleop.gif" alt="Franka Teleoperation">
<div class="carousel-caption">Teleoperation using Vicon tracking system (Speed x4)</div>
</div>
<div class="carousel-slide">
<img src="../media/crisp_overview.png" alt="CRISP Overview">
<div class="carousel-caption">CRISP Framework Overview</div>
</div>
<div class="carousel-slide">
<img src="../media/robots_transparent.png" alt="Robot Collection">
<div class="carousel-caption">Compatible Robot Platforms</div>
</div>
<div class="carousel-slide">
<video src="../media/pap_demo.mp4" width="800" playsinline muted controls loop autoplay alt="Robot Collection">
<div class="carousel-caption">Pick and Place demo</div>
</div>
</div>

<button class="carousel-btn carousel-btn-prev" aria-label="Previous slide">&lt;</button>
<button class="carousel-btn carousel-btn-next" aria-label="Next slide">&gt;</button>

<div class="carousel-indicators"></div>
</div>

<div class="carousel-controls-info">
<p><strong>Navigation:</strong> Use arrow keys ← → or click the buttons to navigate through the gallery.</p>
</div>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
112 changes: 112 additions & 0 deletions docs/design_philosophy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Design Philosophy

We follow a few design strategies in CRISP.
This page describes our choices along with their respective pros and cons.

Our most important goal is to find a **meeting point between robotics and machine learning engineers.**
On the one hand, robotics engineers are already familiar with ROS2 and experienced in control and manipulation in general.
On the other hand, machine learning engineers who might not be familiar with these tools prefer to work with python-only environments.
We try to bring the strengths of both worlds in this project.

## Reuse Existing ROS2 Infrastructure

We decided to build CRISP on top of [ROS2](https://docs.ros.org/) for several reasons:

**Pros:**

- **Large community**: Many users and developers already familiar with tools for interacting with ROS2 systems (e.g., [rviz2](https://github.com/ros2/rviz), [rqt](https://docs.ros.org/en/rolling/Concepts/Intermediate/About-RQt.html), [ros2 CLI](https://docs.ros.org/en/rolling/Tutorials/Beginner-CLI-Tools.html)).
- **Existing ecosystem**: Well-tested packages for robot drivers, sensors, and visualization that we can leverage directly. The [ROS Index](https://index.ros.org/) lists thousands of available packages.
- **Simplified setup with pixi + robostack**: We use [pixi](https://pixi.sh/) with [robostack](https://robostack.github.io/) to provide a conda-like development environment.
This removes the traditional friction of ROS2 installation (no more sourcing setup.bash files or managing system dependencies) and makes it accessible to ML engineers accustomed to Python-centric workflows.

**Cons:**

- **"Framework jail"**: ROS2 imposes certain patterns (nodes, topics, executors) that may feel constraining for simple use cases.
Users must work within the ROS2 paradigm even for straightforward scripts.

In all objects, we try to abstract away ROS2 details behind simple Python APIs:
```python
from crisp_py import Robot

robot = Robot(...) # ROS2 node creation is hidden and spinned up internally to receive/send data
robot.wait_until_ready() # Waits for ROS2 topics to be alive
print(robot.end_effector_pose) # Direct access to data without dealing with ROS2 messages
```

## Configuration: YAML Files with Programmatic Freedom

CRISP uses YAML configuration files to define robots, sensors, and control pipelines.
This provides a declarative way to set up common scenarios without writing code.

**Pros:**

- **Quick iteration**: Change parameters without modifying source code.

**Cons:**

- **Less flexibility**: Complex behaviors may be hard to express in YAML rather than code.

However, we also provide full programmatic access.
Users can instantiate objects directly in Python and modify them freely when the YAML approach becomes limiting.
This is particularly useful for:

- Rapid prototyping and debugging
- *Custom components* that don't fit the YAML schema

Here is an example for the YAML config for an environment:
```yaml
gripper_mode: "absolute_continuous"

robot_config:
robot_type: "franka"
time_to_home: 2.0
publish_frequency: 50.0
home_config: [0.0, 0.1, 0.0,-1.94, 0.0, 2.0, 0.8]

gripper_config:
min_value: 0.0
max_value: 0.4
joint_state_topic: /gripper/joint_states
command_topic: /gripper/command

camera_configs:
- camera_name: "primary"
camera_frame: "primary_link"
resolution: [256, 256]
camera_color_image_topic: "third_person_camera/image_raw"
camera_color_info_topic: "third_person_camera/camera_info"
- camera_name: "wrist"
camera_frame: "wrist_link"
resolution: [256, 256]
camera_color_image_topic: "wrist_camera/color/image_rect_raw"
camera_color_info_topic: "wrist_camera/color/camera_info"

sensor_configs:
- sensor_type: "force_torque"
shape: [6,]
name: "ft_sensor"
data_topic: "external_wrench"
```

See the [example config files](https://github.com/utiasDSL/crisp_gym/tree/main/crisp_gym/config) or [here](https://github.com/utiasDSL/crisp_py/tree/main/crisp_py/config), and [how to define your own configs](getting_started_config.md) for more details.

## Data Collection directly in LeRobot Format, no rosbags

CRISP collects data directly in [LeRobot](https://github.com/huggingface/lerobot) format at a single fixed frequency, rather than saving [ROS bags](https://docs.ros.org/en/rolling/Tutorials/Beginner-CLI-Tools/Recording-And-Playing-Back-Data/Recording-And-Playing-Back-Data.html) for post-processing.

The LeRobot format stores episodes as [HuggingFace datasets](https://huggingface.co/docs/datasets/), making it easy to share, version, and load data for training. Each episode contains synchronized observations (images, joint states) and actions at a consistent frequency.

**Pros:**

- **Minimizes the gap between teleoperation and policy deployment**: The data you collect is immediately ready for training without conversion steps. What you record is exactly what your policy will see.
- **Consistent timing**: A single frequency ensures synchronized observations and actions, avoiding timestamp alignment issues common with bag files.
- **Simpler pipeline**: No need to manage bag files, replay them, and transform to training formats.
- **Easy sharing**: Datasets can be pushed directly to [HuggingFace Hub](https://huggingface.co/datasets) for collaboration.

**Cons:**

- **Less data captured**: You only save what's needed at the target frequency, potentially losing high-frequency sensor data that might be useful for debugging or alternative analysis.
- **Less flexibility**: Post-hoc resampling or different observation combinations require re-collection.

The recording is handled by the [RecordingManager](https://github.com/utiasDSL/crisp_gym/tree/main/crisp_gym/record) and here is an example script showing how the recording works [record_with_leader_follower](https://github.com/utiasDSL/crisp_gym/blob/main/crisp_gym/scripts/record_lerobot_format_leader_follower.py)
More details on recording can be found in the [getting started with the gym](getting_started_gym.md) documentation.
80 changes: 80 additions & 0 deletions docs/examples_camera.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Camera Examples

### Cameras

The cameras that we tested are:

- Any usb camera or webcam using the [ROS2 usb-cam](https://github.com/ros-drivers/usb_cam) package (see [a pixi wrapper here](https://github.com/danielsanjosepro/pixi_usbcam_ros2)),
- [Real Sense](https://github.com/IntelRealSense/realsense-ros/tree/ros2-master) which gives amazing ROS2 support,
- and [Orbbec](https://github.com/orbbec/OrbbecSDK_ROS2).

Check the [getting ros2 side ready guide](getting_started_controllers.md) to see some examples with cameras

## Run a camera node

We first start a camera node that publishes images to ROS2 topics.
Check the [getting ros2 side ready guide](getting_started_controllers.md) to see other examples with cameras.
If you use an usb camera / webcam, you can try [pixi_usbcam_ros2](https://github.com/danielsanjosepro/pixi_usbcam_ros2) and start it with:
```bash
pixi run -e jazzy usb_cam
```

## Access images from `crisp_py`

Now in the broadcasted images can be accessed from `crisp_py`.
Using the Camera class from `crisp_py`:
```python
"""Simple example for a camera. It shows the camera feed in a matplotlib window."""

import matplotlib.pyplot as plt
from crisp_py.camera import CameraConfig, Camera

camera_config = CameraConfig(
camera_name="primary",
camera_frame="primary_link",
resolution=[256, 256],
camera_color_image_topic="/image_raw",
camera_color_info_topic="/image_raw/camera_info",
)

camera = Camera(config=camera_config, namespace="")
camera.wait_until_ready()

# Display camera feed
plt.ion()
fig, ax = plt.subplots()
ax.axis("off")

frame = camera.current_image
im = ax.imshow(frame)
while True:
im.set_data(camera.current_image)
plt.pause(1.0 / 30.0)
```

Or by defining a camera in a YAML configuration file:
```yaml
camera_name: "primary"
camera_frame: "primary_link"
resolution: [256, 256]
camera_color_image_topic: "/image_raw"
camera_color_info_topic: "/image_raw/camera_info"
```

Then load the configuration and use the `make_camera` factory method (assuming that you added the config path to the `CRISP_CONFIG_PATH` environment variable as described in the [configuration guide](getting_started_config.md)):

```python
"""Example showing how to load camera configuration from a YAML file."""
import cv2
from crisp_py.camera import make_camera

camera = make_camera("your_config_file_name")
camera.wait_until_ready()

# Display one frame
plt.imshow(camera.current_image)
plt.axis("off")
plt.show()
```


3 changes: 3 additions & 0 deletions docs/examples_full_environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Full Environment Example

TODO
58 changes: 58 additions & 0 deletions docs/examples_gripper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Gripper Examples

This page provides a simple example on how to use grippers with `crisp_py`.

## Run a gripper node

We first start a gripper node that

- Publishes gripper the value of the gripper `JointState`, where the position value range should be known for further configuration,
- Listens to commands of type `Float32` with the same range as the joint state values to open/close the gripper,
- Optionally, a service to disable the torque of the gripper motors.

Check the [getting ros2 side ready guide](getting_started_controllers.md) to see the examples with grippers.

For...

- ... the Franka Hand, a node is directly started with the FR3 or Panda pixi robots publishes the gripper state and listens to commands automatically.
- ... Dynamixel-based grippers, you can use [dynamixel_wrapper](https://github.com/danielsanjosepro/dynamixel_wrapper). See the README.md to calibrate and get the range of the gripper.

## Access Gripper from `crisp_py`

For simple binary control of the gripper (open/close), you can use the `Gripper` class from `crisp_py` or the `make_gripper` factory function if you defined the gripper in a YAML configuration file (see [config guide](getting_started_config.md) for more information).
```python
"""Simple example to control the gripper."""
import time

from crisp_py.gripper import make_gripper

gripper = make_gripper("gripper_franka")
# or using GripperConfig directly:
gripper.config.max_delta = 0.15
gripper.wait_until_ready()

# Open and close the gripper
gripper.open()
time.sleep(2.0)
gripper.close()
time.sleep(2.0)
gripper.shutdown()
```

If using a disable torque service, you can enable/disable the torque of the gripper motors:
```python
from crisp_py.gripper import Gripper, GripperConfig

config = GripperConfig(
min_value=1046.0,
max_value=2065.0,
joint_state_topic="TODO/joint_states",
command_topic="TODO/command",
enable_torque_service="TODO/set_torque",
)
gripper = Gripper(config=config)
gripper.wait_until_ready()

gripper.enable_torque()
gripper.set_target(0.5) # Set to mid position
```
Loading