Skip to main content

RPi Camera Ensemble: Central orchestration of video acquisition across multiple Raspberry Pi cameras.

Project description

PyPI

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 legacy pigpio
  • PiCamera2 backend: uses the modern libcamera stack; 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/config entrypoints 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 uses gpiochip: 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rpi_camera_ensemble-0.6.1.tar.gz (94.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rpi_camera_ensemble-0.6.1-py3-none-any.whl (71.4 kB view details)

Uploaded Python 3

File details

Details for the file rpi_camera_ensemble-0.6.1.tar.gz.

File metadata

  • Download URL: rpi_camera_ensemble-0.6.1.tar.gz
  • Upload date:
  • Size: 94.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","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

Hashes for rpi_camera_ensemble-0.6.1.tar.gz
Algorithm Hash digest
SHA256 0873f533a31351c931a9939104adc56aa18052f6c454d9c055a64a0a038503ca
MD5 af990f278fe72783dc83f25e8e40e807
BLAKE2b-256 082836dafd1793f21f5ea763218dfb6bc68e3975df99fde2f91e671d11cae344

See more details on using hashes here.

File details

Details for the file rpi_camera_ensemble-0.6.1-py3-none-any.whl.

File metadata

  • Download URL: rpi_camera_ensemble-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 71.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","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

Hashes for rpi_camera_ensemble-0.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c5f461a9461088dd1fdcf08cda14a41a3f5137070929acc2cee0286cc7abeaea
MD5 c3a8d23feb4e88c1bced5c0e7048c3de
BLAKE2b-256 d06ded87e9bd2fdb03e78c8bc4ce6cb16d7de60a3223f3f4f3823f607dc8003b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page