msw-flir-bonsai 0.2.2__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.
- msw_flir_bonsai-0.2.2/.gitignore +72 -0
- msw_flir_bonsai-0.2.2/CITATION.cff +12 -0
- msw_flir_bonsai-0.2.2/LICENSE +31 -0
- msw_flir_bonsai-0.2.2/PKG-INFO +277 -0
- msw_flir_bonsai-0.2.2/README.md +222 -0
- msw_flir_bonsai-0.2.2/VERSION +1 -0
- msw_flir_bonsai-0.2.2/pyproject.toml +134 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/__init__.py +8 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/_version.py +24 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/alignment.py +211 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/bonsai_workflows/__init__.py +12 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/bonsai_workflows/run-flir-flycap-1cam.bonsai +276 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/bonsai_workflows/run-flir-flycap-2cam.bonsai +470 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/bonsai_workflows/run-flir-spinnaker-1cam.bonsai +270 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/bonsai_workflows/run-flir-spinnaker-2cam.bonsai +453 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/cli.py +313 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/py.typed +0 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/runner.py +269 -0
- msw_flir_bonsai-0.2.2/src/msw_flir_bonsai/timestamps.py +148 -0
- msw_flir_bonsai-0.2.2/tests/__init__.py +0 -0
- msw_flir_bonsai-0.2.2/tests/conftest.py +1 -0
- msw_flir_bonsai-0.2.2/tests/integration/__init__.py +0 -0
- msw_flir_bonsai-0.2.2/tests/integration/test_placeholder.py +6 -0
- msw_flir_bonsai-0.2.2/tests/integration/test_runner_live.py +163 -0
- msw_flir_bonsai-0.2.2/tests/test_timestamps.py +100 -0
- msw_flir_bonsai-0.2.2/tests/unit/__init__.py +0 -0
- msw_flir_bonsai-0.2.2/tests/unit/test_alignment.py +156 -0
- msw_flir_bonsai-0.2.2/tests/unit/test_cli.py +80 -0
- msw_flir_bonsai-0.2.2/tests/unit/test_placeholder.py +8 -0
- msw_flir_bonsai-0.2.2/tests/unit/test_runner.py +343 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.so
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
eggs/
|
|
12
|
+
parts/
|
|
13
|
+
var/
|
|
14
|
+
sdist/
|
|
15
|
+
develop-eggs/
|
|
16
|
+
.installed.cfg
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
wheels/
|
|
20
|
+
|
|
21
|
+
# Virtual environments
|
|
22
|
+
.venv/
|
|
23
|
+
venv/
|
|
24
|
+
env/
|
|
25
|
+
ENV/
|
|
26
|
+
.python-version
|
|
27
|
+
|
|
28
|
+
# uv
|
|
29
|
+
uv.lock
|
|
30
|
+
|
|
31
|
+
# Distribution / packaging
|
|
32
|
+
MANIFEST
|
|
33
|
+
|
|
34
|
+
# Testing
|
|
35
|
+
.pytest_cache/
|
|
36
|
+
.coverage
|
|
37
|
+
.coverage.*
|
|
38
|
+
coverage.xml
|
|
39
|
+
htmlcov/
|
|
40
|
+
*.cover
|
|
41
|
+
.hypothesis/
|
|
42
|
+
|
|
43
|
+
# Type checking
|
|
44
|
+
.mypy_cache/
|
|
45
|
+
.dmypy.json
|
|
46
|
+
dmypy.json
|
|
47
|
+
.pytype/
|
|
48
|
+
.pyre/
|
|
49
|
+
|
|
50
|
+
# Build artifacts (hatch-vcs generated)
|
|
51
|
+
src/*/_version.py
|
|
52
|
+
|
|
53
|
+
# Jupyter
|
|
54
|
+
.ipynb_checkpoints
|
|
55
|
+
*.ipynb
|
|
56
|
+
|
|
57
|
+
# IDEs
|
|
58
|
+
.idea/
|
|
59
|
+
.vscode/
|
|
60
|
+
*.swp
|
|
61
|
+
*.swo
|
|
62
|
+
*~
|
|
63
|
+
|
|
64
|
+
# OS
|
|
65
|
+
.DS_Store
|
|
66
|
+
Thumbs.db
|
|
67
|
+
|
|
68
|
+
# Secrets / local config
|
|
69
|
+
.env
|
|
70
|
+
.env.*
|
|
71
|
+
!.env.example
|
|
72
|
+
site/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
type: software
|
|
4
|
+
title: "msw-flir-bonsai: FLIR camera control via Bonsai for murine shift work"
|
|
5
|
+
version: "0.1.0"
|
|
6
|
+
repository-code: https://github.com/murineshiftwork/msw-flir-bonsai
|
|
7
|
+
url: https://github.com/murineshiftwork/msw-flir-bonsai
|
|
8
|
+
license: BSD-3-Clause
|
|
9
|
+
authors:
|
|
10
|
+
- family-names: Rollik
|
|
11
|
+
given-names: Lars B.
|
|
12
|
+
orcid: https://orcid.org/0000-0003-0160-6971
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Copyright (c) 2021-present Lars B. Rollik. All rights reserved.
|
|
2
|
+
|
|
3
|
+
Permission is granted, free of charge, to use, copy, modify, and distribute
|
|
4
|
+
this software and associated documentation files (the "Software") for
|
|
5
|
+
non-commercial research or academic purposes only, subject to the following
|
|
6
|
+
conditions:
|
|
7
|
+
|
|
8
|
+
1. This copyright notice and permission notice must be included in all copies
|
|
9
|
+
or substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
2. Any publication, presentation, or product that uses or builds upon the
|
|
12
|
+
Software must give clear attribution to the original work and its authors.
|
|
13
|
+
|
|
14
|
+
3. Commercial use is prohibited without a separate written commercial licence
|
|
15
|
+
agreement with the copyright holder. Commercial use means incorporation of
|
|
16
|
+
the Software into anything for which fees or other compensation are charged
|
|
17
|
+
or received, including commercial products and commercial services.
|
|
18
|
+
|
|
19
|
+
4. No patent licence, express or implied, is granted under these terms. Any
|
|
20
|
+
use that would require a patent licence from the copyright holder requires
|
|
21
|
+
a separate written agreement.
|
|
22
|
+
|
|
23
|
+
5. Redistribution, in source or binary form, is permitted only for
|
|
24
|
+
non-commercial purposes and must retain this notice unmodified.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,
|
|
28
|
+
DAMAGES, OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
|
|
29
|
+
SOFTWARE OR THE USE OR DEALINGS IN THE SOFTWARE.
|
|
30
|
+
|
|
31
|
+
For commercial or patent licensing enquiries contact: lars@rollik.me
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: msw-flir-bonsai
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: FLIR camera control via Bonsai for murine shift work experiments
|
|
5
|
+
Project-URL: Homepage, https://github.com/MurineShiftWork/msw-flir-bonsai
|
|
6
|
+
Project-URL: Documentation, https://MurineShiftWork.github.io/msw-flir-bonsai/
|
|
7
|
+
Project-URL: Issue Tracker, https://github.com/MurineShiftWork/msw-flir-bonsai/issues
|
|
8
|
+
Author-email: "Lars B. Rollik" <lars@rollik.me>
|
|
9
|
+
License: Copyright (c) 2021-present Lars B. Rollik. All rights reserved.
|
|
10
|
+
|
|
11
|
+
Permission is granted, free of charge, to use, copy, modify, and distribute
|
|
12
|
+
this software and associated documentation files (the "Software") for
|
|
13
|
+
non-commercial research or academic purposes only, subject to the following
|
|
14
|
+
conditions:
|
|
15
|
+
|
|
16
|
+
1. This copyright notice and permission notice must be included in all copies
|
|
17
|
+
or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
2. Any publication, presentation, or product that uses or builds upon the
|
|
20
|
+
Software must give clear attribution to the original work and its authors.
|
|
21
|
+
|
|
22
|
+
3. Commercial use is prohibited without a separate written commercial licence
|
|
23
|
+
agreement with the copyright holder. Commercial use means incorporation of
|
|
24
|
+
the Software into anything for which fees or other compensation are charged
|
|
25
|
+
or received, including commercial products and commercial services.
|
|
26
|
+
|
|
27
|
+
4. No patent licence, express or implied, is granted under these terms. Any
|
|
28
|
+
use that would require a patent licence from the copyright holder requires
|
|
29
|
+
a separate written agreement.
|
|
30
|
+
|
|
31
|
+
5. Redistribution, in source or binary form, is permitted only for
|
|
32
|
+
non-commercial purposes and must retain this notice unmodified.
|
|
33
|
+
|
|
34
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
35
|
+
IMPLIED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM,
|
|
36
|
+
DAMAGES, OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE
|
|
37
|
+
SOFTWARE OR THE USE OR DEALINGS IN THE SOFTWARE.
|
|
38
|
+
|
|
39
|
+
For commercial or patent licensing enquiries contact: lars@rollik.me
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Python: >=3.10
|
|
42
|
+
Requires-Dist: numpy
|
|
43
|
+
Requires-Dist: pandas
|
|
44
|
+
Requires-Dist: typer
|
|
45
|
+
Provides-Extra: dev
|
|
46
|
+
Requires-Dist: commitizen; extra == 'dev'
|
|
47
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
48
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
49
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
50
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
51
|
+
Provides-Extra: docs
|
|
52
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
53
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
54
|
+
Description-Content-Type: text/markdown
|
|
55
|
+
|
|
56
|
+
# msw-flir-bonsai
|
|
57
|
+
|
|
58
|
+
FLIR camera acquisition for MurineShiftWork via Bonsai subprocesses.
|
|
59
|
+
|
|
60
|
+
Each camera runs in an **isolated Bonsai subprocess** — a crash in one camera does not
|
|
61
|
+
affect others or the main behaviour task. Workflow XMLs for FlyCapture (PointGrey2) and
|
|
62
|
+
Spinnaker cameras are shipped as package data.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
pip install msw-flir-bonsai
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or with uv:
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
uv add msw-flir-bonsai
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Runtime dependencies: `numpy`, `pandas`. No FLIR Python SDK is required on the acquisition
|
|
79
|
+
machine — Bonsai handles all camera communication.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Requirements
|
|
84
|
+
|
|
85
|
+
### Bonsai (Windows only)
|
|
86
|
+
|
|
87
|
+
Install [Bonsai](https://bonsai-rx.org/) and the following NuGet packages via the Bonsai
|
|
88
|
+
package manager (`Tools → Manage Packages`):
|
|
89
|
+
|
|
90
|
+
| Package | Purpose | Tested version |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `Bonsai.Core` | Core reactive framework | 2.8.5 |
|
|
93
|
+
| `Bonsai.Design` | Editor UI | 2.8.5 |
|
|
94
|
+
| `Bonsai.Editor` | Editor shell | 2.8.5 |
|
|
95
|
+
| `Bonsai.System` | I/O operators (CsvWriter, etc.) | 2.8.5 |
|
|
96
|
+
| `Bonsai.Vision` | VideoWriter, image processing | 2.8.5 |
|
|
97
|
+
| `Bonsai.Vision.Design` | Vision editor support | 2.8.5 |
|
|
98
|
+
| `Bonsai.Scripting.IronPython` | IronPython inline scripts | 2.8.5 |
|
|
99
|
+
| `Bonsai.Scripting.IronPython.Design` | IronPython editor | 2.8.5 |
|
|
100
|
+
| `Bonsai.PointGrey2` | FlyCapture2 camera driver | 0.3.0 |
|
|
101
|
+
| `Bonsai.Spinnaker` | Spinnaker camera driver | 0.4.0 |
|
|
102
|
+
|
|
103
|
+
### Camera drivers (Windows)
|
|
104
|
+
|
|
105
|
+
| Driver | Version | Notes |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| FlyCapture2 SDK | 2.13.3 | Required for PointGrey / FLIR Grasshopper cameras |
|
|
108
|
+
| Spinnaker SDK | 3.x | Required for FLIR Blackfly S / BFS cameras |
|
|
109
|
+
|
|
110
|
+
> **Note:** FlyCapture2 and Spinnaker are mutually exclusive on the same machine in some
|
|
111
|
+
> versions. If using Spinnaker cameras, install only the Spinnaker SDK.
|
|
112
|
+
|
|
113
|
+
### Bonsai executable path
|
|
114
|
+
|
|
115
|
+
Set the `BONSAI_EXE` environment variable on the acquisition machine:
|
|
116
|
+
|
|
117
|
+
```bat
|
|
118
|
+
setx BONSAI_EXE "C:\Users\<user>\AppData\Local\Bonsai\Bonsai.exe"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or pass `bonsai_exe=` directly to `BonsaiCameraRunner`.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Quick start
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from msw_flir_bonsai.runner import BonsaiCameraRunner
|
|
129
|
+
|
|
130
|
+
runner = BonsaiCameraRunner(
|
|
131
|
+
workflow="run-flir-flycap-1cam", # or "run-flir-spinnaker-1cam"
|
|
132
|
+
output_dir=r"D:\DATA\video",
|
|
133
|
+
session="mouse001__20260518_120000",
|
|
134
|
+
cam_index=0,
|
|
135
|
+
fps=60,
|
|
136
|
+
driver="flycap",
|
|
137
|
+
)
|
|
138
|
+
runner.start()
|
|
139
|
+
|
|
140
|
+
# ... main task runs on Linux, camera runs on Windows acquisition machine ...
|
|
141
|
+
|
|
142
|
+
runner.stop()
|
|
143
|
+
runner.wait(timeout=10)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Multiple cameras
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from msw_flir_bonsai.runner import MultiCameraRunner
|
|
150
|
+
|
|
151
|
+
cameras = MultiCameraRunner.from_config(
|
|
152
|
+
n_cameras=2,
|
|
153
|
+
driver="flycap",
|
|
154
|
+
output_dir=r"D:\DATA\video",
|
|
155
|
+
session="mouse001__20260518_120000",
|
|
156
|
+
fps=60,
|
|
157
|
+
)
|
|
158
|
+
cameras.start()
|
|
159
|
+
# each camera is an independent subprocess — one crash does not stop the other
|
|
160
|
+
cameras.stop()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### CLI
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
msw-flir find-bonsai # locate Bonsai.exe
|
|
167
|
+
msw-flir list-cameras --driver flycap # enumerate connected cameras
|
|
168
|
+
msw-flir test-record --cam-index 0 --fps 30 # 5-second test recording
|
|
169
|
+
msw-flir run D:\DATA\video mouse001 --n-cameras 2 --fps 60
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Output files
|
|
175
|
+
|
|
176
|
+
Each Bonsai workflow creates a session directory and writes:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
<output_dir>/<session>/<session>__<datetime>/
|
|
180
|
+
<session>__<datetime>__cam1.avi # video
|
|
181
|
+
<session>__<datetime>__cam1.csv # per-frame metadata
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**CSV columns (FlyCapture):**
|
|
185
|
+
|
|
186
|
+
| Column | Description |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `frame_counter` | Hardware frame counter (rolls over at 32-bit) |
|
|
189
|
+
| `timestamp_raw` | Embedded hardware timestamp (seconds, cycles every 128 s) |
|
|
190
|
+
| `gpio_state` | GPIO input state (0/1) — records TTL barcode and trial pulses |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Timestamp preprocessing
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from msw_flir_bonsai.timestamps import preprocess_camera_csv, detect_dropped_frames
|
|
198
|
+
|
|
199
|
+
df = preprocess_camera_csv(
|
|
200
|
+
"cam1.csv",
|
|
201
|
+
ts_cycle_s=128.0, # FlyCapture rollover period; np.inf for Spinnaker
|
|
202
|
+
session_start_s=None, # set to subtract session t0 if known
|
|
203
|
+
)
|
|
204
|
+
# df["timestamp_s"] — unwrapped monotonic timestamps
|
|
205
|
+
# df["frame_counter"] — unwrapped frame counter
|
|
206
|
+
# df["gpio_state"] — TTL/barcode input
|
|
207
|
+
|
|
208
|
+
drops = detect_dropped_frames(df, expected_fps=60.0)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Alignment
|
|
214
|
+
|
|
215
|
+
Two channels link camera frames to Bpod behaviour timestamps:
|
|
216
|
+
|
|
217
|
+
### 1. TTL barcodes
|
|
218
|
+
|
|
219
|
+
Periodic binary barcode pulses recorded on both the camera GPIO and the Bpod BNC output.
|
|
220
|
+
Partial barcodes are handled via Hamming-distance matching (up to 2 bit errors tolerated).
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from msw_flir_bonsai.alignment import extract_camera_barcodes, align_barcodes
|
|
224
|
+
|
|
225
|
+
cam_barcodes = extract_camera_barcodes(df) # [(time_s, value), ...]
|
|
226
|
+
bpod_barcodes = [...] # from Bpod session data
|
|
227
|
+
offset_s = align_barcodes(bpod_barcodes, cam_barcodes)
|
|
228
|
+
df["timestamp_bpod"] = df["timestamp_s"] + offset_s
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 2. Trial TTL edges (fallback)
|
|
232
|
+
|
|
233
|
+
The sequence task and others pulse a Bpod BNC line at trial start/end. The camera GPIO
|
|
234
|
+
records these transitions at frame resolution, providing per-frame trial alignment even
|
|
235
|
+
when barcodes are absent or incomplete.
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from msw_flir_bonsai.alignment import align_ttl_edges
|
|
239
|
+
|
|
240
|
+
bpod_trial_starts = [...] # trial-start times from Bpod session YAML
|
|
241
|
+
offset_s = align_ttl_edges(df, bpod_trial_starts)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Development setup
|
|
247
|
+
|
|
248
|
+
```sh
|
|
249
|
+
git clone https://github.com/murineshiftwork/msw-flir-bonsai.git
|
|
250
|
+
cd msw-flir-bonsai
|
|
251
|
+
uv sync --extra dev
|
|
252
|
+
uv run pre-commit install --hook-type pre-commit --hook-type commit-msg
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Running tests
|
|
256
|
+
|
|
257
|
+
```sh
|
|
258
|
+
uv run pytest
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Integration tests against a real Bonsai installation (Windows only):
|
|
262
|
+
|
|
263
|
+
```sh
|
|
264
|
+
set BONSAI_EXE=C:\Users\<user>\AppData\Local\Bonsai\Bonsai.exe
|
|
265
|
+
uv run pytest tests/integration/ -v
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Release workflow
|
|
269
|
+
|
|
270
|
+
1. Work on a `feature/` or `fix/` branch, committing with `cz commit`
|
|
271
|
+
2. Open a PR — CI (lint + tests + secrets scan) must pass before merge
|
|
272
|
+
3. Merge to main → version bump and tag are created automatically
|
|
273
|
+
4. Tag triggers release: GitHub release + PyPI publish
|
|
274
|
+
|
|
275
|
+
## License
|
|
276
|
+
|
|
277
|
+
See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# msw-flir-bonsai
|
|
2
|
+
|
|
3
|
+
FLIR camera acquisition for MurineShiftWork via Bonsai subprocesses.
|
|
4
|
+
|
|
5
|
+
Each camera runs in an **isolated Bonsai subprocess** — a crash in one camera does not
|
|
6
|
+
affect others or the main behaviour task. Workflow XMLs for FlyCapture (PointGrey2) and
|
|
7
|
+
Spinnaker cameras are shipped as package data.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
pip install msw-flir-bonsai
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or with uv:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
uv add msw-flir-bonsai
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Runtime dependencies: `numpy`, `pandas`. No FLIR Python SDK is required on the acquisition
|
|
24
|
+
machine — Bonsai handles all camera communication.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Requirements
|
|
29
|
+
|
|
30
|
+
### Bonsai (Windows only)
|
|
31
|
+
|
|
32
|
+
Install [Bonsai](https://bonsai-rx.org/) and the following NuGet packages via the Bonsai
|
|
33
|
+
package manager (`Tools → Manage Packages`):
|
|
34
|
+
|
|
35
|
+
| Package | Purpose | Tested version |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `Bonsai.Core` | Core reactive framework | 2.8.5 |
|
|
38
|
+
| `Bonsai.Design` | Editor UI | 2.8.5 |
|
|
39
|
+
| `Bonsai.Editor` | Editor shell | 2.8.5 |
|
|
40
|
+
| `Bonsai.System` | I/O operators (CsvWriter, etc.) | 2.8.5 |
|
|
41
|
+
| `Bonsai.Vision` | VideoWriter, image processing | 2.8.5 |
|
|
42
|
+
| `Bonsai.Vision.Design` | Vision editor support | 2.8.5 |
|
|
43
|
+
| `Bonsai.Scripting.IronPython` | IronPython inline scripts | 2.8.5 |
|
|
44
|
+
| `Bonsai.Scripting.IronPython.Design` | IronPython editor | 2.8.5 |
|
|
45
|
+
| `Bonsai.PointGrey2` | FlyCapture2 camera driver | 0.3.0 |
|
|
46
|
+
| `Bonsai.Spinnaker` | Spinnaker camera driver | 0.4.0 |
|
|
47
|
+
|
|
48
|
+
### Camera drivers (Windows)
|
|
49
|
+
|
|
50
|
+
| Driver | Version | Notes |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| FlyCapture2 SDK | 2.13.3 | Required for PointGrey / FLIR Grasshopper cameras |
|
|
53
|
+
| Spinnaker SDK | 3.x | Required for FLIR Blackfly S / BFS cameras |
|
|
54
|
+
|
|
55
|
+
> **Note:** FlyCapture2 and Spinnaker are mutually exclusive on the same machine in some
|
|
56
|
+
> versions. If using Spinnaker cameras, install only the Spinnaker SDK.
|
|
57
|
+
|
|
58
|
+
### Bonsai executable path
|
|
59
|
+
|
|
60
|
+
Set the `BONSAI_EXE` environment variable on the acquisition machine:
|
|
61
|
+
|
|
62
|
+
```bat
|
|
63
|
+
setx BONSAI_EXE "C:\Users\<user>\AppData\Local\Bonsai\Bonsai.exe"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or pass `bonsai_exe=` directly to `BonsaiCameraRunner`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from msw_flir_bonsai.runner import BonsaiCameraRunner
|
|
74
|
+
|
|
75
|
+
runner = BonsaiCameraRunner(
|
|
76
|
+
workflow="run-flir-flycap-1cam", # or "run-flir-spinnaker-1cam"
|
|
77
|
+
output_dir=r"D:\DATA\video",
|
|
78
|
+
session="mouse001__20260518_120000",
|
|
79
|
+
cam_index=0,
|
|
80
|
+
fps=60,
|
|
81
|
+
driver="flycap",
|
|
82
|
+
)
|
|
83
|
+
runner.start()
|
|
84
|
+
|
|
85
|
+
# ... main task runs on Linux, camera runs on Windows acquisition machine ...
|
|
86
|
+
|
|
87
|
+
runner.stop()
|
|
88
|
+
runner.wait(timeout=10)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Multiple cameras
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from msw_flir_bonsai.runner import MultiCameraRunner
|
|
95
|
+
|
|
96
|
+
cameras = MultiCameraRunner.from_config(
|
|
97
|
+
n_cameras=2,
|
|
98
|
+
driver="flycap",
|
|
99
|
+
output_dir=r"D:\DATA\video",
|
|
100
|
+
session="mouse001__20260518_120000",
|
|
101
|
+
fps=60,
|
|
102
|
+
)
|
|
103
|
+
cameras.start()
|
|
104
|
+
# each camera is an independent subprocess — one crash does not stop the other
|
|
105
|
+
cameras.stop()
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### CLI
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
msw-flir find-bonsai # locate Bonsai.exe
|
|
112
|
+
msw-flir list-cameras --driver flycap # enumerate connected cameras
|
|
113
|
+
msw-flir test-record --cam-index 0 --fps 30 # 5-second test recording
|
|
114
|
+
msw-flir run D:\DATA\video mouse001 --n-cameras 2 --fps 60
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Output files
|
|
120
|
+
|
|
121
|
+
Each Bonsai workflow creates a session directory and writes:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
<output_dir>/<session>/<session>__<datetime>/
|
|
125
|
+
<session>__<datetime>__cam1.avi # video
|
|
126
|
+
<session>__<datetime>__cam1.csv # per-frame metadata
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**CSV columns (FlyCapture):**
|
|
130
|
+
|
|
131
|
+
| Column | Description |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `frame_counter` | Hardware frame counter (rolls over at 32-bit) |
|
|
134
|
+
| `timestamp_raw` | Embedded hardware timestamp (seconds, cycles every 128 s) |
|
|
135
|
+
| `gpio_state` | GPIO input state (0/1) — records TTL barcode and trial pulses |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Timestamp preprocessing
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from msw_flir_bonsai.timestamps import preprocess_camera_csv, detect_dropped_frames
|
|
143
|
+
|
|
144
|
+
df = preprocess_camera_csv(
|
|
145
|
+
"cam1.csv",
|
|
146
|
+
ts_cycle_s=128.0, # FlyCapture rollover period; np.inf for Spinnaker
|
|
147
|
+
session_start_s=None, # set to subtract session t0 if known
|
|
148
|
+
)
|
|
149
|
+
# df["timestamp_s"] — unwrapped monotonic timestamps
|
|
150
|
+
# df["frame_counter"] — unwrapped frame counter
|
|
151
|
+
# df["gpio_state"] — TTL/barcode input
|
|
152
|
+
|
|
153
|
+
drops = detect_dropped_frames(df, expected_fps=60.0)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Alignment
|
|
159
|
+
|
|
160
|
+
Two channels link camera frames to Bpod behaviour timestamps:
|
|
161
|
+
|
|
162
|
+
### 1. TTL barcodes
|
|
163
|
+
|
|
164
|
+
Periodic binary barcode pulses recorded on both the camera GPIO and the Bpod BNC output.
|
|
165
|
+
Partial barcodes are handled via Hamming-distance matching (up to 2 bit errors tolerated).
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from msw_flir_bonsai.alignment import extract_camera_barcodes, align_barcodes
|
|
169
|
+
|
|
170
|
+
cam_barcodes = extract_camera_barcodes(df) # [(time_s, value), ...]
|
|
171
|
+
bpod_barcodes = [...] # from Bpod session data
|
|
172
|
+
offset_s = align_barcodes(bpod_barcodes, cam_barcodes)
|
|
173
|
+
df["timestamp_bpod"] = df["timestamp_s"] + offset_s
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 2. Trial TTL edges (fallback)
|
|
177
|
+
|
|
178
|
+
The sequence task and others pulse a Bpod BNC line at trial start/end. The camera GPIO
|
|
179
|
+
records these transitions at frame resolution, providing per-frame trial alignment even
|
|
180
|
+
when barcodes are absent or incomplete.
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from msw_flir_bonsai.alignment import align_ttl_edges
|
|
184
|
+
|
|
185
|
+
bpod_trial_starts = [...] # trial-start times from Bpod session YAML
|
|
186
|
+
offset_s = align_ttl_edges(df, bpod_trial_starts)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Development setup
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
git clone https://github.com/murineshiftwork/msw-flir-bonsai.git
|
|
195
|
+
cd msw-flir-bonsai
|
|
196
|
+
uv sync --extra dev
|
|
197
|
+
uv run pre-commit install --hook-type pre-commit --hook-type commit-msg
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Running tests
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
uv run pytest
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Integration tests against a real Bonsai installation (Windows only):
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
set BONSAI_EXE=C:\Users\<user>\AppData\Local\Bonsai\Bonsai.exe
|
|
210
|
+
uv run pytest tests/integration/ -v
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Release workflow
|
|
214
|
+
|
|
215
|
+
1. Work on a `feature/` or `fix/` branch, committing with `cz commit`
|
|
216
|
+
2. Open a PR — CI (lint + tests + secrets scan) must pass before merge
|
|
217
|
+
3. Merge to main → version bump and tag are created automatically
|
|
218
|
+
4. Tag triggers release: GitHub release + PyPI publish
|
|
219
|
+
|
|
220
|
+
## License
|
|
221
|
+
|
|
222
|
+
See [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.2.2
|