![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)
RPi Camera Colony (RCC)
Central control for video acquisition with (many) Raspberry Pi cameras
Record videos in parallel with one or more remote-controlled Raspberry Pi (RPi) cameras. :movie_camera:
A single configuration file and a few lines of code allow specific and reproducible acquisition settings for groups of cameras.
Example use with Python:
import time
from rpi_camera_colony.control.conductor import Conductor
conductor = Conductor(settings_file="configuration_file")
conductor.start_acquisition()
time.sleep(20)
conductor.stop_acquisition()
or on the commandline:
rcc_conductor --config-data-file CONFIG_DATA_FILE --acquisition-name ACQUISITION_NAME
Features
A centralised control object
One central object handles all communication with the remote cameras and transmits the configuration settings to each.
A single configuration file to define reproducible multi-camera acquisition
Configuration parameters are centrally defined in an easy-to-read file format and then handed down to the cameras.
Flexible entrypoints
Multiple entrypoints for use in python scripts as well as in a single line on the commandline
Additionally, all levels are directly accessible: central Conductor, remote control handlers, and on the RPi the acquisition control (see below for details).
NEW: Network video stream
Add an additional output via network video stream directly from the main config
or via commandline arguments when calling rcc_acquisition
.
config
example to use with rcc_conductor
as usual:
# ...
[controllers]
[[camera_red_60]]
description = "back view"
address = "192.168.0.22"
# Network stream setup:
stream_video = True
stream_address = "192.168.0.22"
stream_port = 8001
# ...
-
Command-line entrypoint example:
-s, --stream-video
-sip STREAM_IP, --stream-ip STREAM_IP
IP address for video stream. (default: 192.168.100.31)
-sport STREAM_PORT, --stream-port STREAM_PORT
Stream port (default: 8001)
rcc_acquisition --auto-start --stream-video --stream-ip 192.168.100.31 --stream-port 9898
Installation
Python dependencies
On RPi only:
Other useful packages
For video conversion:
- gpac # contains MP4Box tool for video conversion
Example hardware architecture
[outside world / internet]
|
|
[central machine]
|
|
[network switch]
/ | | \
| | | | <- network connection
[rpi #1] | | | e.g. ethernet cables
| | |
[rpi #2] | |
| |
[...] |
|
[rpi #n]
Minimal hardware requirements
- Central machine, can be RPi itself (as it only holds the control object, but does no computation)
- Raspberry Pi
- Main RPi board + fast SD card (+ card reader if not available on another machine)
- RPi Camera (+lens?) (depends on your specific acquisition requirements)
- RPi power supply (RPi4 requires USB-C connector)
- Display cable (RPi4 requires mico-HDMI connector)
- Ethernet cables
- Network switch (if more than one RPi), e.g. any 1GB or faster
Mapping between this package & hardware
One Conductor to instruct all RPi cameras via network communication between the RemoteAcquisitionControl and PiAcquisitionControl.
Hardware <--> Software
[central machine] <--> Conductor
| |
| RemoteAcquisitionControl
| |
... ...
| |
[rpi #n] <--> PiAcquisitionControl
|
Camera
Raspberry Pi setup
-
Set up RPi hardware
- Install Raspbian ->
NOTE: Use Raspbian Buster for now. There is no PiCamera equivalent readily available for the Raspbian Bullseye libcamera apps.
- Enable camera, GPIO interfaces, and ssh in
sudo raspi-config
options - Connect hardware:
-
Camera
-
Network cable
-
GPIO pin connection for TTL in/out (See pinout.xyz for board mode pins to use)
Note: adjust pin numbers used in configuration file. Default are pin #8 for frame TTL outputs and #16 for inputs. Choose any free ground pins!
-
Install this package
- Set up python, e.g. with miniconda
- Clone this repository or use
distribute_code.sh
script (Replace hostnames for your RPi) - Install
a. From Pypi
pip install rpi_camera_colony[rpi] # <- Note: `[rpi]` argument adds specific requirements for acquisition on RPi, but is not required for controller
b. From Github
shell pip install https://github.com/larsrollik/rpi_camera_colony[rpi] # <- Note: `[rpi]` argument adds specific requirements for acquisition on RPi, but is not required for controller
Central control machine setup
-
DHCP server on central computer. (Description only for Ubuntu)
-
Set up static IP address on network interface that serves RPi colony via network switch, with e.g. /etc/network/interfaces
or netplan
-
Set up DHCP server with isc-dhcp-server
-
Set up SSH keys to allow interaction with RPi without password (otherwise cannot drop remote process!)
ssh-keygen # into standard file if not exists, no passphrase
ssh-copy-id -i ~/.ssh/id_rsa HOST # where HOST = RPi host name
-
Set up python environment, e.g. with miniconda
-
Install this package
- Clone this repository
- Install with
pip install rpi_camera_colony
Entrypoints & levels
Easy access to central Conductor
rcc_conductor --help
Use acquisition directly on RPi
from rpi_camera_colony.acquisition.acquisition_control import PiAcquisitionControl
or
python rpi_camera_colony/acquisition --help
# or
python -m rpi_camera_colony.acquisition --help
# or
rcc_acquisition --help
One-to-one mapping of local control to remote acquisition
from rpi_camera_colony.acquisition.remote_control import RemoteAcquisitionControl
or
python rpi_camera_colony/acquisition --help
python -m rpi_camera_colony.acquisition.remote_control --help
Read acquisition metadata & check for video files
from rpi_camera_colony import read_session_data
Sandbox Conductor object in separate process (python multiprocessing)
See rpi_camera_colony.control.process_sandbox
for example use of:
from rpi_camera_colony.control.process_sandbox import ConductorAsProcess
Citation
Rollik, Lars B. (2021). RPi Camera Colony: Central control for video acquisition with (many) Raspberry Pi cameras. doi: 10.5281/zenodo.6414747.
BibTeX
@misc{rollik2021rpi,
author = {Lars B. Rollik},
title = {{RPi Camera Colony: Central control for video acquisition with (many) Raspberry Pi cameras}},
year = {2021},
month = jun,
publisher = {Zenodo},
url = {https://doi.org/10.5281/zenodo.6414747},
doi = {10.5281/zenodo.6414747},
}
License
This software is released under the BSD 3-Clause License
Related projects with similar architectures
-
Arne Meyer's RPiCameraPlugin for the OpenEphys GUI
Specific API for one-to-one control mappings between OpenEphys GUI plugin instances and remote RPi cameras. Inspiration for use of ØMQ communication and camera TTL integration in encoder class.
-
Deshmukh lab's PicameraPaper
Video acquisition with multiple RPi synchronised by a central TTL that is recorded with the camera timestamps.
-
Vidgear
General package for different types of video acquisition and streaming.
Configuration file specification
-> Note: additional picamera attributes can be used, but not all types implemented. Check below.
-> Note: acquisition_group
is not specified by default, but if acquisition_name
contains __
double underscores, then the acquisition_group
will get auto-populated from the first segment, when split on the __
. This is to create an acquisition folder organisation like: /path_to_data/acquisition_group/acquisition_name/[files]
[general]
acquisition_name = string(default="_test_rcc_name_config") # base name for recording
acquisition_time = string(default="dummy_time")
acquisition_group = string(default="")
remote_data_path = string(default="/home/pi/data/") # where to store all recordings on RPi
rpi_username = string(default="pi")
remote_python_interpreter = string(default="/home/pi/miniconda3/envs/py36/bin/python") # path to python
remote_python_entrypoint = string(default="rpi_camera_colony.acquisition") # path to __main__ entrypoint
max_acquisition_time = integer(0, 7200, default=7200) # seconds, shut down acquisition after expiration
save_data = boolean(default=True) # if False, then doesn't write files on RPi
general_setting_has_priority = boolean(default=True) # If False, does not patch in general settings
general_settings_to_patch_into_controller = string_list(default=list("save_data", "acquisition_time", "acquisition_group")) # Add variables here for patching into controllers
[log]
address = string(max=15, default="192.168.100.10")
port = integer(default=55555)
level = string(default="DEBUG")
log_to_console = boolean(default=True)
log_to_file = boolean(default=True)
log_file = string(default="/tmp/rpi_camera_colony__logging")
[control]
address = string(default="192.168.100.10")
port = integer(default=54545)
[controllers]
[[__many__]]
description = string(default="")
address = string(max=15, default="")
save_data = boolean(default=True)
ttl_channel_external = integer(default=-1) # metadata info if recording output TTL on specific channel of other acquisition system
ttl_in_pin = integer(default=16)
ttl_out_pin = integer(default=8)
ttl_out_duration = float(default=.001)
# See for list of ALL parameters https://picamera.readthedocs.io/en/latest/api_camera.html
framerate = integer(min=1, max=90, default=90)
resolution = int_list(default=list(640, 480))
vflip = boolean(default=False)
hflip = boolean(default=False)
brightness = integer(min=0, max=100, default=50)
# color_effects: (128, 128) == black and white acquisition. Default is None.
color_effects = int_list(default=list(128, 128))
contrast = integer(min=-100, max=100, default=0)
image_denoise = boolean(default=True)
iso = integer(min=0, max=1600, default=0)
led = boolean(default=False)
preview_alpha = integer(min=0, max=255, default=255) # DEPRECATED
saturation = integer(min=-100, max=100, default=0)
sharpness = integer(min=-100, max=100, default=0)
still_stats = boolean(default=False)
video_denoise = boolean(default=True)
video_stabilization = boolean(default=False)
# zoom: (x, y, w, h)
zoom = float_list(default=list(0.0, 0.0, 1.0, 1.0))
# Other picamera attributes / not implemented / not tested, but might work
# awb_gains
# awb_mode = option(default="auto")
# drc_strength
# exposure_compensation
# exposure_mode
# exposure_speed = 0
# flash_mode = option("off", "auto", "on", "redeye", "fillin", "torch", default="off")
# framerate_delta # new in 1.11
# framerate_range # new in 1.13
# image_effect = "none"
# image_effect_params # https://picamera.readthedocs.io/en/release-1.13/api_camera.html#picamera.PiCamera.image_effect_params
# meter_mode = option("average", "spot", "backlit", "matrix")
# rotation = option(0, 90, 180, 270)
# sensor_mode = integer(default=0)
# shutter_speed # microseconds
Specific install hints
HQ camera for RPi cannot acquire at resolutions or framerates outlined in the technical description
sudo rpi-update
fixes this. - Be careful, this updates the RPi firmware and might have unexpected side effects!
Ip forwarding and routing on central machine
sysctl -w net.ipv4.ip_forward=1
cat /proc/sys/net/ipv4/ip_forward
iptables -A FORWARD -i enp8s0 -o enp7s0 -j ACCEPT
iptables -A FORWARD -i enp7s0 -o enp8s0 -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -o enp7s0 -j MASQUERADE
Update time for ssl certificates
timedatectl status
sudo service ntp stop
sudo ntpd -gq
sudo service ntp start
sudo systemctl restart systemd-timesyncd
Install miniconda on RPi
wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-armv7l.sh
sudo md5sum Miniconda3-latest-Linux-armv7l.sh
bash Miniconda3-latest-Linux-armv7l.sh
echo 'export PATH="/home/pi/miniconda3/bin:$PATH"' >> .bashrc
source .bashrc
conda config --add channels rpi
conda create -y -n py36 python=3.6 numpy pandas pyzmq
echo 'source activate py36' >> .bashrc
source .bashrc
pip uninstall rpi_camera_colony -y
pip install --upgrade rpi_camera_colony
Version: "0.5.0"