driftless-bundle-photometry 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 (134) hide show
  1. driftless_bundle_photometry-0.2.1/.github/workflows/ci.yml +83 -0
  2. driftless_bundle_photometry-0.2.1/.github/workflows/release.yml +192 -0
  3. driftless_bundle_photometry-0.2.1/.gitignore +19 -0
  4. driftless_bundle_photometry-0.2.1/AGENTS.md +185 -0
  5. driftless_bundle_photometry-0.2.1/CLAUDE.md +185 -0
  6. driftless_bundle_photometry-0.2.1/LICENSE +21 -0
  7. driftless_bundle_photometry-0.2.1/PKG-INFO +305 -0
  8. driftless_bundle_photometry-0.2.1/WHATS_NEW.md +265 -0
  9. driftless_bundle_photometry-0.2.1/docs/architecture.md +244 -0
  10. driftless_bundle_photometry-0.2.1/docs/controller-protocol.md +33 -0
  11. driftless_bundle_photometry-0.2.1/docs/releases/0.2.0.md +56 -0
  12. driftless_bundle_photometry-0.2.1/docs/releases/0.2.1.md +45 -0
  13. driftless_bundle_photometry-0.2.1/docs/releasing.md +67 -0
  14. driftless_bundle_photometry-0.2.1/docs/rwd-operator-guide.md +145 -0
  15. driftless_bundle_photometry-0.2.1/docs/rwd-streaming-protocol.md +241 -0
  16. driftless_bundle_photometry-0.2.1/docs/thorlabs-camera-python-api.md +356 -0
  17. driftless_bundle_photometry-0.2.1/environment.yml +8 -0
  18. driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/__init__.py +0 -0
  19. driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/config.py +8 -0
  20. driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/functions.py +232 -0
  21. driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/plotRawAverageTraces.py +103 -0
  22. driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/stimResponse.py +64 -0
  23. driftless_bundle_photometry-0.2.1/old mescoscope/Arduino Codes/cameraControl/cameraControl.ino +187 -0
  24. driftless_bundle_photometry-0.2.1/old mescoscope/Arduino Codes/cameraControl.ino.hex +311 -0
  25. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/SoftwareTrigger.m +111 -0
  26. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/SoftwareTrigger_video.m +89 -0
  27. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/concatCamera_acquisition.py +98 -0
  28. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/concatCameras.bonsai +136 -0
  29. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/concatCameras.bonsai.layout +273 -0
  30. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/mesoscope_RCS_with_preview.mlapp +0 -0
  31. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/mesoscope_arduinoControl.mlapp +0 -0
  32. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/mesoscope_arduinoControl_RCS.mlapp +0 -0
  33. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/mesoscope_control_RCS_Batch.mlapp +0 -0
  34. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/test.m +2 -0
  35. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/test_background.m +17 -0
  36. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/test_externalFigurePanels.mlapp +0 -0
  37. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/test_writing_webcam.m +63 -0
  38. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/writeFrameBatch.asv +12 -0
  39. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/writeFrameBatch.m +12 -0
  40. driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/writeSingleFrame.m +32 -0
  41. driftless_bundle_photometry-0.2.1/old mescoscope/README.md +3 -0
  42. driftless_bundle_photometry-0.2.1/old mescoscope/helper functions/updateCameraLogFromArduino.m +24 -0
  43. driftless_bundle_photometry-0.2.1/old mescoscope/helper functions/writeBatchFrames_binary.m +15 -0
  44. driftless_bundle_photometry-0.2.1/old mescoscope/helper functions/writeSingleFrame_binary.m +14 -0
  45. driftless_bundle_photometry-0.2.1/old mescoscope/mesoscope_control.mlapp +0 -0
  46. driftless_bundle_photometry-0.2.1/old mescoscope/mesoscope_control_crop.mlapp +0 -0
  47. driftless_bundle_photometry-0.2.1/old mescoscope/plot_frames.m +36 -0
  48. driftless_bundle_photometry-0.2.1/packaging/windows/DriftlessBundlePhotometry.iss +62 -0
  49. driftless_bundle_photometry-0.2.1/packaging/windows/DriftlessBundlePhotometry.spec +87 -0
  50. driftless_bundle_photometry-0.2.1/pyproject.toml +83 -0
  51. driftless_bundle_photometry-0.2.1/readme.md +256 -0
  52. driftless_bundle_photometry-0.2.1/roadmap.md +700 -0
  53. driftless_bundle_photometry-0.2.1/src/driftless_photometry/__init__.py +10 -0
  54. driftless_bundle_photometry-0.2.1/src/driftless_photometry/__main__.py +3 -0
  55. driftless_bundle_photometry-0.2.1/src/driftless_photometry/acquisition.py +408 -0
  56. driftless_bundle_photometry-0.2.1/src/driftless_photometry/cli.py +309 -0
  57. driftless_bundle_photometry-0.2.1/src/driftless_photometry/config.py +384 -0
  58. driftless_bundle_photometry-0.2.1/src/driftless_photometry/desktop.py +36 -0
  59. driftless_bundle_photometry-0.2.1/src/driftless_photometry/diagnostics.py +49 -0
  60. driftless_bundle_photometry-0.2.1/src/driftless_photometry/domain.py +143 -0
  61. driftless_bundle_photometry-0.2.1/src/driftless_photometry/faults.py +27 -0
  62. driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/__init__.py +5 -0
  63. driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/app.py +34 -0
  64. driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/assets/__init__.py +1 -0
  65. driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/assets/driftless-photometry.png +0 -0
  66. driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/branding.py +14 -0
  67. driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/main_window.py +2208 -0
  68. driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/plot_interaction.py +94 -0
  69. driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/trace_display.py +118 -0
  70. driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/worker.py +107 -0
  71. driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/__init__.py +41 -0
  72. driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/base.py +23 -0
  73. driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/controller_protocol.py +174 -0
  74. driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/controller_serial.py +124 -0
  75. driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/simulator.py +144 -0
  76. driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/thorlabs.py +56 -0
  77. driftless_bundle_photometry-0.2.1/src/driftless_photometry/live_trace.py +96 -0
  78. driftless_bundle_photometry-0.2.1/src/driftless_photometry/provenance.py +84 -0
  79. driftless_bundle_photometry-0.2.1/src/driftless_photometry/roi.py +119 -0
  80. driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/__init__.py +77 -0
  81. driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/acquisition.py +244 -0
  82. driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/capture.py +477 -0
  83. driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/client.py +286 -0
  84. driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/domain.py +142 -0
  85. driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/protocol.py +301 -0
  86. driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/timing.py +266 -0
  87. driftless_bundle_photometry-0.2.1/src/driftless_photometry/settings.py +382 -0
  88. driftless_bundle_photometry-0.2.1/src/driftless_photometry/state.py +55 -0
  89. driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/__init__.py +82 -0
  90. driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/capacity.py +155 -0
  91. driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/frames.py +32 -0
  92. driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/nwb.py +1162 -0
  93. driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/recovery.py +125 -0
  94. driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/rwd_nwb.py +772 -0
  95. driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/rwd_recovery.py +117 -0
  96. driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/rwd_spool.py +591 -0
  97. driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/spool.py +659 -0
  98. driftless_bundle_photometry-0.2.1/src/driftless_photometry/timing.py +78 -0
  99. driftless_bundle_photometry-0.2.1/tests/conftest.py +12 -0
  100. driftless_bundle_photometry-0.2.1/tests/fixtures/rwd/README.md +14 -0
  101. driftless_bundle_photometry-0.2.1/tests/fixtures/rwd/event_on.hex +1 -0
  102. driftless_bundle_photometry-0.2.1/tests/fixtures/rwd/fluorescence_all.hex +1 -0
  103. driftless_bundle_photometry-0.2.1/tests/fixtures/rwd/mixed_with_banner.hex +1 -0
  104. driftless_bundle_photometry-0.2.1/tests/test_acquisition.py +402 -0
  105. driftless_bundle_photometry-0.2.1/tests/test_agent_contract.py +6 -0
  106. driftless_bundle_photometry-0.2.1/tests/test_capacity.py +130 -0
  107. driftless_bundle_photometry-0.2.1/tests/test_cli.py +124 -0
  108. driftless_bundle_photometry-0.2.1/tests/test_config.py +187 -0
  109. driftless_bundle_photometry-0.2.1/tests/test_controller_protocol.py +73 -0
  110. driftless_bundle_photometry-0.2.1/tests/test_controller_serial.py +78 -0
  111. driftless_bundle_photometry-0.2.1/tests/test_desktop.py +29 -0
  112. driftless_bundle_photometry-0.2.1/tests/test_domain.py +23 -0
  113. driftless_bundle_photometry-0.2.1/tests/test_gui.py +585 -0
  114. driftless_bundle_photometry-0.2.1/tests/test_live_trace.py +89 -0
  115. driftless_bundle_photometry-0.2.1/tests/test_nwb.py +270 -0
  116. driftless_bundle_photometry-0.2.1/tests/test_plot_interaction.py +140 -0
  117. driftless_bundle_photometry-0.2.1/tests/test_recovery.py +188 -0
  118. driftless_bundle_photometry-0.2.1/tests/test_release.py +66 -0
  119. driftless_bundle_photometry-0.2.1/tests/test_roi.py +97 -0
  120. driftless_bundle_photometry-0.2.1/tests/test_rwd_capture.py +136 -0
  121. driftless_bundle_photometry-0.2.1/tests/test_rwd_client.py +433 -0
  122. driftless_bundle_photometry-0.2.1/tests/test_rwd_domain.py +52 -0
  123. driftless_bundle_photometry-0.2.1/tests/test_rwd_protocol.py +265 -0
  124. driftless_bundle_photometry-0.2.1/tests/test_rwd_storage.py +471 -0
  125. driftless_bundle_photometry-0.2.1/tests/test_settings.py +117 -0
  126. driftless_bundle_photometry-0.2.1/tests/test_simulator.py +31 -0
  127. driftless_bundle_photometry-0.2.1/tests/test_spool.py +293 -0
  128. driftless_bundle_photometry-0.2.1/tests/test_state.py +23 -0
  129. driftless_bundle_photometry-0.2.1/tests/test_timing.py +37 -0
  130. driftless_bundle_photometry-0.2.1/tests/test_trace_display.py +114 -0
  131. driftless_bundle_photometry-0.2.1/tools/__init__.py +1 -0
  132. driftless_bundle_photometry-0.2.1/tools/make_windows_icon.py +31 -0
  133. driftless_bundle_photometry-0.2.1/tools/release.py +93 -0
  134. driftless_bundle_photometry-0.2.1/tools/wheel_smoke.py +148 -0
