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.
- driftless_bundle_photometry-0.2.1/.github/workflows/ci.yml +83 -0
- driftless_bundle_photometry-0.2.1/.github/workflows/release.yml +192 -0
- driftless_bundle_photometry-0.2.1/.gitignore +19 -0
- driftless_bundle_photometry-0.2.1/AGENTS.md +185 -0
- driftless_bundle_photometry-0.2.1/CLAUDE.md +185 -0
- driftless_bundle_photometry-0.2.1/LICENSE +21 -0
- driftless_bundle_photometry-0.2.1/PKG-INFO +305 -0
- driftless_bundle_photometry-0.2.1/WHATS_NEW.md +265 -0
- driftless_bundle_photometry-0.2.1/docs/architecture.md +244 -0
- driftless_bundle_photometry-0.2.1/docs/controller-protocol.md +33 -0
- driftless_bundle_photometry-0.2.1/docs/releases/0.2.0.md +56 -0
- driftless_bundle_photometry-0.2.1/docs/releases/0.2.1.md +45 -0
- driftless_bundle_photometry-0.2.1/docs/releasing.md +67 -0
- driftless_bundle_photometry-0.2.1/docs/rwd-operator-guide.md +145 -0
- driftless_bundle_photometry-0.2.1/docs/rwd-streaming-protocol.md +241 -0
- driftless_bundle_photometry-0.2.1/docs/thorlabs-camera-python-api.md +356 -0
- driftless_bundle_photometry-0.2.1/environment.yml +8 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/__init__.py +0 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/config.py +8 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/functions.py +232 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/plotRawAverageTraces.py +103 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Analysis/stimResponse.py +64 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Arduino Codes/cameraControl/cameraControl.ino +187 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Arduino Codes/cameraControl.ino.hex +311 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/SoftwareTrigger.m +111 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/SoftwareTrigger_video.m +89 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/concatCamera_acquisition.py +98 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/concatCameras.bonsai +136 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/concatCameras.bonsai.layout +273 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/mesoscope_RCS_with_preview.mlapp +0 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/mesoscope_arduinoControl.mlapp +0 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/mesoscope_arduinoControl_RCS.mlapp +0 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/mesoscope_control_RCS_Batch.mlapp +0 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/test.m +2 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/test_background.m +17 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/test_externalFigurePanels.mlapp +0 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/test_writing_webcam.m +63 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/writeFrameBatch.asv +12 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/writeFrameBatch.m +12 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/Legacy/writeSingleFrame.m +32 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/README.md +3 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/helper functions/updateCameraLogFromArduino.m +24 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/helper functions/writeBatchFrames_binary.m +15 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/helper functions/writeSingleFrame_binary.m +14 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/mesoscope_control.mlapp +0 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/mesoscope_control_crop.mlapp +0 -0
- driftless_bundle_photometry-0.2.1/old mescoscope/plot_frames.m +36 -0
- driftless_bundle_photometry-0.2.1/packaging/windows/DriftlessBundlePhotometry.iss +62 -0
- driftless_bundle_photometry-0.2.1/packaging/windows/DriftlessBundlePhotometry.spec +87 -0
- driftless_bundle_photometry-0.2.1/pyproject.toml +83 -0
- driftless_bundle_photometry-0.2.1/readme.md +256 -0
- driftless_bundle_photometry-0.2.1/roadmap.md +700 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/__init__.py +10 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/__main__.py +3 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/acquisition.py +408 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/cli.py +309 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/config.py +384 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/desktop.py +36 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/diagnostics.py +49 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/domain.py +143 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/faults.py +27 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/__init__.py +5 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/app.py +34 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/assets/__init__.py +1 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/assets/driftless-photometry.png +0 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/branding.py +14 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/main_window.py +2208 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/plot_interaction.py +94 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/trace_display.py +118 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/gui/worker.py +107 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/__init__.py +41 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/base.py +23 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/controller_protocol.py +174 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/controller_serial.py +124 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/simulator.py +144 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/hardware/thorlabs.py +56 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/live_trace.py +96 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/provenance.py +84 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/roi.py +119 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/__init__.py +77 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/acquisition.py +244 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/capture.py +477 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/client.py +286 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/domain.py +142 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/protocol.py +301 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/rwd/timing.py +266 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/settings.py +382 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/state.py +55 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/__init__.py +82 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/capacity.py +155 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/frames.py +32 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/nwb.py +1162 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/recovery.py +125 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/rwd_nwb.py +772 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/rwd_recovery.py +117 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/rwd_spool.py +591 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/storage/spool.py +659 -0
- driftless_bundle_photometry-0.2.1/src/driftless_photometry/timing.py +78 -0
- driftless_bundle_photometry-0.2.1/tests/conftest.py +12 -0
- driftless_bundle_photometry-0.2.1/tests/fixtures/rwd/README.md +14 -0
- driftless_bundle_photometry-0.2.1/tests/fixtures/rwd/event_on.hex +1 -0
- driftless_bundle_photometry-0.2.1/tests/fixtures/rwd/fluorescence_all.hex +1 -0
- driftless_bundle_photometry-0.2.1/tests/fixtures/rwd/mixed_with_banner.hex +1 -0
- driftless_bundle_photometry-0.2.1/tests/test_acquisition.py +402 -0
- driftless_bundle_photometry-0.2.1/tests/test_agent_contract.py +6 -0
- driftless_bundle_photometry-0.2.1/tests/test_capacity.py +130 -0
- driftless_bundle_photometry-0.2.1/tests/test_cli.py +124 -0
- driftless_bundle_photometry-0.2.1/tests/test_config.py +187 -0
- driftless_bundle_photometry-0.2.1/tests/test_controller_protocol.py +73 -0
- driftless_bundle_photometry-0.2.1/tests/test_controller_serial.py +78 -0
- driftless_bundle_photometry-0.2.1/tests/test_desktop.py +29 -0
- driftless_bundle_photometry-0.2.1/tests/test_domain.py +23 -0
- driftless_bundle_photometry-0.2.1/tests/test_gui.py +585 -0
- driftless_bundle_photometry-0.2.1/tests/test_live_trace.py +89 -0
- driftless_bundle_photometry-0.2.1/tests/test_nwb.py +270 -0
- driftless_bundle_photometry-0.2.1/tests/test_plot_interaction.py +140 -0
- driftless_bundle_photometry-0.2.1/tests/test_recovery.py +188 -0
- driftless_bundle_photometry-0.2.1/tests/test_release.py +66 -0
- driftless_bundle_photometry-0.2.1/tests/test_roi.py +97 -0
- driftless_bundle_photometry-0.2.1/tests/test_rwd_capture.py +136 -0
- driftless_bundle_photometry-0.2.1/tests/test_rwd_client.py +433 -0
- driftless_bundle_photometry-0.2.1/tests/test_rwd_domain.py +52 -0
- driftless_bundle_photometry-0.2.1/tests/test_rwd_protocol.py +265 -0
- driftless_bundle_photometry-0.2.1/tests/test_rwd_storage.py +471 -0
- driftless_bundle_photometry-0.2.1/tests/test_settings.py +117 -0
- driftless_bundle_photometry-0.2.1/tests/test_simulator.py +31 -0
- driftless_bundle_photometry-0.2.1/tests/test_spool.py +293 -0
- driftless_bundle_photometry-0.2.1/tests/test_state.py +23 -0
- driftless_bundle_photometry-0.2.1/tests/test_timing.py +37 -0
- driftless_bundle_photometry-0.2.1/tests/test_trace_display.py +114 -0
- driftless_bundle_photometry-0.2.1/tools/__init__.py +1 -0
- driftless_bundle_photometry-0.2.1/tools/make_windows_icon.py +31 -0
- driftless_bundle_photometry-0.2.1/tools/release.py +93 -0
- 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.
|