rpi-camera-ensemble 0.4.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. rpi_camera_ensemble-0.4.3/.gitignore +168 -0
  2. rpi_camera_ensemble-0.4.3/CITATION.cff +23 -0
  3. rpi_camera_ensemble-0.4.3/LICENSE +29 -0
  4. rpi_camera_ensemble-0.4.3/PKG-INFO +289 -0
  5. rpi_camera_ensemble-0.4.3/README.md +203 -0
  6. rpi_camera_ensemble-0.4.3/VERSION +1 -0
  7. rpi_camera_ensemble-0.4.3/configs/rce-agent.service +16 -0
  8. rpi_camera_ensemble-0.4.3/examples/README.md +41 -0
  9. rpi_camera_ensemble-0.4.3/examples/conductor_async_demo.py +155 -0
  10. rpi_camera_ensemble-0.4.3/examples/conductor_concurrent_demo.py +195 -0
  11. rpi_camera_ensemble-0.4.3/examples/configs/agent.yaml +7 -0
  12. rpi_camera_ensemble-0.4.3/examples/configs/cameras.yaml +187 -0
  13. rpi_camera_ensemble-0.4.3/examples/configs/conductor.yaml +5 -0
  14. rpi_camera_ensemble-0.4.3/examples/configs/ensemble.session.yaml +54 -0
  15. rpi_camera_ensemble-0.4.3/examples/configs/ensemble.yaml +49 -0
  16. rpi_camera_ensemble-0.4.3/examples/make_acq_process.py +69 -0
  17. rpi_camera_ensemble-0.4.3/examples/rce-openapi.json +1 -0
  18. rpi_camera_ensemble-0.4.3/examples/run_agent.py +19 -0
  19. rpi_camera_ensemble-0.4.3/examples/run_api_client.py +35 -0
  20. rpi_camera_ensemble-0.4.3/examples/run_conductor.py +55 -0
  21. rpi_camera_ensemble-0.4.3/examples/run_conductor_from_conf.py +75 -0
  22. rpi_camera_ensemble-0.4.3/pyproject.toml +195 -0
  23. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/__init__.py +22 -0
  24. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/_version.py +24 -0
  25. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/__init__.py +0 -0
  26. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/acquisition.py +210 -0
  27. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/camera/__init__.py +0 -0
  28. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/camera/base.py +200 -0
  29. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/camera/factory.py +57 -0
  30. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/camera/picamera2_encoder.py +133 -0
  31. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/camera/picamera2_impl.py +346 -0
  32. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/camera/picamera2_streaming.py +99 -0
  33. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/process/__init__.py +0 -0
  34. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/process/manager.py +205 -0
  35. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/process/models.py +21 -0
  36. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/acquisition/process/process.py +202 -0
  37. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/__init__.py +0 -0
  38. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/agent.py +217 -0
  39. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/agent_api_interface.py +101 -0
  40. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/api/__init__.py +0 -0
  41. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/api/factory.py +45 -0
  42. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/api/routers/__init__.py +0 -0
  43. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/api/routers/acquisition.py +118 -0
  44. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/api/routers/agent.py +40 -0
  45. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/api_thread.py +48 -0
  46. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/agent/maintenance_threads.py +69 -0
  47. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/cli/__init__.py +0 -0
  48. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/cli/acquisition/__init__.py +0 -0
  49. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/cli/acquisition/acquisition.py +141 -0
  50. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/cli/agent/__init__.py +0 -0
  51. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/cli/agent/entrypoint.py +203 -0
  52. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/cli/agent/parser.py +113 -0
  53. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/cli/conductor/__init__.py +0 -0
  54. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/cli/conductor/entrypoint.py +121 -0
  55. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/cli/conductor/parser.py +70 -0
  56. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/conductor/__init__.py +0 -0
  57. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/conductor/agent_client.py +129 -0
  58. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/conductor/conductor.py +488 -0
  59. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/conductor/models.py +25 -0
  60. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/config/__init__.py +0 -0
  61. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/config/_mixin.py +108 -0
  62. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/config/acquisition.py +222 -0
  63. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/config/agent.py +33 -0
  64. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/config/api_models.py +39 -0
  65. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/config/camera/__init__.py +0 -0
  66. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/config/camera/camera.py +81 -0
  67. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/config/camera/picamera2_impl.py +141 -0
  68. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/config/conductor.py +42 -0
  69. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/io/__init__.py +7 -0
  70. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/io/models.py +96 -0
  71. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/io/session.py +210 -0
  72. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/io/validate.py +334 -0
  73. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/py.typed +0 -0
  74. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/utils/__init__.py +44 -0
  75. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/utils/log.py +74 -0
  76. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/utils/ttl/__init__.py +0 -0
  77. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/utils/ttl/emitter.py +138 -0
  78. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/utils/ttl/mixin.py +111 -0
  79. rpi_camera_ensemble-0.4.3/src/rpi_camera_ensemble/utils/ttl/receiver.py +177 -0
  80. rpi_camera_ensemble-0.4.3/tests/fixtures/rce_session/t000_test__20260101_120000_000000__fixedsubjects.rce.conductor.20260101120000.cfg.yaml +11 -0
  81. rpi_camera_ensemble-0.4.3/tests/fixtures/rce_session/t000_test__20260101_120000_000000__fixedsubjects.rce.conductor.20260101120000.ensemble.yaml +54 -0
  82. rpi_camera_ensemble-0.4.3/tests/fixtures/rce_session/t000_test__20260101_120000_000000__fixedsubjects.rce.rpi-131.20260101120000.ttl_in.npz +0 -0
  83. rpi_camera_ensemble-0.4.3/tests/fixtures/rce_session/t000_test__20260101_120000_000000__fixedsubjects.rce.rpi-131.20260101120000.ttl_out.npz +0 -0
  84. rpi_camera_ensemble-0.4.3/tests/fixtures/rce_session/t000_test__20260101_120000_000000__fixedsubjects.rce.rpi-131.20260101120000.video.h264 +0 -0
  85. rpi_camera_ensemble-0.4.3/tests/fixtures/rce_session/t000_test__20260101_120000_000000__fixedsubjects.rce.rpi-132.20260101120000.ttl_in.npz +0 -0
  86. rpi_camera_ensemble-0.4.3/tests/fixtures/rce_session/t000_test__20260101_120000_000000__fixedsubjects.rce.rpi-132.20260101120000.ttl_out.npz +0 -0
  87. rpi_camera_ensemble-0.4.3/tests/fixtures/rce_session/t000_test__20260101_120000_000000__fixedsubjects.rce.rpi-132.20260101120000.video.h264 +0 -0
  88. rpi_camera_ensemble-0.4.3/tests/tests/test_integration/test_placeholder.py +1 -0
  89. rpi_camera_ensemble-0.4.3/tests/tests/test_unit/test_unit.py +273 -0
