msw_flir_bonsai 0.2.1__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.1/.gitignore +72 -0
- msw_flir_bonsai-0.2.1/CITATION.cff +12 -0
- msw_flir_bonsai-0.2.1/LICENSE +29 -0
- msw_flir_bonsai-0.2.1/PKG-INFO +275 -0
- msw_flir_bonsai-0.2.1/README.md +222 -0
- msw_flir_bonsai-0.2.1/VERSION +1 -0
- msw_flir_bonsai-0.2.1/pyproject.toml +134 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/__init__.py +8 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/_version.py +24 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/alignment.py +211 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/__init__.py +12 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/run-flir-flycap-1cam.bonsai +276 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/run-flir-flycap-2cam.bonsai +470 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/run-flir-spinnaker-1cam.bonsai +270 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/run-flir-spinnaker-2cam.bonsai +453 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/cli.py +313 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/py.typed +0 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/runner.py +269 -0
- msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/timestamps.py +148 -0
- msw_flir_bonsai-0.2.1/tests/__init__.py +0 -0
- msw_flir_bonsai-0.2.1/tests/conftest.py +1 -0
- msw_flir_bonsai-0.2.1/tests/integration/__init__.py +0 -0
- msw_flir_bonsai-0.2.1/tests/integration/test_placeholder.py +6 -0
- msw_flir_bonsai-0.2.1/tests/integration/test_runner_live.py +163 -0
- msw_flir_bonsai-0.2.1/tests/test_timestamps.py +100 -0
- msw_flir_bonsai-0.2.1/tests/unit/__init__.py +0 -0
- msw_flir_bonsai-0.2.1/tests/unit/test_alignment.py +156 -0
- msw_flir_bonsai-0.2.1/tests/unit/test_cli.py +80 -0
- msw_flir_bonsai-0.2.1/tests/unit/test_placeholder.py +8 -0
- msw_flir_bonsai-0.2.1/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,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021, 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,275 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: msw_flir_bonsai
|
|
3
|
+
Version: 0.2.1
|
|
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" <L.B.Rollik@protonmail.com>
|
|
9
|
+
License: BSD 3-Clause License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2021, 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
|
+
Requires-Python: >=3.10
|
|
40
|
+
Requires-Dist: numpy
|
|
41
|
+
Requires-Dist: pandas
|
|
42
|
+
Requires-Dist: typer
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: commitizen; extra == 'dev'
|
|
45
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
46
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
47
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
48
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
49
|
+
Provides-Extra: docs
|
|
50
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
51
|
+
Requires-Dist: mkdocstrings[python]; extra == 'docs'
|
|
52
|
+
Description-Content-Type: text/markdown
|
|
53
|
+
|
|
54
|
+
# msw-flir-bonsai
|
|
55
|
+
|
|
56
|
+
FLIR camera acquisition for MurineShiftWork via Bonsai subprocesses.
|
|
57
|
+
|
|
58
|
+
Each camera runs in an **isolated Bonsai subprocess** — a crash in one camera does not
|
|
59
|
+
affect others or the main behaviour task. Workflow XMLs for FlyCapture (PointGrey2) and
|
|
60
|
+
Spinnaker cameras are shipped as package data.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
pip install msw-flir-bonsai
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or with uv:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
uv add msw-flir-bonsai
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Runtime dependencies: `numpy`, `pandas`. No FLIR Python SDK is required on the acquisition
|
|
77
|
+
machine — Bonsai handles all camera communication.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Requirements
|
|
82
|
+
|
|
83
|
+
### Bonsai (Windows only)
|
|
84
|
+
|
|
85
|
+
Install [Bonsai](https://bonsai-rx.org/) and the following NuGet packages via the Bonsai
|
|
86
|
+
package manager (`Tools → Manage Packages`):
|
|
87
|
+
|
|
88
|
+
| Package | Purpose | Tested version |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `Bonsai.Core` | Core reactive framework | 2.8.5 |
|
|
91
|
+
| `Bonsai.Design` | Editor UI | 2.8.5 |
|
|
92
|
+
| `Bonsai.Editor` | Editor shell | 2.8.5 |
|
|
93
|
+
| `Bonsai.System` | I/O operators (CsvWriter, etc.) | 2.8.5 |
|
|
94
|
+
| `Bonsai.Vision` | VideoWriter, image processing | 2.8.5 |
|
|
95
|
+
| `Bonsai.Vision.Design` | Vision editor support | 2.8.5 |
|
|
96
|
+
| `Bonsai.Scripting.IronPython` | IronPython inline scripts | 2.8.5 |
|
|
97
|
+
| `Bonsai.Scripting.IronPython.Design` | IronPython editor | 2.8.5 |
|
|
98
|
+
| `Bonsai.PointGrey2` | FlyCapture2 camera driver | 0.3.0 |
|
|
99
|
+
| `Bonsai.Spinnaker` | Spinnaker camera driver | 0.4.0 |
|
|
100
|
+
|
|
101
|
+
### Camera drivers (Windows)
|
|
102
|
+
|
|
103
|
+
| Driver | Version | Notes |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| FlyCapture2 SDK | 2.13.3 | Required for PointGrey / FLIR Grasshopper cameras |
|
|
106
|
+
| Spinnaker SDK | 3.x | Required for FLIR Blackfly S / BFS cameras |
|
|
107
|
+
|
|
108
|
+
> **Note:** FlyCapture2 and Spinnaker are mutually exclusive on the same machine in some
|
|
109
|
+
> versions. If using Spinnaker cameras, install only the Spinnaker SDK.
|
|
110
|
+
|
|
111
|
+
### Bonsai executable path
|
|
112
|
+
|
|
113
|
+
Set the `BONSAI_EXE` environment variable on the acquisition machine:
|
|
114
|
+
|
|
115
|
+
```bat
|
|
116
|
+
setx BONSAI_EXE "C:\Users\<user>\AppData\Local\Bonsai\Bonsai.exe"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Or pass `bonsai_exe=` directly to `BonsaiCameraRunner`.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Quick start
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from msw_flir_bonsai.runner import BonsaiCameraRunner
|
|
127
|
+
|
|
128
|
+
runner = BonsaiCameraRunner(
|
|
129
|
+
workflow="run-flir-flycap-1cam", # or "run-flir-spinnaker-1cam"
|
|
130
|
+
output_dir=r"D:\DATA\video",
|
|
131
|
+
session="mouse001__20260518_120000",
|
|
132
|
+
cam_index=0,
|
|
133
|
+
fps=60,
|
|
134
|
+
driver="flycap",
|
|
135
|
+
)
|
|
136
|
+
runner.start()
|
|
137
|
+
|
|
138
|
+
# ... main task runs on Linux, camera runs on Windows acquisition machine ...
|
|
139
|
+
|
|
140
|
+
runner.stop()
|
|
141
|
+
runner.wait(timeout=10)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Multiple cameras
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from msw_flir_bonsai.runner import MultiCameraRunner
|
|
148
|
+
|
|
149
|
+
cameras = MultiCameraRunner.from_config(
|
|
150
|
+
n_cameras=2,
|
|
151
|
+
driver="flycap",
|
|
152
|
+
output_dir=r"D:\DATA\video",
|
|
153
|
+
session="mouse001__20260518_120000",
|
|
154
|
+
fps=60,
|
|
155
|
+
)
|
|
156
|
+
cameras.start()
|
|
157
|
+
# each camera is an independent subprocess — one crash does not stop the other
|
|
158
|
+
cameras.stop()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### CLI
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
msw-flir find-bonsai # locate Bonsai.exe
|
|
165
|
+
msw-flir list-cameras --driver flycap # enumerate connected cameras
|
|
166
|
+
msw-flir test-record --cam-index 0 --fps 30 # 5-second test recording
|
|
167
|
+
msw-flir run D:\DATA\video mouse001 --n-cameras 2 --fps 60
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Output files
|
|
173
|
+
|
|
174
|
+
Each Bonsai workflow creates a session directory and writes:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
<output_dir>/<session>/<session>__<datetime>/
|
|
178
|
+
<session>__<datetime>__cam1.avi # video
|
|
179
|
+
<session>__<datetime>__cam1.csv # per-frame metadata
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**CSV columns (FlyCapture):**
|
|
183
|
+
|
|
184
|
+
| Column | Description |
|
|
185
|
+
|---|---|
|
|
186
|
+
| `frame_counter` | Hardware frame counter (rolls over at 32-bit) |
|
|
187
|
+
| `timestamp_raw` | Embedded hardware timestamp (seconds, cycles every 128 s) |
|
|
188
|
+
| `gpio_state` | GPIO input state (0/1) — records TTL barcode and trial pulses |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Timestamp preprocessing
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from msw_flir_bonsai.timestamps import preprocess_camera_csv, detect_dropped_frames
|
|
196
|
+
|
|
197
|
+
df = preprocess_camera_csv(
|
|
198
|
+
"cam1.csv",
|
|
199
|
+
ts_cycle_s=128.0, # FlyCapture rollover period; np.inf for Spinnaker
|
|
200
|
+
session_start_s=None, # set to subtract session t0 if known
|
|
201
|
+
)
|
|
202
|
+
# df["timestamp_s"] — unwrapped monotonic timestamps
|
|
203
|
+
# df["frame_counter"] — unwrapped frame counter
|
|
204
|
+
# df["gpio_state"] — TTL/barcode input
|
|
205
|
+
|
|
206
|
+
drops = detect_dropped_frames(df, expected_fps=60.0)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Alignment
|
|
212
|
+
|
|
213
|
+
Two channels link camera frames to Bpod behaviour timestamps:
|
|
214
|
+
|
|
215
|
+
### 1. TTL barcodes
|
|
216
|
+
|
|
217
|
+
Periodic binary barcode pulses recorded on both the camera GPIO and the Bpod BNC output.
|
|
218
|
+
Partial barcodes are handled via Hamming-distance matching (up to 2 bit errors tolerated).
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from msw_flir_bonsai.alignment import extract_camera_barcodes, align_barcodes
|
|
222
|
+
|
|
223
|
+
cam_barcodes = extract_camera_barcodes(df) # [(time_s, value), ...]
|
|
224
|
+
bpod_barcodes = [...] # from Bpod session data
|
|
225
|
+
offset_s = align_barcodes(bpod_barcodes, cam_barcodes)
|
|
226
|
+
df["timestamp_bpod"] = df["timestamp_s"] + offset_s
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 2. Trial TTL edges (fallback)
|
|
230
|
+
|
|
231
|
+
The sequence task and others pulse a Bpod BNC line at trial start/end. The camera GPIO
|
|
232
|
+
records these transitions at frame resolution, providing per-frame trial alignment even
|
|
233
|
+
when barcodes are absent or incomplete.
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from msw_flir_bonsai.alignment import align_ttl_edges
|
|
237
|
+
|
|
238
|
+
bpod_trial_starts = [...] # trial-start times from Bpod session YAML
|
|
239
|
+
offset_s = align_ttl_edges(df, bpod_trial_starts)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Development setup
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
git clone https://github.com/murineshiftwork/msw-flir-bonsai.git
|
|
248
|
+
cd msw-flir-bonsai
|
|
249
|
+
uv sync --extra dev
|
|
250
|
+
uv run pre-commit install --hook-type pre-commit --hook-type commit-msg
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Running tests
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
uv run pytest
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Integration tests against a real Bonsai installation (Windows only):
|
|
260
|
+
|
|
261
|
+
```sh
|
|
262
|
+
set BONSAI_EXE=C:\Users\<user>\AppData\Local\Bonsai\Bonsai.exe
|
|
263
|
+
uv run pytest tests/integration/ -v
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Release workflow
|
|
267
|
+
|
|
268
|
+
1. Work on a `feature/` or `fix/` branch, committing with `cz commit`
|
|
269
|
+
2. Open a PR — CI (lint + tests + secrets scan) must pass before merge
|
|
270
|
+
3. Merge to main → version bump and tag are created automatically
|
|
271
|
+
4. Tag triggers release: GitHub release + PyPI publish
|
|
272
|
+
|
|
273
|
+
## License
|
|
274
|
+
|
|
275
|
+
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.1
|