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