@@ -0,0 +1,168 @@
1
+ # IDE files
2
+ .idea/
3
+ .vscode/
4
+
5
+ # Ignored file types
6
+ .DS_Store
7
+ *.yaml
8
+ *.json
9
+
10
+ # Allow test fixtures regardless of extension
11
+ !tests/fixtures/
12
+ !tests/fixtures/**
13
+ !tests/fixtures/**/*.yaml
14
+ !tests/fixtures/**/*.json
15
+ !tests/fixtures/**/*.npz
16
+ !tests/fixtures/**/*.h264
17
+ !tests/fixtures/**/*.csv
18
+
19
+ # Allow deploy directory
20
+ !deploy/
21
+ !deploy/**
22
+ !deploy/**/*.yaml
23
+ !deploy/**/*.ini
24
+ !deploy/**/*.cfg
25
+ !deploy/**/*.j2
26
+
27
+ # Allow examples directory
28
+ !examples/
29
+ !examples/**
30
+ !examples/**/*.yaml
31
+ !examples/**/*.json
32
+ !examples/**/*.py
33
+
34
+ # Custom config files
35
+ *.conf.custom
36
+
37
+ # Byte-compiled / optimized / DLL files
38
+ __pycache__/
39
+ *.py[cod]
40
+ *$py.class
41
+
42
+ # C extensions
43
+ *.so
44
+
45
+ # Distribution / packaging
46
+ .Python
47
+ build/
48
+ develop-eggs/
49
+ dist/
50
+ downloads/
51
+ eggs/
52
+ .eggs/
53
+ lib/
54
+ lib64/
55
+ parts/
56
+ sdist/
57
+ var/
58
+ wheels/
59
+ pip-wheel-metadata/
60
+ share/python-wheels/
61
+ *.egg-info/
62
+ .installed.cfg
63
+ *.egg
64
+ MANIFEST
65
+
66
+ # PyInstaller
67
+ # Usually these files are written by a python script from a template
68
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
69
+ *.manifest
70
+ *.spec
71
+
72
+ # Installer logs
73
+ pip-log.txt
74
+ pip-delete-this-directory.txt
75
+
76
+ # Unit test / coverage reports
77
+ htmlcov/
78
+ .tox/
79
+ .nox/
80
+ .coverage
81
+ .coverage.*
82
+ .cache
83
+ nosetests.xml
84
+ coverage.xml
85
+ *.cover
86
+ *.py,cover
87
+ .hypothesis/
88
+ .pytest_cache/
89
+
90
+ # Translations
91
+ *.mo
92
+ *.pot
93
+
94
+ # Django stuff:
95
+ *.log
96
+ local_settings.py
97
+ db.sqlite3
98
+ db.sqlite3-journal
99
+
100
+ # Flask stuff:
101
+ instance/
102
+ .webassets-cache
103
+
104
+ # Scrapy stuff:
105
+ .scrapy
106
+
107
+ # Sphinx documentation
108
+ docs/_build/
109
+
110
+ # PyBuilder
111
+ target/
112
+
113
+ # Jupyter Notebook
114
+ .ipynb_checkpoints
115
+
116
+ # IPython
117
+ profile_default/
118
+ ipython_config.py
119
+
120
+ # pyenv
121
+ .python-version
122
+
123
+ # pipenv
124
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
125
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
126
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
127
+ # install all needed dependencies.
128
+ #Pipfile.lock
129
+
130
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
131
+ __pypackages__/
132
+
133
+ # Celery stuff
134
+ celerybeat-schedule
135
+ celerybeat.pid
136
+
137
+ # SageMath parsed files
138
+ *.sage.py
139
+
140
+ # Environments
141
+ .env
142
+ .venv
143
+ env/
144
+ venv/
145
+ ENV/
146
+ env.bak/
147
+ venv.bak/
148
+
149
+ # Spyder project settings
150
+ .spyderproject
151
+ .spyproject
152
+
153
+ # Rope project settings
154
+ .ropeproject
155
+
156
+ # mkdocs documentation
157
+ /site
158
+
159
+ # mypy
160
+ .mypy_cache/
161
+ .dmypy.json
162
+ dmypy.json
163
+
164
+ # Pyre type checker
165
+ .pyre/
166
+
167
+ # hatch-vcs generated version file — do not track
168
+ src/*/_version.py
@@ -0,0 +1,23 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ type: software
4
+ title: "rpi-camera-ensemble: Central orchestration of video acquisition across multiple Raspberry Pi cameras"
5
+ version: "0.4.1"
6
+ date-released: "2026-05-30"
7
+ repository-code: https://github.com/MurineShiftWork/rpi-camera-ensemble
8
+ url: https://MurineShiftWork.github.io/rpi-camera-ensemble/
9
+ license: BSD-3-Clause
10
+ authors:
11
+ - family-names: Rollik
12
+ given-names: Lars B.
13
+ email: L.B.Rollik@protonmail.com
14
+ orcid: https://orcid.org/0000-0003-0160-6971
15
+
16
+ # Zenodo integration (optional):
17
+ # 1. Enable the Zenodo webhook at zenodo.org/account/settings/github
18
+ # 2. After first tag release, copy the concept DOI from Zenodo and add:
19
+ #
20
+ # identifiers:
21
+ # - type: doi
22
+ # value: 10.5281/zenodo.XXXXXXX
23
+ # description: Zenodo concept DOI
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Lars B. Rollik
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.4
2
+ Name: rpi-camera-ensemble
3
+ Version: 0.4.3
4
+ Summary: RPi Camera Ensemble: Central orchestration of video acquisition across multiple Raspberry Pi cameras.
5
+ Project-URL: Homepage, https://github.com/MurineShiftWork/rpi-camera-ensemble
6
+ Project-URL: Documentation, https://MurineShiftWork.github.io/rpi-camera-ensemble/
7
+ Project-URL: Issue Tracker, https://github.com/MurineShiftWork/rpi-camera-ensemble/issues
8
+ Author-email: "Lars B. Rollik" <L.B.Rollik@protonmail.com>
9
+ License: BSD 3-Clause License
10
+
11
+ Copyright (c) 2025, Lars B. Rollik
12
+ All rights reserved.
13
+
14
+ Redistribution and use in source and binary forms, with or without
15
+ modification, are permitted provided that the following conditions are met:
16
+
17
+ 1. Redistributions of source code must retain the above copyright notice, this
18
+ list of conditions and the following disclaimer.
19
+
20
+ 2. Redistributions in binary form must reproduce the above copyright notice,
21
+ this list of conditions and the following disclaimer in the documentation
22
+ and/or other materials provided with the distribution.
23
+
24
+ 3. Neither the name of the copyright holder nor the names of its
25
+ contributors may be used to endorse or promote products derived from
26
+ this software without specific prior written permission.
27
+
28
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
29
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
32
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
34
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
36
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
+ License-File: LICENSE
39
+ Classifier: Development Status :: 4 - Beta
40
+ Classifier: License :: OSI Approved :: BSD License
41
+ Classifier: Operating System :: POSIX :: Linux
42
+ Classifier: Programming Language :: Python :: 3
43
+ Classifier: Programming Language :: Python :: 3.10
44
+ Classifier: Programming Language :: Python :: 3.11
45
+ Classifier: Programming Language :: Python :: 3.12
46
+ Classifier: Topic :: Multimedia :: Video
47
+ Classifier: Topic :: Scientific/Engineering
48
+ Requires-Python: >=3.10
49
+ Requires-Dist: numpy
50
+ Requires-Dist: pydantic>=2.0
51
+ Requires-Dist: pyyaml
52
+ Requires-Dist: typing-extensions; python_version < '3.11'
53
+ Provides-Extra: agent
54
+ Requires-Dist: fastapi; extra == 'agent'
55
+ Requires-Dist: picamera2; extra == 'agent'
56
+ Requires-Dist: uvicorn[standard]; extra == 'agent'
57
+ Provides-Extra: all
58
+ Requires-Dist: aiohttp; extra == 'all'
59
+ Requires-Dist: fastapi; extra == 'all'
60
+ Requires-Dist: lgpio; extra == 'all'
61
+ Requires-Dist: picamera2; extra == 'all'
62
+ Requires-Dist: uvicorn[standard]; extra == 'all'
63
+ Provides-Extra: conductor
64
+ Requires-Dist: aiohttp; extra == 'conductor'
65
+ Provides-Extra: dev
66
+ Requires-Dist: commitizen; extra == 'dev'
67
+ Requires-Dist: mypy; extra == 'dev'
68
+ Requires-Dist: pre-commit; extra == 'dev'
69
+ Requires-Dist: pytest-cov; extra == 'dev'
70
+ Requires-Dist: pytest>=8; extra == 'dev'
71
+ Requires-Dist: types-pyyaml; extra == 'dev'
72
+ Provides-Extra: docs
73
+ Requires-Dist: mkdocs-material; extra == 'docs'
74
+ Provides-Extra: lgpio
75
+ Requires-Dist: lgpio; extra == 'lgpio'
76
+ Provides-Extra: pigpio
77
+ Requires-Dist: pigpio; extra == 'pigpio'
78
+ Provides-Extra: rpi
79
+ Requires-Dist: fastapi; extra == 'rpi'
80
+ Requires-Dist: lgpio; extra == 'rpi'
81
+ Requires-Dist: picamera2; extra == 'rpi'
82
+ Requires-Dist: uvicorn[standard]; extra == 'rpi'
83
+ Provides-Extra: rpigpio
84
+ Requires-Dist: rpi-gpio; extra == 'rpigpio'
85
+ Description-Content-Type: text/markdown
86
+
87
+ [//]: # (Badges)
88
+
89
+ [![CI](https://img.shields.io/github/actions/workflow/status/MurineShiftWork/rpi-camera-ensemble/ci.yml?branch=main&label=CI)](https://github.com/MurineShiftWork/rpi-camera-ensemble/actions)
90
+ [![Docs](https://img.shields.io/github/actions/workflow/status/MurineShiftWork/rpi-camera-ensemble/docs.yml?branch=main&label=docs)](https://MurineShiftWork.github.io/rpi-camera-ensemble/)
91
+ [![PyPI](https://img.shields.io/pypi/v/rpi-camera-ensemble)](https://pypi.org/project/rpi-camera-ensemble/)
92
+ [![License](https://img.shields.io/github/license/MurineShiftWork/rpi-camera-ensemble)](LICENSE)
93
+
94
+ # RPi Camera Ensemble
95
+
96
+ Central orchestration of synchronized video acquisition across multiple Raspberry Pi cameras.
97
+
98
+ 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.
99
+
100
+ ---
101
+
102
+ ## Features
103
+
104
+ - **Multi-camera orchestration**: start/stop all cameras with a single call; UUID-guarded sessions prevent accidental stops
105
+ - **TTL synchronization**: per-frame GPIO pulses (emitter) and edge-detection timestamps (receiver) via `lgpio` (RPi 4 + RPi 5) or legacy `pigpio`
106
+ - **PiCamera2 backend**: uses the modern `libcamera` stack; preview and recording states
107
+ - **REST API on each agent**: `GET /status`, `POST /agent/acquisition/init`, `POST /agent/acquisition/record`, `POST /agent/acquisition/stop`
108
+ - **Heartbeat monitoring**: background health checks with configurable interval
109
+ - **Disk space check**: pre-acquisition guard based on estimated bitrate and free space
110
+ - **CLI**: `rce-agent run/config` entrypoints for systemd or docker-compose deployment
111
+
112
+ ---
113
+
114
+ ## Architecture
115
+
116
+ ```
117
+ Conductor (acquisition machine)
118
+ │ HTTP via aiohttp
119
+ ├── AgentClient → Agent (RPi cam01) → Acquisition → PiCamera2
120
+ ├── AgentClient → Agent (RPi cam02) → Acquisition → PiCamera2
121
+ └── AgentClient → Agent (RPi cam03) → Acquisition → PiCamera2
122
+
123
+ TTL out pin (lgpio)
124
+ TTL in pin (lgpio)
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Requirements
130
+
131
+ - Python ≥ 3.10
132
+ - **On each Raspberry Pi**: `picamera2` (system package), `lgpio` (`pip install lgpio`), `libgpiod2` (system package)
133
+ - **On the acquisition machine (Conductor)**: `aiohttp`
134
+
135
+ ---
136
+
137
+ ## Installation
138
+
139
+ ```bash
140
+ # Acquisition machine (Conductor only)
141
+ pip install "rpi-camera-ensemble[conductor]"
142
+
143
+ # On each Raspberry Pi (Agent + TTL sync)
144
+ pip install "rpi-camera-ensemble[agent,lgpio]"
145
+ ```
146
+
147
+ Install from source:
148
+
149
+ ```bash
150
+ git clone https://github.com/MurineShiftWork/rpi-camera-ensemble.git
151
+ cd rpi-camera-ensemble
152
+ pip install -e ".[agent,lgpio,dev]"
153
+ ```
154
+
155
+ ---
156
+
157
+ ## Quick start
158
+
159
+ ### Agent (on each Raspberry Pi)
160
+
161
+ Create a config file (`agent.yaml`):
162
+
163
+ ```yaml
164
+ instance_name: cam01
165
+ api_host: 0.0.0.0
166
+ api_port: 8000
167
+ data_dir: /data/recordings
168
+ log_dir: /data/logs
169
+ ```
170
+
171
+ Start the agent:
172
+
173
+ ```bash
174
+ rce-agent run --config agent.yaml
175
+ ```
176
+
177
+ Generate a template config:
178
+
179
+ ```bash
180
+ rce-agent config --to-template agent.template.yaml
181
+ ```
182
+
183
+ ### Conductor (on the acquisition machine)
184
+
185
+ ```python
186
+ from rpi_camera_ensemble.conductor.conductor import Conductor
187
+ from rpi_camera_ensemble.config.acquisition import EnsembleAcquisitionConfig
188
+ from rpi_camera_ensemble.config.conductor import ConductorConfig
189
+
190
+ ensemble_cfg = EnsembleAcquisitionConfig.from_yaml("ensemble.yaml")
191
+ conductor_cfg = ConductorConfig(log_dir="/data/logs/conductor")
192
+
193
+ with Conductor(config=conductor_cfg, ensemble_config=ensemble_cfg) as conductor:
194
+ conductor.setup_agents()
195
+ conductor.initialize_acquisition(
196
+ acquisition_path="/data/recordings/experiment_001",
197
+ acquisition_name="experiment_001",
198
+ )
199
+ conductor.start_preview()
200
+ conductor.start_recording()
201
+ # ... recording ...
202
+ conductor.stop_acquisition()
203
+ ```
204
+
205
+ Ensemble config (`ensemble.yaml`):
206
+
207
+ ```yaml
208
+ agents:
209
+ cam01:
210
+ api_url: http://192.168.1.10:8000
211
+ cam02:
212
+ api_url: http://192.168.1.11:8000
213
+ ```
214
+
215
+ ---
216
+
217
+ ## TTL synchronization
218
+
219
+ TTL is configured per agent inside `ensemble.yaml` under `camera_config.ttl`. Default backend is `lgpio` (recommended):
220
+
221
+ ```yaml
222
+ agents:
223
+ cam01:
224
+ api_url: http://192.168.1.10:8000
225
+ camera_config:
226
+ ttl:
227
+ library: lgpio
228
+ gpiochip: 0 # RPi 4: 0, RPi 5: 4
229
+ out_pin: 25 # emits 1 ms pulse on each encoded frame
230
+ in_pin: 23 # records edge timestamps from external trigger
231
+ out_duration_us: 1000
232
+ ```
233
+
234
+ `lgpio` requires `libgpiod2` on the Pi:
235
+
236
+ ```bash
237
+ sudo apt install libgpiod2
238
+ pip install lgpio
239
+ ```
240
+
241
+ > **RPi 5 note**: set `gpiochip: 4` (RP1 southbridge). RPi 4 uses `gpiochip: 0`.
242
+
243
+ ---
244
+
245
+ ## Output structure
246
+
247
+ Each agent writes files named `{basename}.rce.{instance}.{timestamp}.{type}.{ext}`:
248
+
249
+ ```
250
+ /data/recordings/experiment_001/
251
+ ├── session.rce.cam01.20260101_120000_000000.video.h264
252
+ ├── session.rce.cam01.20260101_120000_000000.ttl_out.npz
253
+ ├── session.rce.cam01.20260101_120000_000000.ttl_in.npz
254
+ ├── session.rce.cam02.20260101_120000_000000.video.h264
255
+ ├── session.rce.cam02.20260101_120000_000000.ttl_out.npz
256
+ └── session.rce.cam02.20260101_120000_000000.ttl_in.npz
257
+ ```
258
+
259
+ Load and validate a session from the acquisition machine:
260
+
261
+ ```python
262
+ from rpi_camera_ensemble.io import RCESession, validate_session
263
+
264
+ session = RCESession.from_directory("/data/recordings/experiment_001/")
265
+ qc = validate_session(session)
266
+ qc.print_report()
267
+ ```
268
+
269
+ ---
270
+
271
+ ## Deployment
272
+
273
+ See [`deploy/`](https://github.com/MurineShiftWork/rpi-camera-ensemble/tree/main/deploy) for Ansible playbooks to provision Raspberry Pis (apt packages, Python venv, systemd service).
274
+
275
+ ---
276
+
277
+ ## Development
278
+
279
+ ```bash
280
+ uv sync --extra dev
281
+ uv run pre-commit install
282
+ uv run pytest
283
+ ```
284
+
285
+ ---
286
+
287
+ ## License
288
+
289
+ Copyright (c) 2025 Lars B. Rollik. All rights reserved. See [LICENSE](LICENSE).
@@ -0,0 +1,203 @@
1
+ [//]: # (Badges)
2
+
3
+ [![CI](https://img.shields.io/github/actions/workflow/status/MurineShiftWork/rpi-camera-ensemble/ci.yml?branch=main&label=CI)](https://github.com/MurineShiftWork/rpi-camera-ensemble/actions)
4
+ [![Docs](https://img.shields.io/github/actions/workflow/status/MurineShiftWork/rpi-camera-ensemble/docs.yml?branch=main&label=docs)](https://MurineShiftWork.github.io/rpi-camera-ensemble/)
5
+ [![PyPI](https://img.shields.io/pypi/v/rpi-camera-ensemble)](https://pypi.org/project/rpi-camera-ensemble/)
6
+ [![License](https://img.shields.io/github/license/MurineShiftWork/rpi-camera-ensemble)](LICENSE)
7
+
8
+ # RPi Camera Ensemble
9
+
10
+ Central orchestration of synchronized video acquisition across multiple Raspberry Pi cameras.
11
+
12
+ 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.
13
+
14
+ ---
15
+
16
+ ## Features
17
+
18
+ - **Multi-camera orchestration**: start/stop all cameras with a single call; UUID-guarded sessions prevent accidental stops
19
+ - **TTL synchronization**: per-frame GPIO pulses (emitter) and edge-detection timestamps (receiver) via `lgpio` (RPi 4 + RPi 5) or legacy `pigpio`
20
+ - **PiCamera2 backend**: uses the modern `libcamera` stack; preview and recording states
21
+ - **REST API on each agent**: `GET /status`, `POST /agent/acquisition/init`, `POST /agent/acquisition/record`, `POST /agent/acquisition/stop`
22
+ - **Heartbeat monitoring**: background health checks with configurable interval
23
+ - **Disk space check**: pre-acquisition guard based on estimated bitrate and free space
24
+ - **CLI**: `rce-agent run/config` entrypoints for systemd or docker-compose deployment
25
+
26
+ ---
27
+
28
+ ## Architecture
29
+
30
+ ```
31
+ Conductor (acquisition machine)
32
+ │ HTTP via aiohttp
33
+ ├── AgentClient → Agent (RPi cam01) → Acquisition → PiCamera2
34
+ ├── AgentClient → Agent (RPi cam02) → Acquisition → PiCamera2
35
+ └── AgentClient → Agent (RPi cam03) → Acquisition → PiCamera2
36
+
37
+ TTL out pin (lgpio)
38
+ TTL in pin (lgpio)
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Requirements
44
+
45
+ - Python ≥ 3.10
46
+ - **On each Raspberry Pi**: `picamera2` (system package), `lgpio` (`pip install lgpio`), `libgpiod2` (system package)
47
+ - **On the acquisition machine (Conductor)**: `aiohttp`
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ # Acquisition machine (Conductor only)
55
+ pip install "rpi-camera-ensemble[conductor]"
56
+
57
+ # On each Raspberry Pi (Agent + TTL sync)
58
+ pip install "rpi-camera-ensemble[agent,lgpio]"
59
+ ```
60
+
61
+ Install from source:
62
+
63
+ ```bash
64
+ git clone https://github.com/MurineShiftWork/rpi-camera-ensemble.git
65
+ cd rpi-camera-ensemble
66
+ pip install -e ".[agent,lgpio,dev]"
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Quick start
72
+
73
+ ### Agent (on each Raspberry Pi)
74
+
75
+ Create a config file (`agent.yaml`):
76
+
77
+ ```yaml
78
+ instance_name: cam01
79
+ api_host: 0.0.0.0
80
+ api_port: 8000
81
+ data_dir: /data/recordings
82
+ log_dir: /data/logs
83
+ ```
84
+
85
+ Start the agent:
86
+
87
+ ```bash
88
+ rce-agent run --config agent.yaml
89
+ ```
90
+
91
+ Generate a template config:
92
+
93
+ ```bash
94
+ rce-agent config --to-template agent.template.yaml
95
+ ```
96
+
97
+ ### Conductor (on the acquisition machine)
98
+
99
+ ```python
100
+ from rpi_camera_ensemble.conductor.conductor import Conductor
101
+ from rpi_camera_ensemble.config.acquisition import EnsembleAcquisitionConfig
102
+ from rpi_camera_ensemble.config.conductor import ConductorConfig
103
+
104
+ ensemble_cfg = EnsembleAcquisitionConfig.from_yaml("ensemble.yaml")
105
+ conductor_cfg = ConductorConfig(log_dir="/data/logs/conductor")
106
+
107
+ with Conductor(config=conductor_cfg, ensemble_config=ensemble_cfg) as conductor:
108
+ conductor.setup_agents()
109
+ conductor.initialize_acquisition(
110
+ acquisition_path="/data/recordings/experiment_001",
111
+ acquisition_name="experiment_001",
112
+ )
113
+ conductor.start_preview()
114
+ conductor.start_recording()
115
+ # ... recording ...
116
+ conductor.stop_acquisition()
117
+ ```
118
+
119
+ Ensemble config (`ensemble.yaml`):
120
+
121
+ ```yaml
122
+ agents:
123
+ cam01:
124
+ api_url: http://192.168.1.10:8000
125
+ cam02:
126
+ api_url: http://192.168.1.11:8000
127
+ ```
128
+
129
+ ---
130
+
131
+ ## TTL synchronization
132
+
133
+ TTL is configured per agent inside `ensemble.yaml` under `camera_config.ttl`. Default backend is `lgpio` (recommended):
134
+
135
+ ```yaml
136
+ agents:
137
+ cam01:
138
+ api_url: http://192.168.1.10:8000
139
+ camera_config:
140
+ ttl:
141
+ library: lgpio
142
+ gpiochip: 0 # RPi 4: 0, RPi 5: 4
143
+ out_pin: 25 # emits 1 ms pulse on each encoded frame
144
+ in_pin: 23 # records edge timestamps from external trigger
145
+ out_duration_us: 1000
146
+ ```
147
+
148
+ `lgpio` requires `libgpiod2` on the Pi:
149
+
150
+ ```bash
151
+ sudo apt install libgpiod2
152
+ pip install lgpio
153
+ ```
154
+
155
+ > **RPi 5 note**: set `gpiochip: 4` (RP1 southbridge). RPi 4 uses `gpiochip: 0`.
156
+
157
+ ---
158
+
159
+ ## Output structure
160
+
161
+ Each agent writes files named `{basename}.rce.{instance}.{timestamp}.{type}.{ext}`:
162
+
163
+ ```
164
+ /data/recordings/experiment_001/
165
+ ├── session.rce.cam01.20260101_120000_000000.video.h264
166
+ ├── session.rce.cam01.20260101_120000_000000.ttl_out.npz
167
+ ├── session.rce.cam01.20260101_120000_000000.ttl_in.npz
168
+ ├── session.rce.cam02.20260101_120000_000000.video.h264
169
+ ├── session.rce.cam02.20260101_120000_000000.ttl_out.npz
170
+ └── session.rce.cam02.20260101_120000_000000.ttl_in.npz
171
+ ```
172
+
173
+ Load and validate a session from the acquisition machine:
174
+
175
+ ```python
176
+ from rpi_camera_ensemble.io import RCESession, validate_session
177
+
178
+ session = RCESession.from_directory("/data/recordings/experiment_001/")
179
+ qc = validate_session(session)
180
+ qc.print_report()
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Deployment
186
+
187
+ See [`deploy/`](https://github.com/MurineShiftWork/rpi-camera-ensemble/tree/main/deploy) for Ansible playbooks to provision Raspberry Pis (apt packages, Python venv, systemd service).
188
+
189
+ ---
190
+
191
+ ## Development
192
+
193
+ ```bash
194
+ uv sync --extra dev
195
+ uv run pre-commit install
196
+ uv run pytest
197
+ ```
198
+
199
+ ---
200
+
201
+ ## License
202
+
203
+ Copyright (c) 2025 Lars B. Rollik. All rights reserved. See [LICENSE](LICENSE).