@@ -0,0 +1,83 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ci-${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ quality:
17
+ name: Quality checks
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v7
21
+ - uses: actions/setup-python@v7
22
+ with:
23
+ python-version: "3.13"
24
+ cache: pip
25
+ - name: Install development dependencies
26
+ run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
27
+ - name: Lint
28
+ run: python -m ruff check .
29
+ - name: Check formatting
30
+ run: python -m ruff format --check .
31
+ - name: Verify synchronized agent contracts
32
+ run: >-
33
+ python -c "from pathlib import Path;
34
+ assert Path('AGENTS.md').read_bytes() == Path('CLAUDE.md').read_bytes(),
35
+ 'AGENTS.md and CLAUDE.md differ'"
36
+
37
+ tests:
38
+ name: Tests (${{ matrix.os }}, Python ${{ matrix.python-version }})
39
+ runs-on: ${{ matrix.os }}
40
+ strategy:
41
+ fail-fast: false
42
+ matrix:
43
+ os: [windows-latest, macos-latest]
44
+ python-version: ["3.11", "3.12", "3.13"]
45
+ env:
46
+ PYTHONUTF8: "1"
47
+ QT_QPA_PLATFORM: offscreen
48
+ steps:
49
+ - uses: actions/checkout@v7
50
+ - uses: actions/setup-python@v7
51
+ with:
52
+ python-version: ${{ matrix.python-version }}
53
+ cache: pip
54
+ - name: Install exact project constraints
55
+ run: python -m pip install --upgrade pip && python -m pip install -e ".[dev]"
56
+ - name: Run full test suite
57
+ run: python -m pytest -q
58
+
59
+ wheel-smoke:
60
+ name: Wheel smoke (${{ matrix.os }}, ${{ matrix.mode }})
61
+ runs-on: ${{ matrix.os }}
62
+ strategy:
63
+ fail-fast: false
64
+ matrix:
65
+ os: [windows-latest, macos-latest]
66
+ mode: [core, gui]
67
+ env:
68
+ PYTHONUTF8: "1"
69
+ QT_QPA_PLATFORM: offscreen
70
+ steps:
71
+ - uses: actions/checkout@v7
72
+ - uses: actions/setup-python@v7
73
+ with:
74
+ python-version: "3.13"
75
+ cache: pip
76
+ - name: Build wheel
77
+ run: python -m pip install --upgrade pip && python -m pip wheel . --no-deps --wheel-dir wheelhouse
78
+ - name: Test wheel from an isolated environment
79
+ run: >-
80
+ python tools/wheel_smoke.py
81
+ --wheel-dir wheelhouse
82
+ --mode ${{ matrix.mode }}
83
+ --expected-version $(python tools/release.py version)
@@ -0,0 +1,192 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: release-${{ github.event.release.tag_name }}
12
+ cancel-in-progress: false
13
+
14
+ env:
15
+ PYTHONUTF8: "1"
16
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
17
+
18
+ jobs:
19
+ python-distributions:
20
+ name: Build and verify Python distributions
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v7
24
+ with:
25
+ ref: ${{ env.RELEASE_TAG }}
26
+ - uses: actions/setup-python@v7
27
+ with:
28
+ python-version: "3.13"
29
+ cache: pip
30
+ - name: Validate synchronized release metadata
31
+ run: python tools/release.py check "$RELEASE_TAG"
32
+ - name: Install Linux GUI runtime libraries
33
+ run: sudo apt-get update && sudo apt-get install --yes libegl1
34
+ - name: Install release tooling
35
+ run: python -m pip install --upgrade pip && python -m pip install ".[dev,release]"
36
+ - name: Run release quality gates
37
+ env:
38
+ QT_QPA_PLATFORM: offscreen
39
+ run: |
40
+ python -m ruff check .
41
+ python -m ruff format --check .
42
+ python -m pytest -q
43
+ - name: Build wheel and source distribution
44
+ run: python -m build
45
+ - name: Validate distribution metadata
46
+ run: python -m twine check --strict dist/*
47
+ - name: Smoke-test the built wheel outside the checkout
48
+ run: >-
49
+ python tools/wheel_smoke.py
50
+ --wheel-dir dist
51
+ --mode gui
52
+ --expected-version "${RELEASE_TAG#v}"
53
+ - name: Upload exact Python release artifacts
54
+ uses: actions/upload-artifact@v7
55
+ with:
56
+ name: python-distributions
57
+ path: |
58
+ dist/*.whl
59
+ dist/*.tar.gz
60
+ if-no-files-found: error
61
+
62
+ windows-installer:
63
+ name: Build and verify Windows installer
64
+ runs-on: windows-latest
65
+ steps:
66
+ - uses: actions/checkout@v7
67
+ with:
68
+ ref: ${{ env.RELEASE_TAG }}
69
+ - uses: actions/setup-python@v7
70
+ with:
71
+ python-version: "3.13"
72
+ cache: pip
73
+ - name: Validate synchronized release metadata
74
+ run: python tools/release.py check "$env:RELEASE_TAG"
75
+ - name: Install application and freezer
76
+ run: python -m pip install --upgrade pip && python -m pip install ".[gui,release]"
77
+ - name: Create multi-resolution Windows icon
78
+ run: >-
79
+ python tools/make_windows_icon.py
80
+ src/driftless_photometry/gui/assets/driftless-photometry.png
81
+ build/windows/driftless-photometry.ico
82
+ - name: Freeze the self-contained desktop application
83
+ run: python -m PyInstaller --noconfirm --clean packaging/windows/DriftlessBundlePhotometry.spec
84
+ - name: Smoke-test the frozen application
85
+ shell: pwsh
86
+ run: |
87
+ $desktop = Start-Process `
88
+ -FilePath "dist/DriftlessBundlePhotometry/DriftlessBundlePhotometry.exe" `
89
+ -ArgumentList "--desktop-smoke" `
90
+ -Wait -PassThru
91
+ if ($desktop.ExitCode -ne 0) {
92
+ throw "Frozen desktop smoke exited with code $($desktop.ExitCode)"
93
+ }
94
+ $outputDir = Join-Path $env:RUNNER_TEMP "dbf-frozen-acquisition"
95
+ $headless = Start-Process `
96
+ -FilePath "dist/DriftlessBundlePhotometry/DriftlessBundlePhotometry.exe" `
97
+ -ArgumentList "--headless", "--duration", "0.04", "--fibers", "1", "--output", $outputDir `
98
+ -Wait -PassThru
99
+ if ($headless.ExitCode -ne 0) {
100
+ throw "Frozen acquisition smoke exited with code $($headless.ExitCode)"
101
+ }
102
+ if ((Get-ChildItem $outputDir -Filter "*.nwb").Count -ne 1) {
103
+ throw "Frozen acquisition smoke did not create exactly one NWB file"
104
+ }
105
+ - name: Compile the per-user installer
106
+ shell: pwsh
107
+ run: |
108
+ $version = $env:RELEASE_TAG.TrimStart("v")
109
+ $compiler = Join-Path ${env:ProgramFiles(x86)} "Inno Setup 6/ISCC.exe"
110
+ & $compiler `
111
+ "/DAppVersion=$version" `
112
+ "/DSourceDir=$((Resolve-Path 'dist/DriftlessBundlePhotometry').Path)" `
113
+ "/DOutputDir=$((New-Item -ItemType Directory -Force 'release-assets').FullName)" `
114
+ "/DIconFile=$((Resolve-Path 'build/windows/driftless-photometry.ico').Path)" `
115
+ "packaging/windows/DriftlessBundlePhotometry.iss"
116
+ if ($LASTEXITCODE -ne 0) { throw "Inno Setup failed with code $LASTEXITCODE" }
117
+ - name: Test silent install, launch, and uninstall
118
+ shell: pwsh
119
+ run: |
120
+ $installer = Get-Item "release-assets/*.exe"
121
+ $installDir = Join-Path $env:RUNNER_TEMP "dbf-installer-smoke"
122
+ $install = Start-Process $installer.FullName `
123
+ -ArgumentList "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/DIR=$installDir" `
124
+ -Wait -PassThru
125
+ if ($install.ExitCode -ne 0) { throw "Installer exited with code $($install.ExitCode)" }
126
+ $app = Start-Process (Join-Path $installDir "DriftlessBundlePhotometry.exe") `
127
+ -ArgumentList "--desktop-smoke" -Wait -PassThru
128
+ if ($app.ExitCode -ne 0) { throw "Installed application exited with code $($app.ExitCode)" }
129
+ $uninstall = Start-Process (Join-Path $installDir "unins000.exe") `
130
+ -ArgumentList "/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART" `
131
+ -Wait -PassThru
132
+ if ($uninstall.ExitCode -ne 0) { throw "Uninstaller exited with code $($uninstall.ExitCode)" }
133
+ - name: Upload Windows installer
134
+ uses: actions/upload-artifact@v7
135
+ with:
136
+ name: windows-installer
137
+ path: release-assets/*.exe
138
+ if-no-files-found: error
139
+
140
+ pypi:
141
+ name: Publish to PyPI
142
+ needs: [python-distributions, windows-installer]
143
+ runs-on: ubuntu-latest
144
+ environment:
145
+ name: pypi
146
+ url: https://pypi.org/p/driftless-bundle-photometry
147
+ permissions:
148
+ id-token: write
149
+ steps:
150
+ - uses: actions/download-artifact@v8
151
+ with:
152
+ name: python-distributions
153
+ path: dist
154
+ - name: Publish the verified distributions to PyPI
155
+ uses: pypa/gh-action-pypi-publish@v1.14.2
156
+
157
+ github-assets:
158
+ name: Attach synchronized release assets
159
+ needs: pypi
160
+ runs-on: ubuntu-latest
161
+ permissions:
162
+ contents: write
163
+ id-token: write
164
+ attestations: write
165
+ steps:
166
+ - uses: actions/checkout@v7
167
+ with:
168
+ ref: ${{ env.RELEASE_TAG }}
169
+ - uses: actions/download-artifact@v8
170
+ with:
171
+ path: release-assets
172
+ merge-multiple: true
173
+ - name: Generate SHA-256 manifest
174
+ shell: bash
175
+ run: >-
176
+ python tools/release.py checksums
177
+ release-assets/SHA256SUMS.txt
178
+ release-assets/*.whl
179
+ release-assets/*.tar.gz
180
+ release-assets/*.exe
181
+ - name: Attest release artifacts
182
+ uses: actions/attest-build-provenance@v4.2.2
183
+ with:
184
+ subject-path: |
185
+ release-assets/*.whl
186
+ release-assets/*.tar.gz
187
+ release-assets/*.exe
188
+ release-assets/SHA256SUMS.txt
189
+ - name: Upload assets to the matching GitHub release
190
+ env:
191
+ GH_TOKEN: ${{ github.token }}
192
+ run: gh release upload "$RELEASE_TAG" release-assets/* --clobber
@@ -0,0 +1,19 @@
1
+ .DS_Store
2
+ .venv/
3
+ __pycache__/
4
+ *.py[cod]
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .coverage
8
+ htmlcov/
9
+ build/
10
+ dist/
11
+ *.egg-info/
12
+ *.nwb
13
+ *.partial.nwb
14
+ *.photometry-spool/
15
+ *.rwd-spool/
16
+ *.rwd-wire
17
+ *.rwd-wire.partial
18
+ demo-output/
19
+ old mescoscope/averages.dat
@@ -0,0 +1,185 @@
1
+ # Driftless Bundle Photometry — Agent Contract
2
+
3
+ `AGENTS.md` and `CLAUDE.md` must remain byte-for-byte identical. Any change to one
4
+ must be copied to the other in the same commit. The test suite enforces this.
5
+
6
+ ## Mission
7
+
8
+ Build a reliable, Python-based multichannel fiber-photometry acquisition system.
9
+ The supported hardware target is Windows. macOS must support the simulator,
10
+ replay, NWB writing/reading, analysis, and GUI demo; physical camera operation on
11
+ macOS is best-effort only.
12
+
13
+ The MATLAB applications, Arduino firmware, Bonsai files, and manual are historical
14
+ design evidence. They are not specifications to port verbatim.
15
+
16
+ ## Scientific and product invariants
17
+
18
+ - The primary workflow is photometry traces, not an imaging dashboard.
19
+ - Camera imagery is used for calibration, focus, ROI placement, and diagnostics.
20
+ - Support 1–9 circular fiber ROIs with stable IDs, labels, positions, and radii.
21
+ Each ROI maps to one animal/subject and one independently valid NWB file.
22
+ - Support up to three configured excitation wavelengths: 405, 470, and 565 nm.
23
+ - Normal acquisition interleaves exactly one excitation wavelength per exposure.
24
+ Do not combine wavelengths in one monochrome exposure unless a future hardware
25
+ design provides a validated unmixing mechanism.
26
+ - The total camera frame rate is shared across enabled wavelengths.
27
+ - Raw camera frames are optional. When retained, preserve original `uint16` values;
28
+ never normalize them per frame.
29
+ - Preserve one original `uint16` camera reference for every wavelength observed,
30
+ even when full raw-frame retention is disabled. ROI-annotated display copies are
31
+ derived diagnostic images with fixed bit-depth scaling and never replace raw data.
32
+ - Support four behavior TTL inputs and retain both rising and falling edges.
33
+ - Never infer wavelength solely from frame parity/modulo. Persist an explicit
34
+ controller sequence and excitation identity for every camera exposure.
35
+ - Preserve raw data. Corrections, fitted controls, dF/F, z-scores, and QC outputs
36
+ are separate derived datasets.
37
+ - Live Absolute/dF/F views and per-wavelength visibility are presentation state.
38
+ Calculate dF/F baselines independently per ROI and wavelength, keep their window
39
+ explicit, and never overwrite raw samples with a display transformation.
40
+ - Never silently drop data. Queue pressure, dropped frames, malformed device
41
+ messages, clock discontinuities, and writer failures must be observable.
42
+
43
+ ## Timing and controller contract
44
+
45
+ The hardware controller is the deterministic timing authority. The intended design
46
+ uses one clock for camera trigger/exposure records, excitation gates, and all TTL
47
+ edges. Each exposure record includes a monotonic sequence number, wavelength,
48
+ controller tick, and commanded intensity.
49
+
50
+ The GUI must not directly bit-bang LEDs or use host scheduling for exposure-critical
51
+ timing. The legacy Arduino Uno and firmware may be reused only after electrical and
52
+ timing characterization. Prefer true DAC outputs (or a validated external DAC) for
53
+ 0–5 V intensity commands and separate digital LED gates.
54
+
55
+ Every hardware command protocol must be versioned and include explicit HELLO/READY,
56
+ configuration acknowledgement, ARM/START/STOP, error reporting, and safe LED-off
57
+ behavior on disconnect or watchdog timeout.
58
+
59
+ ## Canonical data contract
60
+
61
+ The final scientific artifacts are one self-contained NWB-HDF5 file per configured
62
+ ROI/animal for each acquisition session. Temporary recovery journals/spools may be
63
+ used during acquisition, but they are not the canonical deliverables. Shared timing,
64
+ TTL, exposure, system-event, calibration, and optional raw-frame records are copied
65
+ into every ROI file so each file is independently interpretable.
66
+
67
+ Use a hybrid of core NWB and `ndx-fiber-photometry`:
68
+
69
+ - `NWBFile`, per-ROI `Subject`, `DeviceModel`, and `Device`: subject, session, and
70
+ hardware provenance.
71
+ - `ndx-fiber-photometry`/`ndx-ophys-devices`: fibers, indicators, excitation
72
+ sources, photodetector, filters, commanded voltage, and per-wavelength response
73
+ series.
74
+ - One raw `FiberPhotometryResponseSeries` per enabled wavelength in each file,
75
+ shaped `[time, 1]`, with explicit timestamps.
76
+ - Core ROI table, calibration image, and per-wavelength camera references: the
77
+ authoritative circular camera-space ROI represented by that file, plus derived
78
+ fixed-scale RGB views with the ROI outlined.
79
+ - Core `ImageSeries`: optional embedded lossless chronological `uint16` frames.
80
+ - Core `EventsTable`: TTL edges, camera exposures, excitation events, dropped frames,
81
+ and system events.
82
+ - `invalid_times`/`TimeIntervals`: bad continuous spans.
83
+ - Processing modules: immutable derived traces and QC.
84
+ - A versioned complete settings snapshot: session metadata, camera/controller and
85
+ channel settings, display preferences, and all configured ROIs. Embed the snapshot
86
+ in every per-ROI file so any one current-format NWB can restore the whole setup;
87
+ legacy imports must identify fields that were not historically persisted.
88
+
89
+ Use NWB seconds on a shared session time base and preserve raw camera frame IDs and
90
+ controller ticks for forensic reconstruction. Use stable `fiber_id` values to map
91
+ camera ROIs to fiber-photometry table rows.
92
+
93
+ Write every output to a partial path, drain and close all writers, validate, reopen
94
+ and verify counts, then atomically promote each partial only after the complete ROI
95
+ file set passes validation. Never label an unvalidated partial file as complete.
96
+
97
+ ## Architecture boundaries
98
+
99
+ - `config`: validated immutable session/camera/channel/ROI/TTL configuration.
100
+ - `domain`: typed frames, events, samples, and result records.
101
+ - `timing`: wavelength schedules, tick unwrapping, and clock mapping.
102
+ - `roi`: circular masks, hardware crop calculation, and signal extraction.
103
+ - `hardware.camera`: camera interface; simulator and Thorlabs implementations.
104
+ - `hardware.controller`: controller interface, protocol, and serial implementation.
105
+ - `acquisition`: explicit state machine and coordination; no GUI dependencies.
106
+ - `storage`: append-safe spool/recovery and NWB finalization/validation.
107
+ - `gui`: PySide6/pyqtgraph presentation; communicates with workers through signals.
108
+
109
+ Device-owning objects have one owner thread. HDF5/NWB writes have one writer owner.
110
+ Use bounded queues. The GUI thread must never poll hardware, write frames, or block on
111
+ acquisition loops.
112
+ Live plots may window and aggregate for display, but this must be explicitly
113
+ display-only. Point count must remain bounded by screen resolution rather than session
114
+ duration, brief extrema must remain visible, and acquired or stored samples must never
115
+ be discarded by a rendering optimization.
116
+
117
+ State transitions are explicit:
118
+
119
+ `DISCONNECTED -> READY -> CALIBRATING -> ARMED -> RECORDING -> DRAINING -> READY`
120
+
121
+ `ERROR` is reachable from every active state, with deterministic cleanup and LEDs
122
+ off. Invalid transitions raise errors rather than being ignored.
123
+
124
+ ## Development gates
125
+
126
+ Work in order and do not advance past a failing gate:
127
+
128
+ 1. Domain/config/timing/ROI unit tests.
129
+ 2. Tiny golden NWB fixture: round-trip, PyNWB validation, NWB Inspector, and exact
130
+ frame/trace/event count checks.
131
+ 3. Headless simulator and acquisition pipeline with bounded queues and recovery.
132
+ 4. Fault tests: stop, queue full, disk error, malformed controller data, dropped
133
+ frame, camera loss, and clock rollover.
134
+ 5. GUI demo backed by the same headless services.
135
+ 6. Windows camera/controller integration and bench characterization.
136
+ 7. Packaging and clean-machine smoke tests.
137
+
138
+ Hardware-dependent work must have a simulator/replay test path. Do not claim camera
139
+ or electrical validation without the physical hardware and measurements.
140
+
141
+ ## Code and repository rules
142
+
143
+ - Target Python 3.11–3.13 and use type annotations throughout.
144
+ - Prefer small pure functions and dependency-injected interfaces.
145
+ - Use Pydantic for persisted user/session configuration and dataclasses for hot-path
146
+ runtime records.
147
+ - Use NumPy arrays with explicit dtype and documented axis order `[y, x]` or
148
+ `[time, y, x]`.
149
+ - Use `pathlib.Path`; never add machine-specific absolute paths.
150
+ - Public behavior changes require tests. Bugs receive regression tests.
151
+ - Keep optional hardware imports lazy so simulator mode works without vendor SDKs.
152
+ - Before implementing or changing the Thorlabs camera adapter, read
153
+ `docs/thorlabs-camera-python-api.md`; it records the vendor API lifecycle, DBF
154
+ integration decisions, and the boundary between documented behavior and required
155
+ CS505MU bench validation.
156
+ - Do not edit generated/vendor/legacy files unless the task explicitly targets them.
157
+ - Do not commit recordings, build outputs, virtual environments, or secrets.
158
+ - Keep `WHATS_NEW.md` current for every user-visible change. Record work under
159
+ `Unreleased` as it lands, including changes that have not shipped, and move those
160
+ entries into the matching version section when a version is released.
161
+ - Use `roadmap.md` as the living development sequence. Update it when phase scope,
162
+ dependencies, status, or exit criteria change, and never mark a phase complete
163
+ without the evidence required by its exit criteria.
164
+ - Use `ruff` for lint/format and `pytest` for tests.
165
+ - Keep dependency versions bounded and test the exact NWB/PyNWB/NDX combination.
166
+
167
+ ## Standard commands
168
+
169
+ ```bash
170
+ python -m venv .venv
171
+ .venv/bin/python -m pip install -e '.[dev]'
172
+ .venv/bin/python -m pytest
173
+ .venv/bin/python -m ruff check .
174
+ .venv/bin/python -m ruff format --check .
175
+ .venv/bin/python -m driftless_photometry --demo
176
+ ```
177
+
178
+ On Windows, replace `.venv/bin/python` with `.venv\Scripts\python.exe`.
179
+
180
+ ## Definition of done
181
+
182
+ A step is complete only when its automated tests pass, relevant validation succeeds,
183
+ failure behavior is surfaced, documentation reflects the behavior, and no unrelated
184
+ user files were changed. A simulator passing is not proof that physical hardware has
185
+ been validated.
@@ -0,0 +1,185 @@
1
+ # Driftless Bundle Photometry — Agent Contract
2
+
3
+ `AGENTS.md` and `CLAUDE.md` must remain byte-for-byte identical. Any change to one
4
+ must be copied to the other in the same commit. The test suite enforces this.
5
+
6
+ ## Mission
7
+
8
+ Build a reliable, Python-based multichannel fiber-photometry acquisition system.
9
+ The supported hardware target is Windows. macOS must support the simulator,
10
+ replay, NWB writing/reading, analysis, and GUI demo; physical camera operation on
11
+ macOS is best-effort only.
12
+
13
+ The MATLAB applications, Arduino firmware, Bonsai files, and manual are historical
14
+ design evidence. They are not specifications to port verbatim.
15
+
16
+ ## Scientific and product invariants
17
+
18
+ - The primary workflow is photometry traces, not an imaging dashboard.
19
+ - Camera imagery is used for calibration, focus, ROI placement, and diagnostics.
20
+ - Support 1–9 circular fiber ROIs with stable IDs, labels, positions, and radii.
21
+ Each ROI maps to one animal/subject and one independently valid NWB file.
22
+ - Support up to three configured excitation wavelengths: 405, 470, and 565 nm.
23
+ - Normal acquisition interleaves exactly one excitation wavelength per exposure.
24
+ Do not combine wavelengths in one monochrome exposure unless a future hardware
25
+ design provides a validated unmixing mechanism.
26
+ - The total camera frame rate is shared across enabled wavelengths.
27
+ - Raw camera frames are optional. When retained, preserve original `uint16` values;
28
+ never normalize them per frame.
29
+ - Preserve one original `uint16` camera reference for every wavelength observed,
30
+ even when full raw-frame retention is disabled. ROI-annotated display copies are
31
+ derived diagnostic images with fixed bit-depth scaling and never replace raw data.
32
+ - Support four behavior TTL inputs and retain both rising and falling edges.
33
+ - Never infer wavelength solely from frame parity/modulo. Persist an explicit
34
+ controller sequence and excitation identity for every camera exposure.
35
+ - Preserve raw data. Corrections, fitted controls, dF/F, z-scores, and QC outputs
36
+ are separate derived datasets.
37
+ - Live Absolute/dF/F views and per-wavelength visibility are presentation state.
38
+ Calculate dF/F baselines independently per ROI and wavelength, keep their window
39
+ explicit, and never overwrite raw samples with a display transformation.
40
+ - Never silently drop data. Queue pressure, dropped frames, malformed device
41
+ messages, clock discontinuities, and writer failures must be observable.
42
+
43
+ ## Timing and controller contract
44
+
45
+ The hardware controller is the deterministic timing authority. The intended design
46
+ uses one clock for camera trigger/exposure records, excitation gates, and all TTL
47
+ edges. Each exposure record includes a monotonic sequence number, wavelength,
48
+ controller tick, and commanded intensity.
49
+
50
+ The GUI must not directly bit-bang LEDs or use host scheduling for exposure-critical
51
+ timing. The legacy Arduino Uno and firmware may be reused only after electrical and
52
+ timing characterization. Prefer true DAC outputs (or a validated external DAC) for
53
+ 0–5 V intensity commands and separate digital LED gates.
54
+
55
+ Every hardware command protocol must be versioned and include explicit HELLO/READY,
56
+ configuration acknowledgement, ARM/START/STOP, error reporting, and safe LED-off
57
+ behavior on disconnect or watchdog timeout.
58
+
59
+ ## Canonical data contract
60
+
61
+ The final scientific artifacts are one self-contained NWB-HDF5 file per configured
62
+ ROI/animal for each acquisition session. Temporary recovery journals/spools may be
63
+ used during acquisition, but they are not the canonical deliverables. Shared timing,
64
+ TTL, exposure, system-event, calibration, and optional raw-frame records are copied
65
+ into every ROI file so each file is independently interpretable.
66
+
67
+ Use a hybrid of core NWB and `ndx-fiber-photometry`:
68
+
69
+ - `NWBFile`, per-ROI `Subject`, `DeviceModel`, and `Device`: subject, session, and
70
+ hardware provenance.
71
+ - `ndx-fiber-photometry`/`ndx-ophys-devices`: fibers, indicators, excitation
72
+ sources, photodetector, filters, commanded voltage, and per-wavelength response
73
+ series.
74
+ - One raw `FiberPhotometryResponseSeries` per enabled wavelength in each file,
75
+ shaped `[time, 1]`, with explicit timestamps.
76
+ - Core ROI table, calibration image, and per-wavelength camera references: the
77
+ authoritative circular camera-space ROI represented by that file, plus derived
78
+ fixed-scale RGB views with the ROI outlined.
79
+ - Core `ImageSeries`: optional embedded lossless chronological `uint16` frames.
80
+ - Core `EventsTable`: TTL edges, camera exposures, excitation events, dropped frames,
81
+ and system events.
82
+ - `invalid_times`/`TimeIntervals`: bad continuous spans.
83
+ - Processing modules: immutable derived traces and QC.
84
+ - A versioned complete settings snapshot: session metadata, camera/controller and
85
+ channel settings, display preferences, and all configured ROIs. Embed the snapshot
86
+ in every per-ROI file so any one current-format NWB can restore the whole setup;
87
+ legacy imports must identify fields that were not historically persisted.
88
+
89
+ Use NWB seconds on a shared session time base and preserve raw camera frame IDs and
90
+ controller ticks for forensic reconstruction. Use stable `fiber_id` values to map
91
+ camera ROIs to fiber-photometry table rows.
92
+
93
+ Write every output to a partial path, drain and close all writers, validate, reopen
94
+ and verify counts, then atomically promote each partial only after the complete ROI
95
+ file set passes validation. Never label an unvalidated partial file as complete.
96
+
97
+ ## Architecture boundaries
98
+
99
+ - `config`: validated immutable session/camera/channel/ROI/TTL configuration.
100
+ - `domain`: typed frames, events, samples, and result records.
101
+ - `timing`: wavelength schedules, tick unwrapping, and clock mapping.
102
+ - `roi`: circular masks, hardware crop calculation, and signal extraction.
103
+ - `hardware.camera`: camera interface; simulator and Thorlabs implementations.
104
+ - `hardware.controller`: controller interface, protocol, and serial implementation.
105
+ - `acquisition`: explicit state machine and coordination; no GUI dependencies.
106
+ - `storage`: append-safe spool/recovery and NWB finalization/validation.
107
+ - `gui`: PySide6/pyqtgraph presentation; communicates with workers through signals.
108
+
109
+ Device-owning objects have one owner thread. HDF5/NWB writes have one writer owner.
110
+ Use bounded queues. The GUI thread must never poll hardware, write frames, or block on
111
+ acquisition loops.
112
+ Live plots may window and aggregate for display, but this must be explicitly
113
+ display-only. Point count must remain bounded by screen resolution rather than session
114
+ duration, brief extrema must remain visible, and acquired or stored samples must never
115
+ be discarded by a rendering optimization.
116
+
117
+ State transitions are explicit:
118
+
119
+ `DISCONNECTED -> READY -> CALIBRATING -> ARMED -> RECORDING -> DRAINING -> READY`
120
+
121
+ `ERROR` is reachable from every active state, with deterministic cleanup and LEDs
122
+ off. Invalid transitions raise errors rather than being ignored.
123
+
124
+ ## Development gates
125
+
126
+ Work in order and do not advance past a failing gate:
127
+
128
+ 1. Domain/config/timing/ROI unit tests.
129
+ 2. Tiny golden NWB fixture: round-trip, PyNWB validation, NWB Inspector, and exact
130
+ frame/trace/event count checks.
131
+ 3. Headless simulator and acquisition pipeline with bounded queues and recovery.
132
+ 4. Fault tests: stop, queue full, disk error, malformed controller data, dropped
133
+ frame, camera loss, and clock rollover.
134
+ 5. GUI demo backed by the same headless services.
135
+ 6. Windows camera/controller integration and bench characterization.
136
+ 7. Packaging and clean-machine smoke tests.
137
+
138
+ Hardware-dependent work must have a simulator/replay test path. Do not claim camera
139
+ or electrical validation without the physical hardware and measurements.
140
+
141
+ ## Code and repository rules
142
+
143
+ - Target Python 3.11–3.13 and use type annotations throughout.
144
+ - Prefer small pure functions and dependency-injected interfaces.
145
+ - Use Pydantic for persisted user/session configuration and dataclasses for hot-path
146
+ runtime records.
147
+ - Use NumPy arrays with explicit dtype and documented axis order `[y, x]` or
148
+ `[time, y, x]`.
149
+ - Use `pathlib.Path`; never add machine-specific absolute paths.
150
+ - Public behavior changes require tests. Bugs receive regression tests.
151
+ - Keep optional hardware imports lazy so simulator mode works without vendor SDKs.
152
+ - Before implementing or changing the Thorlabs camera adapter, read
153
+ `docs/thorlabs-camera-python-api.md`; it records the vendor API lifecycle, DBF
154
+ integration decisions, and the boundary between documented behavior and required
155
+ CS505MU bench validation.
156
+ - Do not edit generated/vendor/legacy files unless the task explicitly targets them.
157
+ - Do not commit recordings, build outputs, virtual environments, or secrets.
158
+ - Keep `WHATS_NEW.md` current for every user-visible change. Record work under
159
+ `Unreleased` as it lands, including changes that have not shipped, and move those
160
+ entries into the matching version section when a version is released.
161
+ - Use `roadmap.md` as the living development sequence. Update it when phase scope,
162
+ dependencies, status, or exit criteria change, and never mark a phase complete
163
+ without the evidence required by its exit criteria.
164
+ - Use `ruff` for lint/format and `pytest` for tests.
165
+ - Keep dependency versions bounded and test the exact NWB/PyNWB/NDX combination.
166
+
167
+ ## Standard commands
168
+
169
+ ```bash
170
+ python -m venv .venv
171
+ .venv/bin/python -m pip install -e '.[dev]'
172
+ .venv/bin/python -m pytest
173
+ .venv/bin/python -m ruff check .
174
+ .venv/bin/python -m ruff format --check .
175
+ .venv/bin/python -m driftless_photometry --demo
176
+ ```
177
+
178
+ On Windows, replace `.venv/bin/python` with `.venv\Scripts\python.exe`.
179
+
180
+ ## Definition of done
181
+
182
+ A step is complete only when its automated tests pass, relevant validation succeeds,
183
+ failure behavior is surfaced, documentation reflects the behavior, and no unrelated
184
+ user files were changed. A simulator passing is not proof that physical hardware has
185
+ been validated.