driftlessflip 0.15.0__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 (74) hide show
  1. driftlessflip-0.15.0/LICENSE +21 -0
  2. driftlessflip-0.15.0/PKG-INFO +441 -0
  3. driftlessflip-0.15.0/README.md +409 -0
  4. driftlessflip-0.15.0/assets/branding/driftlessflip-logo.png +0 -0
  5. driftlessflip-0.15.0/pyproject.toml +60 -0
  6. driftlessflip-0.15.0/setup.cfg +4 -0
  7. driftlessflip-0.15.0/src/driftlessflip/__init__.py +5 -0
  8. driftlessflip-0.15.0/src/driftlessflip/__main__.py +3 -0
  9. driftlessflip-0.15.0/src/driftlessflip/acquisition/__init__.py +1 -0
  10. driftlessflip-0.15.0/src/driftlessflip/acquisition/looping.py +162 -0
  11. driftlessflip-0.15.0/src/driftlessflip/acquisition/preview.py +485 -0
  12. driftlessflip-0.15.0/src/driftlessflip/acquisition/runner.py +931 -0
  13. driftlessflip-0.15.0/src/driftlessflip/acquisition/tuning.py +226 -0
  14. driftlessflip-0.15.0/src/driftlessflip/cli.py +264 -0
  15. driftlessflip-0.15.0/src/driftlessflip/config.py +392 -0
  16. driftlessflip-0.15.0/src/driftlessflip/gui_entry.py +13 -0
  17. driftlessflip-0.15.0/src/driftlessflip/hardware/__init__.py +1 -0
  18. driftlessflip-0.15.0/src/driftlessflip/hardware/base.py +62 -0
  19. driftlessflip-0.15.0/src/driftlessflip/hardware/simulator.py +273 -0
  20. driftlessflip-0.15.0/src/driftlessflip/hardware/timeharp.py +550 -0
  21. driftlessflip-0.15.0/src/driftlessflip/metadata.py +343 -0
  22. driftlessflip-0.15.0/src/driftlessflip/processing/__init__.py +1 -0
  23. driftlessflip-0.15.0/src/driftlessflip/processing/aggregation.py +388 -0
  24. driftlessflip-0.15.0/src/driftlessflip/processing/analysis.py +296 -0
  25. driftlessflip-0.15.0/src/driftlessflip/processing/corrections.py +152 -0
  26. driftlessflip-0.15.0/src/driftlessflip/processing/fitting.py +494 -0
  27. driftlessflip-0.15.0/src/driftlessflip/processing/rates.py +166 -0
  28. driftlessflip-0.15.0/src/driftlessflip/processing/review.py +622 -0
  29. driftlessflip-0.15.0/src/driftlessflip/processing/t3.py +81 -0
  30. driftlessflip-0.15.0/src/driftlessflip/schemas/__init__.py +1 -0
  31. driftlessflip-0.15.0/src/driftlessflip/schemas/run_metadata.schema.json +118 -0
  32. driftlessflip-0.15.0/src/driftlessflip/storage/__init__.py +1 -0
  33. driftlessflip-0.15.0/src/driftlessflip/storage/background.py +233 -0
  34. driftlessflip-0.15.0/src/driftlessflip/storage/common.py +52 -0
  35. driftlessflip-0.15.0/src/driftlessflip/storage/csv_export.py +200 -0
  36. driftlessflip-0.15.0/src/driftlessflip/storage/legacy_histogram.py +197 -0
  37. driftlessflip-0.15.0/src/driftlessflip/storage/manifest.py +48 -0
  38. driftlessflip-0.15.0/src/driftlessflip/storage/naming.py +193 -0
  39. driftlessflip-0.15.0/src/driftlessflip/storage/nwb_export.py +309 -0
  40. driftlessflip-0.15.0/src/driftlessflip/storage/profiles.py +111 -0
  41. driftlessflip-0.15.0/src/driftlessflip/storage/raw_h5.py +371 -0
  42. driftlessflip-0.15.0/src/driftlessflip/storage/recovery.py +467 -0
  43. driftlessflip-0.15.0/src/driftlessflip/storage/settings_file.py +344 -0
  44. driftlessflip-0.15.0/src/driftlessflip/storage/verify.py +202 -0
  45. driftlessflip-0.15.0/src/driftlessflip/ui/__init__.py +1 -0
  46. driftlessflip-0.15.0/src/driftlessflip/ui/app.py +4996 -0
  47. driftlessflip-0.15.0/src/driftlessflip/ui/axes.py +201 -0
  48. driftlessflip-0.15.0/src/driftlessflip/ui/plots.py +2002 -0
  49. driftlessflip-0.15.0/src/driftlessflip.egg-info/PKG-INFO +441 -0
  50. driftlessflip-0.15.0/src/driftlessflip.egg-info/SOURCES.txt +72 -0
  51. driftlessflip-0.15.0/src/driftlessflip.egg-info/dependency_links.txt +1 -0
  52. driftlessflip-0.15.0/src/driftlessflip.egg-info/entry_points.txt +2 -0
  53. driftlessflip-0.15.0/src/driftlessflip.egg-info/requires.txt +12 -0
  54. driftlessflip-0.15.0/src/driftlessflip.egg-info/top_level.txt +1 -0
  55. driftlessflip-0.15.0/tests/test_aggregation.py +173 -0
  56. driftlessflip-0.15.0/tests/test_axes.py +72 -0
  57. driftlessflip-0.15.0/tests/test_channels.py +133 -0
  58. driftlessflip-0.15.0/tests/test_config.py +49 -0
  59. driftlessflip-0.15.0/tests/test_corrections.py +483 -0
  60. driftlessflip-0.15.0/tests/test_fitting.py +262 -0
  61. driftlessflip-0.15.0/tests/test_legacy_histogram.py +92 -0
  62. driftlessflip-0.15.0/tests/test_metadata.py +38 -0
  63. driftlessflip-0.15.0/tests/test_naming.py +81 -0
  64. driftlessflip-0.15.0/tests/test_plots.py +598 -0
  65. driftlessflip-0.15.0/tests/test_preview.py +434 -0
  66. driftlessflip-0.15.0/tests/test_recording.py +804 -0
  67. driftlessflip-0.15.0/tests/test_review_analysis.py +229 -0
  68. driftlessflip-0.15.0/tests/test_settings_file.py +374 -0
  69. driftlessflip-0.15.0/tests/test_simulator_physics.py +280 -0
  70. driftlessflip-0.15.0/tests/test_suite_hygiene.py +26 -0
  71. driftlessflip-0.15.0/tests/test_t3.py +258 -0
  72. driftlessflip-0.15.0/tests/test_timeharp_adapter.py +654 -0
  73. driftlessflip-0.15.0/tests/test_ui_states.py +3289 -0
  74. driftlessflip-0.15.0/tests/test_workflow.py +352 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohebi and Associates
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,441 @@
1
+ Metadata-Version: 2.4
2
+ Name: driftlessflip
3
+ Version: 0.15.0
4
+ Summary: Time-domain fluorescence lifetime photometry acquisition for PicoQuant TimeHARP 260
5
+ Author: Mohebi and Associates
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mohebi-n-associates/DriftlessFLIP
8
+ Project-URL: Repository, https://github.com/mohebi-n-associates/DriftlessFLIP
9
+ Project-URL: Issues, https://github.com/mohebi-n-associates/DriftlessFLIP/issues
10
+ Project-URL: Changelog, https://github.com/mohebi-n-associates/DriftlessFLIP/blob/main/WHATS_NEW.md
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Topic :: Scientific/Engineering
18
+ Requires-Python: >=3.11
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: h5py<4,>=3.11
22
+ Requires-Dist: jsonschema<5,>=4.23
23
+ Requires-Dist: numpy<3,>=2.0
24
+ Requires-Dist: pynwb<4,>=3.0
25
+ Requires-Dist: scipy<2,>=1.11
26
+ Requires-Dist: PySide6<7,>=6.7
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest<9,>=8.3; extra == "dev"
29
+ Provides-Extra: packaging
30
+ Requires-Dist: pyinstaller==6.22.0; extra == "packaging"
31
+ Dynamic: license-file
32
+
33
+ # DriftlessFLIP
34
+
35
+ A desktop application for **time-domain fluorescence lifetime photometry** with a PicoQuant
36
+ TimeHARP 260 PICO DUAL. It acquires TTTR photon streams, turns them into per-sample decay
37
+ curves, and derives intensity, mean photon emission time, and phasor coordinates — while
38
+ keeping the raw photon records so any of those numbers can be recomputed later.
39
+
40
+ It runs without hardware. A built-in simulator reproduces the board's record semantics, so
41
+ the whole workflow can be learned, tested, and demonstrated on any machine.
42
+
43
+ > **Ready for real experiments.** The acquisition path has been validated with a physical
44
+ > TimeHARP 260 across multiple bench sessions and real recordings, including a sustained
45
+ > high-volume run. The simulator and automated suite cover the same processing and storage
46
+ > path. Before collecting irreplaceable data on a new computer, board, detector, or timing
47
+ > setup, verify that exact hardware profile and follow the deployment checks in
48
+ > [`DESIGN_PLAN.md`](docs/DESIGN_PLAN.md) section 7.
49
+
50
+ ---
51
+
52
+ ## What it does
53
+
54
+ **Acquisition**
55
+
56
+ - Preview live signal without saving anything, to set discriminator levels and timing.
57
+ - Record for a fixed duration, or loop N repeats on a start-to-start interval.
58
+ - Start from software or from an external TTL trigger.
59
+ - Two detector channels, each with its own experiment-specific name such as `DA in PFC`.
60
+ - Four named TTL marker inputs, with automatic rising-to-falling pulse pairing.
61
+ - Free-text operator notes, timestamped from the moment you start typing.
62
+
63
+ **Analysis**
64
+
65
+ - Per-sample decay histograms, intensity, mean photon emission time, and first-harmonic
66
+ phasor coordinates.
67
+ - Background subtraction from a separately recorded background, scaled for detector dead
68
+ time so a busier detector is not over-corrected.
69
+ - Per-channel afterpulse correction across the complete decay curve.
70
+ - Single- and double-exponential fitting, convolved with a Gaussian instrument response
71
+ and re-excited every laser period, with per-parameter fixed/floating control.
72
+ - Phasor plot with the universal semicircle and lifetime reference points.
73
+
74
+ **Data you can trust later**
75
+
76
+ - Bit-exact raw TTTR retained, so any derived quantity can be recomputed when an
77
+ algorithm changes.
78
+ - Every recording writes raw HDF5, NWB, CSV, canonical metadata JSON, a run log, and a
79
+ checksummed manifest, all cross-verified against each other.
80
+ - An interrupted recording can be recovered, reproducing the corrections the run was
81
+ applying.
82
+ - Read-only import of supported legacy histogram recordings for comparison and analysis.
83
+
84
+ ---
85
+
86
+ ## Installation for end users
87
+
88
+ Download `DriftlessFLIP-<version>-Setup.exe` from the corresponding
89
+ [GitHub Release](https://github.com/mohebi-n-associates/DriftlessFLIP/releases), run it,
90
+ and launch **DriftlessFLIP** from the Start Menu. The installer contains the application,
91
+ Python runtime, and open-source dependencies; users do not need Conda, Python, or terminal
92
+ commands. It installs per user and does not remove or overwrite recordings, profiles, or
93
+ settings in `Documents\DriftlessFLIP` during upgrades or uninstall.
94
+
95
+ PicoQuant's driver and licensed `TH260Lib64.dll` remain separate installations and are
96
+ never bundled. For hardware acquisition, obtain TH260Lib from PicoQuant or install
97
+ PicoQuant's [snAPI](https://github.com/PicoQuant/snAPI), which supplies the DLL, and keep
98
+ the PicoQuant device driver installed. If automatic discovery does not find the DLL,
99
+ select its installed location in DriftlessFLIP's **TH260 DLL** field. Simulator mode works
100
+ without either component. Installer builds are currently unsigned, so Windows may show a
101
+ publisher warning until code signing is added.
102
+
103
+ ## Installation for developers
104
+
105
+ Use 64-bit Python 3.11 or newer in a dedicated
106
+ [Conda](https://docs.conda.io/) environment. Clone this repository, open PowerShell in
107
+ the clone, and run:
108
+
109
+ ```powershell
110
+ conda create -n driftlessflip python=3.12
111
+ conda activate driftlessflip
112
+ python -m pip install --upgrade pip
113
+ python -m pip install -e ".[dev]"
114
+ ```
115
+
116
+ This editable installation uses the files in the clone, so code changes take effect
117
+ without reinstalling. Run the tests with `python -m pytest -q`.
118
+
119
+ For a non-editable installation of the latest published Python package, use:
120
+
121
+ ```powershell
122
+ python -m pip install --upgrade driftlessflip
123
+ ```
124
+
125
+ Developers must obtain the PicoQuant driver and licensed `TH260Lib64.dll` separately too;
126
+ pip and Conda cannot install them. Install TH260Lib from PicoQuant or use the DLL supplied
127
+ with [snAPI](https://github.com/PicoQuant/snAPI). Keep it at the default path or select its
128
+ actual location in the GUI's **TH260 DLL** field. Never commit the vendor DLL to this
129
+ repository. Simulator mode works without the driver or DLL.
130
+
131
+ To create the application executable and Setup installer locally or through GitHub
132
+ Actions, follow [Building the Windows executable and installer](docs/BUILD_WINDOWS_INSTALLER.md).
133
+ The short local build is:
134
+
135
+ ```powershell
136
+ python -m pip install -e ".[dev,packaging]"
137
+ .\scripts\build_windows_installer.ps1
138
+ ```
139
+
140
+ The result is `installer-dist\DriftlessFLIP-<version>-Setup.exe`. The automation attaches
141
+ that executable and its SHA-256 checksum to every published GitHub Release. Release tags
142
+ must exactly match the application version as `v<version>`.
143
+
144
+ ---
145
+
146
+ ## Getting started
147
+
148
+ Launch the interface:
149
+
150
+ ```powershell
151
+ driftlessflip gui
152
+ ```
153
+
154
+ ### The normal workflow
155
+
156
+ 1. **Preview.** Press **Preview** and watch the count rates, live traces, and decay curve.
157
+ Nothing is saved. Adjust discriminator thresholds, channel offsets, and `t0`, then
158
+ preview again — each preview re-applies the current settings.
159
+ 2. **Calibrate `t0` against the live decay.** In **Hardware & calibration**, press **Fit
160
+ decay curve** and then **Apply fitted t0**. `t0` is live-adjustable, so the running
161
+ preview picks it up at once and MPET updates with it — no throwaway recording needed.
162
+ Settings that reprogram the board, such as channel timing offsets, still need the
163
+ preview stopped and restarted, because TH260Lib forbids changing them mid-measurement.
164
+ 3. **Check the rate.** The count-rate readout is coloured against the pile-up and detector
165
+ dead-time limits for your sync rate. If it reads over, reduce excitation power — never
166
+ raise the discriminator threshold to bring the rate down. A higher threshold discards
167
+ valid photon events rather than fixing pile-up, and costs signal-to-noise for nothing
168
+ in return; lower power also reduces photobleaching and photodamage to the sample.
169
+ A detected rate of roughly 2×10⁶–1.5×10⁷ counts/s is a reasonable operating range for
170
+ this hardware; at the low end, aim for the signal to sit at least 10× above whatever
171
+ the detector reads with no sample connected.
172
+ 4. **Record a background.** Same settings, no signal, ten seconds is usually enough. Tick
173
+ **This run is a background measurement**.
174
+ 5. **Record.** Load the background as the reference, then press **Start recording**.
175
+ 6. **Review.** Open the recording, integrate the decay over a time range, fit it, and read
176
+ the phasor position.
177
+
178
+ ### From the terminal
179
+
180
+ ```powershell
181
+ driftlessflip preview --duration 10
182
+ driftlessflip simulate --output recordings --prefix bg --duration 10 --background
183
+ driftlessflip simulate --output recordings --prefix experiment --background-reference recordings\bg_<timestamp>
184
+ driftlessflip device-info
185
+ driftlessflip verify recordings\experiment_<timestamp>
186
+ driftlessflip import-legacy path\to\legacy-recording
187
+ driftlessflip recover recordings\experiment_<timestamp>
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Key concepts
193
+
194
+ ### Preview is not a recording
195
+
196
+ Preview and recording share the device, so only one runs at a time. Preview exists for
197
+ setup: it shows count rates, traces, and the summed decay curve you set CFD levels and
198
+ `t0` from, and it is bounded by a rolling window rather than the recording duration, so it
199
+ can run indefinitely at fixed memory.
200
+
201
+ Preview **cannot** produce a recording. The module imports nothing from the storage layer,
202
+ so it cannot create a run folder, a raw file, or a manifest. While previewing, the control
203
+ reads `Previewing — not saving`.
204
+
205
+ ### What a recording contains
206
+
207
+ Each run creates one immutable folder named for its prefix and start time:
208
+
209
+ ```text
210
+ experiment_20260802T193052.123456Z/
211
+ experiment_20260802T193052.123456Z.tttr.h5 raw TTTR records and applied corrections
212
+ experiment_20260802T193052.123456Z.nwb histograms and derived traces
213
+ experiment_20260802T193052.123456Z_metrics.csv per-sample, per-channel values
214
+ experiment_20260802T193052.123456Z_metadata.json canonical RunMetadata
215
+ experiment_20260802T193052.123456Z_manifest.json sizes and SHA-256 of every artifact
216
+ experiment_20260802T193052.123456Z_run.log structured event log
217
+ ```
218
+
219
+ The raw TTTR stream is authoritative. One versioned `RunMetadata` object is embedded in
220
+ every artifact and cross-checked between them, so a package that verifies is internally
221
+ consistent. `driftlessflip verify` re-checks that at any time.
222
+
223
+ ### Settings files and profiles
224
+
225
+ Two ways to keep a configuration, for two different jobs.
226
+
227
+ A **settings profile** is a named instrument setup kept between sessions in your home
228
+ directory. It deliberately leaves out the fields that belong to one particular run — the
229
+ recording prefix, the background reference, and whether the run is a background — so
230
+ loading a routine setup cannot drag last week's background along with it.
231
+
232
+ A **settings file** is everything, written wherever you choose: hardware, analysis,
233
+ experiment metadata, the background reference, output location and prefix included. Use
234
+ **Export all…** and **Import all…** on the Acquire tab. It is indented, key-sorted JSON
235
+ that you can read, hand-edit, diff between sessions, keep alongside a protocol, or send
236
+ to a collaborator so they can reproduce your setup exactly.
237
+
238
+ Importing never repairs a file quietly. A file that is not a settings file, or that has a
239
+ key the application does not know, is refused with the offending detail named. A file
240
+ that loads but asks for something unavailable here — a background recording that has
241
+ moved, a value outside its allowed range — is applied and the problem reported, so you
242
+ can see and fix it rather than meeting it at record time.
243
+
244
+ The settings file is a setup document, not a provenance record: what a recording actually
245
+ used is written inside that recording.
246
+
247
+ ### Where settings come from at startup
248
+
249
+ Everything DriftlessFLIP keeps for you lives in one visible folder:
250
+
251
+ ```text
252
+ Documents/DriftlessFLIP/
253
+ default_settings.json loaded at every start
254
+ profiles/ named instrument setups
255
+ recordings/ where recordings go unless you point them elsewhere
256
+ ```
257
+
258
+ It is deliberately not a hidden dotfolder: these files exist to be read, hand-edited,
259
+ copied to a colleague, and handed over with a rig. Settings kept under `~/.driftlessflip`
260
+ by an earlier version are moved here automatically on first start, without overwriting
261
+ anything already present.
262
+
263
+ On Windows the folder is found through the location Explorer itself uses, so a Documents
264
+ folder redirected to OneDrive — the default on a new machine — is followed rather than
265
+ guessed at. Because that can make for a deep path, and because a recording's prefix and
266
+ timestamp appear both in its folder name and in every file inside it, DriftlessFLIP warns
267
+ before arming if a run's longest path would exceed the 260-character Windows limit. That
268
+ failure would otherwise land at finalisation, after the data had been collected.
269
+
270
+ DriftlessFLIP loads `default_settings.json` when it exists, so the application opens on
271
+ your rig rather than on factory defaults. Press **Set as default** to write the current
272
+ settings there. Having no such file is the ordinary first-run state.
273
+ If the file exists but cannot be read, the application still starts, on built-in defaults,
274
+ and says so in the status line and the Diagnostics tab — it will not fail silently and
275
+ leave you believing your own settings are in force.
276
+
277
+ Three ways to load a setup, in the order you are likely to want them:
278
+
279
+ 1. **The default file**, applied automatically at startup.
280
+ 2. **Import all…**, for a settings file someone sent you or that you keep with a protocol.
281
+ 3. **From recording…**, which rebuilds a setup from a past recording's own metadata.
282
+
283
+ The third has two forms. Next to *Settings profile* it loads a past recording's instrument
284
+ settings into the current session while keeping your output location and recording name.
285
+ Next to *Settings file* it writes that recording's configuration out as a settings file,
286
+ prefix and background reference included, so a session can be turned into a reusable
287
+ setup. The same thing without the interface:
288
+
289
+ ```powershell
290
+ driftlessflip export-settings recordings\experiment_<timestamp> --output my_rig.settings.json
291
+ ```
292
+
293
+ Settings are read from the recording's canonical metadata JSON. The NWB file carries an
294
+ identical copy of the same block, and both are checksummed in the manifest.
295
+
296
+ ### Background subtraction
297
+
298
+ Record a background with matching settings and no signal, then load it as the reference
299
+ for an experiment. It is refused unless it matches on sample rate, lifetime binning, laser
300
+ rate, sync configuration, and every per-channel discriminator and offset; the error names
301
+ the field that differs. Duration may differ, since the correction uses the mean background
302
+ per sample.
303
+
304
+ The measured background is scaled by the ratio of detector live fractions before
305
+ subtraction, because a detector busy with signal photons collects less background than it
306
+ did when measured alone. The background's identity and the correction actually applied are
307
+ stored with the recording, so it stays reproducible even if the background file is later
308
+ moved.
309
+
310
+ ### Which settings can change while acquiring
311
+
312
+ `t0`, the MPET window, and the afterpulse ratio are applied when metrics are recalculated,
313
+ so they can be retuned **during a preview** with immediate effect. Everything else is
314
+ locked while a measurement runs: TH260Lib forbids reprogramming the board mid-measurement,
315
+ and the sample rate, lifetime binning, and output location fix buffer shapes and identity
316
+ at start.
317
+
318
+ During a **recording** everything is locked, including those three, because metadata is
319
+ captured when the recording starts and a later edit would let the stored configuration
320
+ disagree with the derived outputs. Raw TTTR retention means any of them can be reapplied
321
+ offline. For the reasoning behind exactly which settings fall on which side of that line —
322
+ and how the raw T3 stream makes it possible — see [`SOFTWARE_TUTORIAL.md` section
323
+ 3](docs/SOFTWARE_TUTORIAL.md#3-raw-retention-vs-hardware-settings-what-you-can-change-after-recording).
324
+
325
+ ### External TTL markers
326
+
327
+ The PICO DUAL marker inputs are Marker 1/pin 9, Marker 2/pin 4, Marker 3/pin 5, and
328
+ Marker 4/pin 10. The acquisition trigger line is pin 13 and measurement status is output on
329
+ pin 3, with ground on pin 12 or 14.
330
+
331
+ `TH260_SetMarkerEdges` selects one active edge per input, so a single input cannot capture
332
+ both polarities. To measure pulse width, split the source to two inputs, set one rising and
333
+ one falling, and give both the same **Pulse signal** name. Each rising edge is then matched
334
+ to the next falling edge automatically.
335
+
336
+ The exact event table retains macro-sync count, recording-relative time, derived UTC
337
+ timestamp, edge, input name and pin, source bitmask, and sample index. The per-sample
338
+ `external_marker_bits` trace is only a summary — use the event table when timing or event
339
+ multiplicity matters.
340
+
341
+ ---
342
+
343
+ ## Interpreting the numbers
344
+
345
+ **MPET is not a fitted lifetime.** It is the photon-weighted mean arrival time within the
346
+ configured analysis window, minus `t0`. Truncation by the laser period and the window
347
+ shifts it below the exponential time constant, so the interface labels it *mean photon
348
+ emission time* rather than lifetime. Use the fit when you want `tau`.
349
+
350
+ **Microtime is nominal `dtime * resolution`**, the bin's left edge, matching the TimeHARP
351
+ convention. Bin centres exist only as a display axis. This means a calibrated `t0` has to
352
+ absorb the offset between a bin's left edge and the mean arrival time within it, which is
353
+ why `t0` is determined by fitting rather than assumed.
354
+
355
+ **Phasor spans one full laser period**, independent of the MPET window. A cropped window
356
+ rotates and shortens the vector and moves even a pure single-exponential decay off the
357
+ universal semicircle.
358
+
359
+ **Corrections happen before windowing.** Background and afterpulsing are removed across the
360
+ complete decay curve, so changing the MPET window cannot change how much was subtracted.
361
+
362
+ Every one of these choices is recorded in the `calculation_contract` and
363
+ `algorithm_versions` fields of each recording, so a file states the conventions it was
364
+ computed under.
365
+
366
+ ---
367
+
368
+ ## Current limitations
369
+
370
+ - Physical TimeHARP operation is validated and ready for experiments. A new acquisition
371
+ computer, board, detector, or timing configuration still needs its own profile check;
372
+ the supplied bench procedures make that verification reproducible.
373
+ - **The board can enter an unresponsive state that neither DriftlessFLIP nor the vendor's
374
+ own software can currently distinguish from "not connected."** No software fix is known.
375
+ If `device-info` or a board scan fails immediately after working moments earlier: close
376
+ every process that might hold the device, and if it still won't open, power-cycle the
377
+ computer (unplugging power for ~15 s before restart has been reported to help; this is
378
+ a board/driver-level condition, not something DriftlessFLIP causes or can detect from
379
+ software alone).
380
+ - Histogram memory is bounded before arming but allocated for the whole requested run, so
381
+ a memory budget caps run length rather than removing the limit.
382
+ - The software FIFO handoff queue defaults to 256 chunks (at most 128 MiB) and is
383
+ configurable from Acquire. A sustained full queue or a hardware FIFO-full flag stops
384
+ acquisition and preserves a verified truncated prefix. Increasing the queue absorbs
385
+ temporary storage/CPU stalls; it cannot reconstruct records the board already lost or
386
+ compensate indefinitely when processing is slower than acquisition.
387
+ - NWB uses valid core types plus lossless canonical metadata; formal fibre-photometry
388
+ extension mappings are not done.
389
+ - Unsupported legacy formats are refused rather than guessed.
390
+ - The simulator models a Gaussian instrument response, periodic re-excitation,
391
+ afterpulsing, dead time, and structured background, but not measured per-instrument IRF
392
+ shape or timing jitter.
393
+
394
+ [`ROADMAP.md`](docs/ROADMAP.md) lists what remains and why.
395
+
396
+ ---
397
+
398
+ ## Development
399
+
400
+ ```powershell
401
+ pytest
402
+ ```
403
+
404
+ The suite covers T3 decoding against a hand-calculated golden stream, correction arithmetic,
405
+ phasor geometry, fit parameter recovery, storage verification, recovery, overrun salvage,
406
+ looping, and cross-validation against independent golden fixtures. GitHub Actions
407
+ runs it on Python 3.11 and 3.12, on Ubuntu and Windows.
408
+
409
+ ---
410
+
411
+ ## Documents
412
+
413
+ | File | What it covers |
414
+ |---|---|
415
+ | [`docs/FAQ.md`](docs/FAQ.md) | Common questions on theory, setup, and calibration, answered in one place |
416
+ | [`educational/`](educational/README.md) | Background notes: what the measurement is, how to run an experiment, how to calibrate the instrument, how to compare lifetimes across sessions, and how it compares with frequency-domain methods |
417
+ | [`docs/SOFTWARE_TUTORIAL.md`](docs/SOFTWARE_TUTORIAL.md) | Practical workflow for running an experiment, plus a button-by-button tour of every tab |
418
+ | [`docs/OUTPUT_DATA_STRUCTURE.md`](docs/OUTPUT_DATA_STRUCTURE.md) | What each file in a recording folder contains, with real example data |
419
+ | [`WHATS_NEW.md`](WHATS_NEW.md) | Dated release history and the reasoning behind each change |
420
+ | [`docs/ROADMAP.md`](docs/ROADMAP.md) | What remains, and the constraints on future work |
421
+ | [`docs/DESIGN_PLAN.md`](docs/DESIGN_PLAN.md) | Measurement model, hardware constraints, and deployment checks |
422
+ | [`docs/SOFTWARE_ARCHITECTURE.md`](docs/SOFTWARE_ARCHITECTURE.md) | How the software is built |
423
+ | [`docs/BUILD_WINDOWS_INSTALLER.md`](docs/BUILD_WINDOWS_INSTALLER.md) | How maintainers build and automatically attach the Windows installer to releases |
424
+ | [`docs/SYNC_TIMEBASE_BENCH_PROCEDURE.md`](docs/SYNC_TIMEBASE_BENCH_PROCEDURE.md) | Bench procedure for the sync time base |
425
+ | [`docs/CFD_THRESHOLD_BENCH_PROCEDURE.md`](docs/CFD_THRESHOLD_BENCH_PROCEDURE.md) | Bench procedure for verifying CFD threshold/zero-cross against the real pulse |
426
+ | [`docs/INDEPENDENT_AUDIT.md`](docs/INDEPENDENT_AUDIT.md) | Current independent audit: latest assessment and open findings |
427
+ | [`docs/ARCHIVED_AUDIT.md`](docs/ARCHIVED_AUDIT.md) | Resolved audit findings, dated by fix commit and later verification |
428
+ | [`docs/HARDWARE_API_COMPARISON.md`](docs/HARDWARE_API_COMPARISON.md) | Why the hardware adapter uses low-level TH260Lib calls instead of PicoQuant's snAPI |
429
+
430
+ ---
431
+
432
+ ## Development, inspiration, and license
433
+
434
+ DriftlessFLIP is developed by **Mohebi and Associates**.
435
+
436
+ The software was inspired by **iFLIP2 from the Hong Lab**.
437
+
438
+ Released under the [MIT License](LICENSE). PicoQuant drivers and `TH260Lib64.dll` are
439
+ separately licensed third-party components and are not distributed under it. Third-party
440
+ manuals and journal articles are cited by link in [`DESIGN_PLAN.md`](docs/DESIGN_PLAN.md), are
441
+ not stored in this repository, and are outside the scope of the MIT license.