RPi Camera Ensemble: Central orchestration of video acquisition across multiple Raspberry Pi cameras.
Project description
RPi Camera Ensemble
Central orchestration of synchronized video acquisition across multiple Raspberry Pi cameras.
Runs a Conductor on the acquisition machine that controls one or more Agents: each Agent is a FastAPI server running on an individual Raspberry Pi, managing local camera acquisition and GPIO-based TTL synchronization.
Features
- Multi-camera orchestration: start/stop all cameras with a single call; UUID-guarded sessions prevent accidental stops
- TTL synchronization: per-frame GPIO pulses (emitter) and edge-detection timestamps (receiver) via
lgpio(RPi 4 + RPi 5) or legacypigpio - PiCamera2 backend: uses the modern
libcamerastack; preview and recording states - REST API on each agent:
GET /status,POST /agent/acquisition/init,POST /agent/acquisition/record,POST /agent/acquisition/stop - Heartbeat monitoring: background health checks with configurable interval
- Disk space check: pre-acquisition guard based on estimated bitrate and free space
- CLI:
rce-agent run/configentrypoints for systemd or docker-compose deployment
Architecture
Conductor (acquisition machine)
│ HTTP via aiohttp
├── AgentClient → Agent (RPi cam01) → Acquisition → PiCamera2
├── AgentClient → Agent (RPi cam02) → Acquisition → PiCamera2
└── AgentClient → Agent (RPi cam03) → Acquisition → PiCamera2
│
TTL out pin (lgpio)
TTL in pin (lgpio)
Requirements
- Python ≥ 3.10
- On each Raspberry Pi:
picamera2(system package),lgpio(pip install lgpio),libgpiod2(system package) - On the acquisition machine (Conductor):
aiohttp
Installation
# Acquisition machine (Conductor only)
pip install "rpi-camera-ensemble[conductor]"
# On each Raspberry Pi (Agent + TTL sync)
pip install "rpi-camera-ensemble[agent,lgpio]"
Install from source:
git clone https://github.com/MurineShiftWork/rpi-camera-ensemble.git
cd rpi-camera-ensemble
pip install -e ".[agent,lgpio,dev]"
Quick start
Agent (on each Raspberry Pi)
Create a config file (agent.yaml):
instance_name: cam01
api_host: 0.0.0.0
api_port: 8000
data_dir: /data/recordings
log_dir: /data/logs
Start the agent:
rce-agent run --config agent.yaml
Generate a template config:
rce-agent config --to-template agent.template.yaml
Conductor (on the acquisition machine)
from rpi_camera_ensemble.conductor.conductor import Conductor
from rpi_camera_ensemble.config.acquisition import EnsembleAcquisitionConfig
from rpi_camera_ensemble.config.conductor import ConductorConfig
ensemble_cfg = EnsembleAcquisitionConfig.from_yaml("ensemble.yaml")
conductor_cfg = ConductorConfig(log_dir="/data/logs/conductor")
with Conductor(config=conductor_cfg, ensemble_config=ensemble_cfg) as conductor:
conductor.setup_agents()
conductor.initialize_acquisition(
acquisition_path="/data/recordings/experiment_001",
acquisition_name="experiment_001",
)
conductor.start_preview()
conductor.start_recording()
# ... recording ...
conductor.stop_acquisition()
Ensemble config (ensemble.yaml):
agents:
cam01:
api_url: http://192.168.1.10:8000
cam02:
api_url: http://192.168.1.11:8000
TTL synchronization
TTL is configured per agent inside ensemble.yaml under camera_config.ttl. Default backend is lgpio (recommended):
agents:
cam01:
api_url: http://192.168.1.10:8000
camera_config:
ttl:
library: lgpio
gpiochip: 0 # RPi 4: 0, RPi 5: 4
out_pin: 25 # emits 1 ms pulse on each encoded frame
in_pin: 23 # records edge timestamps from external trigger
out_duration_us: 1000
lgpio requires libgpiod2 on the Pi:
sudo apt install libgpiod2
pip install lgpio
RPi 5 note: set
gpiochip: 4(RP1 southbridge). RPi 4 usesgpiochip: 0.
Output structure
Each agent writes files named {basename}.rce.{instance}.{timestamp}.{type}.{ext}:
/data/recordings/experiment_001/
├── session.rce.cam01.20260101_120000_000000.video.h264
├── session.rce.cam01.20260101_120000_000000.ttl_out.npz
├── session.rce.cam01.20260101_120000_000000.ttl_in.npz
├── session.rce.cam02.20260101_120000_000000.video.h264
├── session.rce.cam02.20260101_120000_000000.ttl_out.npz
└── session.rce.cam02.20260101_120000_000000.ttl_in.npz
Load and validate a session from the acquisition machine:
from rpi_camera_ensemble.io import RCESession, validate_session
session = RCESession.from_directory("/data/recordings/experiment_001/")
qc = validate_session(session)
qc.print_report()
Deployment
See deploy/ for Ansible playbooks to provision Raspberry Pis (apt packages, Python venv, systemd service).
Development
uv sync --extra dev
uv run pre-commit install
uv run pytest
License
Copyright (c) 2025 Lars B. Rollik. All rights reserved. See LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rpi_camera_ensemble-0.5.2.tar.gz.
File metadata
- Download URL: rpi_camera_ensemble-0.5.2.tar.gz
- Upload date:
- Size: 93.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a01adb98e11730f8580c68277dee216e665ee254ca9523d80a15418eaa6597a1
|
|
| MD5 |
d8cdb54d46f3dfb9882490edee4810b2
|
|
| BLAKE2b-256 |
da7f70c27ef25c6d67c07d0d1c2cc65b9891e8217d5df6b959fedafa4e563805
|
File details
Details for the file rpi_camera_ensemble-0.5.2-py3-none-any.whl.
File metadata
- Download URL: rpi_camera_ensemble-0.5.2-py3-none-any.whl
- Upload date:
- Size: 70.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9f4824a79f2f33c26ef6f2e1f3efd504ed3baa56d888860ca5aac56c6f8ae63
|
|
| MD5 |
bd530ebe2582e33d55efff77ea2976e7
|
|
| BLAKE2b-256 |
1da1a449163e800d172fca00090c383b59e22bb77a2f04365c2423227bb094e8
|