Skip to content
Draft
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
79 changes: 79 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# MDPCalib Docker Compose Environment Configuration
# Copy this file to .env and adjust the values to match your setup.
#
# Usage:
# cp .env.example .env
# # edit .env as needed
# docker compose up

# --- Calibration data ---
# Absolute path to your local calibration data directory.
# This directory will be mounted to /data inside the mdpcalib container.
# It will contain:
# - cmrnext/ CMRNext model weights (*.tar files) — downloaded automatically if absent
# - calibration/ output directory (created automatically)
# - runtime_logs/ log files
#
# NOTE: Keep this directory completely separate from your rosbag storage (ROSBAG_PATH).
CALIBRATION_DATA_PATH=/CHANGE/ME/absolute/path/to/calibration/data

# Directory inside the container where CMRNext model weights are stored.
# Override only if you want to store weights elsewhere (e.g. a shared read-only volume).
# Defaults to /data/cmrnext — all three weights must reside in the same directory.
# CMRNEXT_WEIGHTS_DIR=/data/cmrnext

# --- Bag player ---

# Playback mode: 'ros2bag' (default) or 'kitti'
# ros2bag — play an existing ros2bag folder from ROSBAG_PATH / BAG_PATH
# kitti — download KITTI raw_synced data automatically, convert to ros2bag,
# and play it (data is cached in the 'kitti_data' named Docker volume)
# PLAYBACK_MODE=ros2bag

# Host directory containing your rosbag2 folders (used when PLAYBACK_MODE=ros2bag).
# Defaults to $HOME/rosbags; override to point to a different location.
# The entire directory is mounted to /rosbags inside the bag-player container.
# ROSBAG_PATH=${HOME}/rosbags

# Subfolder or specific bag inside /rosbags to play (PLAYBACK_MODE=ros2bag only).
# Defaults to /rosbags (plays the top-level folder).
# Set this if your bags are in a subdirectory, e.g. /rosbags/my_recording.
# BAG_PATH=/rosbags

# Set to "false" to play once and exit (default is to loop indefinitely).
# BAG_LOOP=true

# Playback rate multiplier (e.g. 0.5 for half speed).
# BAG_RATE=1.0

# --- KITTI mode variables (only used when PLAYBACK_MODE=kitti) ---
# KITTI raw_synced recording to download and play.
# Default: sequence 00 (residential, 2011_10_03, drive 0027) — ~9 GB download.
# Cached permanently in the 'kitti_data' named Docker volume.
# KITTI_DATE=2011_10_03
# KITTI_DRIVE=0027
# Which colour camera to use for calibration: 'left' (default) or 'right'
# KITTI_CAMERA=left
# Odometry sequence whose motion-compensated velodyne scans replace the raw ones.
# KITTI_SEQUENCE=00
# Set to "true" to delete the cached ros2bag and force re-conversion.
# Useful after changing KITTI_DATE/KITTI_DRIVE/KITTI_CAMERA. Leave unset for normal use.
# KITTI_FORCE_REBUILD=false
#
# For KITTI mode, also set FAST-LO to the Velodyne HDL-64E configuration:
# FAST_LO_CONFIG_FILE=/root/catkin_ws/src/mdpcalib/FAST_LO/config/velodyne.yaml

# --- ROS 2 sensor topics ---
# Adjust to match the topic names recorded in your rosbag.

ROS2_CAMERA_IMAGE_TOPIC=/camera/image_raw
ROS2_CAMERA_INFO_TOPIC=/camera/camera_info
ROS2_LIDAR_POINTS_TOPIC=/points_raw
ROS2_IMU_TOPIC=/imu/data

# Coordinate frame IDs written into the exported calibration YAML.
LIDAR_FRAME_ID=lidar
CAMERA_FRAME_ID=camera

# ROS 2 domain ID (must match across bag-player and mdpcalib containers).
# ROS_DOMAIN_ID=0
59 changes: 59 additions & 0 deletions .github/workflows/docker-build-push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Build and Push Docker Image

on:
push:
branches:
- main
tags:
- "v*"
workflow_dispatch:

env:
REGISTRY: ghcr.io
# github.repository is 'LCAS/MDPCalib'; ghcr.io automatically lowercases
# the path, so the published image is ghcr.io/lcas/mdpcalib.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the correct way that we want to do this instead of hosting on lcas.lincoln.ac.uk

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow pushes to ghcr.io because it uses the built-in GITHUB_TOKEN — no extra secrets needed. Pushing to lcas.lincoln.ac.uk is possible but requires a registry credential stored as a repository secret. If you'd like to add that registry, let me know and I'll add a second push step once the secret name is confirmed.

IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push:
name: Build and push Docker image
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ src/CMRNet/visibility_pkg/build/
src/CMRNet/visibility_pkg/dist/
__pycache__
src/FAST_LO/Log/*

# local Docker Compose environment — never commit real credentials
.env
109 changes: 105 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,106 @@ Sensor setups of robotic platforms commonly include both camera and LiDAR as the

Tested with `Docker version 28.0.1` and `Docker Compose version v2.33.1`.

- To build the image, run `docker compose build` in the root of this repository.
- Prepare using GUIs in the container: `xhost +local:docker`.
- Start container and mount rosbags: `docker compose run -v PATH_TO_DATA:/data -it mdpcalib`
Pre-built images are published automatically to the GitHub Container Registry on every push to `main` and on every version tag:

```
ghcr.io/lcas/mdpcalib:latest # latest build from main
ghcr.io/lcas/mdpcalib:<version> # e.g. ghcr.io/lcas/mdpcalib:1.2.3
```

The compose stack includes a VNC service ([`lcas.lincoln.ac.uk/vnc`](https://github.com/LCAS/ros2_pkg_template)) so no local X11 display or `xhost` configuration is required. The VNC viewer is accessible at **http://localhost:5801**.

##### Data prerequisites

Two separate host directories are required — keep them distinct to avoid conflicts:

| Directory | Configured by | Contents |
|---|---|---|
| Calibration data | `CALIBRATION_DATA_PATH` | CMRNext model weights (auto-downloaded if absent), calibration output |
| ROS bags | `ROSBAG_PATH` | Your ros2bag folder(s) |

- **CMRNext model weights** are downloaded automatically on first run from
`https://calibration.cs.uni-freiburg.de/downloads/cmrnext_weights.zip`
and stored in `$CALIBRATION_DATA_PATH/cmrnext/`. No manual download needed.
- **ros2bag** — place your rosbag2 folder inside `$HOME/rosbags/` (or set `ROSBAG_PATH` in `.env`)

##### Quick start — calibrate from a ros2bag

The `bag-player` service plays a ros2bag folder automatically. The bag loops by default so the calibration stack has enough time to complete. CMRNext model weights are downloaded automatically on first startup.

1. Download the compose file and example environment:
```bash
curl -O https://raw.githubusercontent.com/LCAS/MDPCalib/main/docker-compose.yaml
curl -O https://raw.githubusercontent.com/LCAS/MDPCalib/main/.env.example
cp .env.example .env
```
2. Edit `.env`:
- Set `CALIBRATION_DATA_PATH` to an empty directory for calibration output (weights are downloaded there automatically).
- Place your rosbag2 folder(s) in `$HOME/rosbags/` (or override `ROSBAG_PATH`).
- Set the ROS 2 topic names to match what is recorded in your bag.
3. Start the full stack (VNC + bag player + calibration):
```bash
docker compose up
```

The calibration starts automatically once the bag begins playing. Logs are written to
`$CALIBRATION_DATA_PATH/runtime_logs/`. The final calibration result is written to
`$CALIBRATION_DATA_PATH/calibration/ros2/extrinsics.yaml`.

Open **http://localhost:5801** in a browser to view the RViz / GUI output via VNC.

##### Quick start — calibrate from KITTI

The `bag-player` service also supports a built-in KITTI mode that downloads the KITTI raw_synced dataset automatically, converts it to a ROS 2 bag, and plays it. All data is stored in the `kitti_data` named Docker volume — no host bind mount or manual download is required.

> **⚠ Note:** The initial download is ~9 GB (raw sync + calibration + odometry velodyne). After the first run the data is cached in the Docker volume; subsequent `docker compose up` invocations skip both the download and the conversion step.

1. Copy and edit `.env` as above, then add / uncomment:
```ini
PLAYBACK_MODE=kitti
# Use the Velodyne HDL-64E FAST-LO config for KITTI:
FAST_LO_CONFIG_FILE=/root/catkin_ws/src/mdpcalib/FAST_LO/config/velodyne.yaml
```
2. Start the stack:
```bash
docker compose up
```
The bag-player will download the KITTI data into the `kitti_data` volume, convert it to a
ROS 2 bag, and start playback. The calibration stack starts automatically in `mdpcalib`.

To change the KITTI sequence, override `KITTI_DATE`, `KITTI_DRIVE`, and `KITTI_SEQUENCE` in `.env`
(see `.env.example` for details and the [KITTI mapping table](https://github.com/tomas789/kitti2bag/issues/10#issuecomment-352962278)).

The `.env` file controls the following variables (see [`.env.example`](.env.example) for full documentation):

| Variable | Default | Description |
|---|---|---|
| `CALIBRATION_DATA_PATH` | `./calibration-data` | Host path for calibration output + model weights (auto-downloaded) |
| `PLAYBACK_MODE` | `ros2bag` | `ros2bag` — play from file; `kitti` — download and play KITTI |
| `ROSBAG_PATH` | `$HOME/rosbags` | Host directory mounted as `/rosbags` in the bag-player (ros2bag mode) |
| `BAG_PATH` | `/rosbags` | Path inside container to the rosbag2 folder (ros2bag mode) |
| `BAG_LOOP` | `true` | Set to `false` to play once and exit |
| `BAG_RATE` | `1.0` | Playback rate multiplier |
| `KITTI_DATE` | `2011_10_03` | KITTI recording date (kitti mode) |
| `KITTI_DRIVE` | `0027` | KITTI drive number, zero-padded (kitti mode) |
| `KITTI_CAMERA` | `left` | Colour camera: `left` or `right` (kitti mode) |
| `KITTI_SEQUENCE` | `00` | Odometry sequence for motion-compensated velodyne (kitti mode) |
| `ROS2_CAMERA_IMAGE_TOPIC` | `/camera/image_raw` | Camera image topic |
| `ROS2_CAMERA_INFO_TOPIC` | `/camera/camera_info` | Camera info topic |
| `ROS2_LIDAR_POINTS_TOPIC` | `/points_raw` | LiDAR point cloud topic |
| `ROS2_IMU_TOPIC` | `/imu/data` | IMU topic |
| `LIDAR_FRAME_ID` | `lidar` | LiDAR frame ID in the exported YAML |
| `CAMERA_FRAME_ID` | `camera` | Camera frame ID in the exported YAML |

##### Building the images locally (for development)

The compose file includes `build:` contexts for both the `mdpcalib` and `bag-player` services, so you can build everything from source:

```bash
docker compose build
docker compose up
```

- Connect to a running container: `docker compose exec -it mdpcalib bash`


Expand Down Expand Up @@ -90,7 +187,11 @@ In the public release of our MDPCalib, we provide instructions for running camer

#### Downloading model weights 🏋️

Please download the model weights of CMRNext from this link and store them under: `/data/cmrnext`.
When using the Docker compose stack, model weights are **downloaded automatically** on first run
from `https://calibration.cs.uni-freiburg.de/downloads/cmrnext_weights.zip` and stored in
`$CALIBRATION_DATA_PATH/cmrnext/`.

For manual / non-Docker runs, download the weights and store them under `/data/cmrnext`:
- Model weights: https://calibration.cs.uni-freiburg.de/downloads/cmrnext_weights.zip

### 🏃 Running the calibration
Expand Down
36 changes: 36 additions & 0 deletions bag-player/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
ARG BASE_IMAGE=lcas.lincoln.ac.uk/ros:humble-latest
ARG ROS_DISTRO=humble

FROM ${BASE_IMAGE}

ARG ROS_DISTRO
ENV ROS_DISTRO=${ROS_DISTRO}

WORKDIR /workspace

# Copy the package manifest so rosdep can install all declared dependencies.
COPY package.xml /workspace/src/mdpcalib_bag_player/package.xml

RUN rosdep update --rosdistro "${ROS_DISTRO}" \
&& apt-get update \
&& rosdep install --from-paths /workspace/src --ignore-src -r -y \
&& rm -rf /var/lib/apt/lists/*

# Python packages required for KITTI → ros2bag conversion (no rosdep keys available)
RUN rm -rf /var/lib/apt/lists/* \
&& apt-get update --fix-missing \
&& apt-get -y install --no-install-recommends --fix-missing \
python3 \
python3-pip \
python3-colcon-common-extensions \
python3-colcon-mixin \
&& rm -rf /var/lib/apt/lists/*

# Pin numpy<2: pykitti (and several ros2 packages) are compiled against
# NumPy 1.x ABI and raise "cannot be run in NumPy 2.x" at runtime otherwise.
RUN pip3 install --no-cache-dir "numpy<2" pykitti progressbar2

COPY play_bag.sh play_kitti.sh kitti_to_ros2bag.py /workspace/
RUN chmod +x /workspace/play_bag.sh /workspace/play_kitti.sh

CMD ["/workspace/play_bag.sh"]
Loading