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.
Files changed (30) hide show
  1. msw_flir_bonsai-0.2.1/.gitignore +72 -0
  2. msw_flir_bonsai-0.2.1/CITATION.cff +12 -0
  3. msw_flir_bonsai-0.2.1/LICENSE +29 -0
  4. msw_flir_bonsai-0.2.1/PKG-INFO +275 -0
  5. msw_flir_bonsai-0.2.1/README.md +222 -0
  6. msw_flir_bonsai-0.2.1/VERSION +1 -0
  7. msw_flir_bonsai-0.2.1/pyproject.toml +134 -0
  8. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/__init__.py +8 -0
  9. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/_version.py +24 -0
  10. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/alignment.py +211 -0
  11. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/__init__.py +12 -0
  12. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/run-flir-flycap-1cam.bonsai +276 -0
  13. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/run-flir-flycap-2cam.bonsai +470 -0
  14. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/run-flir-spinnaker-1cam.bonsai +270 -0
  15. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/bonsai_workflows/run-flir-spinnaker-2cam.bonsai +453 -0
  16. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/cli.py +313 -0
  17. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/py.typed +0 -0
  18. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/runner.py +269 -0
  19. msw_flir_bonsai-0.2.1/src/msw_flir_bonsai/timestamps.py +148 -0
  20. msw_flir_bonsai-0.2.1/tests/__init__.py +0 -0
  21. msw_flir_bonsai-0.2.1/tests/conftest.py +1 -0
  22. msw_flir_bonsai-0.2.1/tests/integration/__init__.py +0 -0
  23. msw_flir_bonsai-0.2.1/tests/integration/test_placeholder.py +6 -0
  24. msw_flir_bonsai-0.2.1/tests/integration/test_runner_live.py +163 -0
  25. msw_flir_bonsai-0.2.1/tests/test_timestamps.py +100 -0
  26. msw_flir_bonsai-0.2.1/tests/unit/__init__.py +0 -0
  27. msw_flir_bonsai-0.2.1/tests/unit/test_alignment.py +156 -0
  28. msw_flir_bonsai-0.2.1/tests/unit/test_cli.py +80 -0
  29. msw_flir_bonsai-0.2.1/tests/unit/test_placeholder.py +8 -0
  30. 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