You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add configurable system-delay timestamp compensation; document it
Subtract a configurable system/pipeline latency (systemDelayMs, default 4.5 ms)
from every pushed LSL timestamp -- EEG, Physio16, and DIN alike -- to compensate
for the time between digitization and the sample being available to time-stamp
in this client (device firmware + network transmission + read path). Measured
~5 ms in native mode via the audio latency test; the default deliberately
under-compensates so a sample is never back-dated before its event. Applied
unconditionally at the point the batch timestamp is captured, on top of the
decimated-only FPGA filter offset.
Document the full timestamp-compensation behaviour in the README (system delay,
FPGA filter offset, Physio16 realignment), replacing the stale --align-timestamps
section and correcting the filter-delay table to the measured 111/61/36 ms.
Also gitignore the audio-latency-test recordings and Python caches.
Copy file name to clipboardExpand all lines: README.md
+44-26Lines changed: 44 additions & 26 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -35,7 +35,6 @@ The CLI provides a lightweight alternative to the GUI:
35
35
-`--amp-id <id>` - Amplifier ID (default: 0)
36
36
-`--sample-rate <hz>` - Sample rate in Hz (default: 1000). Forces amplifier to this rate if already running at a different rate. Valid rates: 250, 500, 1000 (decimated) or 500, 1000, 2000, 4000, 8000 (native).
37
37
-`--fast-recovery` - Use native rate mode (no FPGA anti-alias filter) for lower latency. See [Sample Rate Modes](#sample-rate-modes).
38
-
-`--align-timestamps` - Adjust timestamps to compensate for anti-alias filter delay. See [Timestamp Alignment](#timestamp-alignment).
39
38
-`--impedance` - Enable impedance testing mode
40
39
-`--native-format` - Transmit raw int32 ADC counts instead of float microvolts
41
40
-`--shutdown` - Shutdown the Amp Server (terminates all connections)
@@ -52,11 +51,11 @@ The CLI provides a lightweight alternative to the GUI:
@@ -78,9 +77,8 @@ This is safe to use alongside Net Station — it will not stop or reconfigure th
78
77
79
78
The following flags cause the CLI to stop, reconfigure, and restart the amplifier — even if it was started by another application:
80
79
81
-
-`--sample-rate <hz>` — reinitializes if the detected rate differs from the requested rate
80
+
-`--sample-rate <hz>` — reinitializes if the detected rate differs from the requested rate. At 500/1000 Hz, where native and decimated are indistinguishable in the data stream, requesting a decimated rate also forces reinitialization to guarantee decimated mode (so the automatic [timestamp compensation](#timestamp-compensation) is correct)
82
81
-`--fast-recovery` — reinitializes to ensure native (unfiltered) mode
83
-
-`--align-timestamps` — reinitializes at 500/1000 Hz to ensure decimated (filtered) mode, since the operating mode cannot be distinguished from the data stream alone
84
82
85
83
**Warning**: Reinitialization will interrupt any active Net Station recording. If you need to coexist with Net Station, omit these flags and let the CLI match the existing configuration.
86
84
@@ -100,14 +98,14 @@ The NA400/NA410 amplifiers support two operating modes that affect anti-aliasing
100
98
101
99
Uses the FPGA's digital anti-aliasing filter to downsample from the ADC's native rate. This provides:
102
100
- Better frequency response (~400 Hz bandwidth at 1000 Hz sample rate)
103
-
- Higher latency due to filter delay (36-112 samples depending on rate)
101
+
- Higher latency due to the filter group delay (36-111 ms depending on rate), which the app compensates for automatically — see [Timestamp Compensation](#timestamp-compensation)
104
102
105
103
Available decimated rates: 250, 500, 1000 Hz
106
104
107
105
### Native Mode (Fast Recovery)
108
106
109
107
Bypasses the FPGA filter and samples directly at the requested rate. This provides:
110
-
- Lower latency (~3 samples)
108
+
- Lower latency (no filter group delay)
111
109
- Reduced bandwidth (~1/4 of sample rate, e.g., 250 Hz at 1000 Hz sample rate)
112
110
- Optimized for EEG-TMS and real-time BCI applications
113
111
@@ -117,32 +115,52 @@ Use `--fast-recovery` to enable native mode for rates that support both modes (5
When using decimated mode, the FPGA anti-aliasing filter introduces a delay between when brain activity occurs and when it appears in the data stream. The `--align-timestamps` option compensates for this by adjusting LSL timestamps backward by the filter delay amount.
127
+
## Timestamp Compensation
130
128
131
-
### When to Use
129
+
The application adjusts the LSL timestamp it assigns to each sample so that the timestamp reflects **when the signal actually occurred**, not when the bytes happened to arrive at this client. This makes EEG, Physio16, and DIN events line up with each other and with external event markers. Three corrections are applied; together they are what we call timestamp compensation.
132
130
133
-
-**ERP analysis**: Enable `--align-timestamps` to align EEG data with event markers
134
-
-**Real-time BCI**: Use `--fast-recovery` instead (no filter delay to compensate)
135
-
-**Raw recording**: Disable alignment if you prefer unmodified timestamps
131
+
There is **no flag to toggle this** — it follows the configured mode automatically. (The old `--align-timestamps` flag was removed.) The app trusts the mode you configure: a decimated rate means the filter corrections apply; a native rate (`--fast-recovery`, or any rate above 1000 Hz) means they do not. At 500/1000 Hz, where native and decimated are indistinguishable in the data stream, the app assumes the mode you asked for (and, when forcing a rate, reinitializes to guarantee it).
136
132
137
-
### Limitations
133
+
### 1. System (pipeline) delay — always applied
138
134
139
-
**Important**: Timestamp alignment only works correctly when this application initializes the amplifier. If Net Station or another application previously initialized the amplifier, the current operating mode (decimated vs native) cannot be queried from AmpServer. In this case:
135
+
Every pushed timestamp (EEG, Physio16, **and** DIN) is moved earlier by `systemdelayms` to account for the time between digitization and the sample becoming available here: device firmware + network transmission + our read path. Measured at ~5 ms in native mode using `scripts/audio_latency_test`.
140
136
141
-
1. The application will reinitialize the amplifier to ensure the correct mode
142
-
2. This will interrupt any existing Net Station recording
143
-
3. To avoid this, start EGIAmpServer before Net Station, or restart the amplifier
137
+
The default is **4.5 ms** — deliberately a little *under* the measured value. Under-compensating is the safe direction: it guarantees a sample is never back-dated to *before* the event that produced it (a response must not precede its stimulus). It is user-configurable; raise it toward the measured latency if you prefer tighter alignment and can accept that risk.
144
138
145
-
If you need to join an existing Net Station session without reinitialization, do not use `--align-timestamps` unless you are certain of the current mode.
139
+
### 2. FPGA filter offset — decimated mode only
140
+
141
+
In decimated mode the anti-alias filter delays the EEG relative to the (unfiltered) DIN by the group delay in the table above (111/61/36 ms at 250/500/1000 Hz). The app subtracts this from the EEG/Physio timestamps so filtered EEG lines up with DIN. Native mode has no filter, so nothing is subtracted. DIN itself is never filter-shifted (only the system delay applies to it).
142
+
143
+
### 3. Physio16 realignment — decimated mode only
144
+
145
+
The Physio16 (PNS/PIB) acquisition path runs **ahead** of the FPGA-filtered EEG by a fixed ~33 ms in decimated mode, so the same event lands at different sample indices in the EEG vs physio channels of the combined stream. To keep them sample-aligned, the physio channels are buffered (delayed) by `physioaligndelayms` (default **33 ms**, applied as `floor(33 ms × rate)` samples). The physio channels therefore read **zeros for the first ~33 ms** after streaming starts, then real data. Native mode applies no physio delay.
146
+
147
+
### Configuration
148
+
149
+
Both values live in the config file (and persist via File → Save Configuration):
150
+
151
+
```xml
152
+
<settings>
153
+
...
154
+
<physioaligndelayms>33</physioaligndelayms> <!-- Physio16 realignment, decimated only -->
The filter-offset values (111/61/36 ms) are built in. All three were measured on real hardware; see `scripts/audio_latency_test/README.md` (system delay) and `notebooks/delay_inspection.ipynb` (filter and physio delays).
160
+
161
+
### Coexisting with Net Station
162
+
163
+
The filter corrections are only correct when the operating mode is known. If another application (e.g. Net Station) already started the amplifier at an ambiguous rate (500/1000 Hz), the app applies the corrections for the mode **you configured**, assuming it matches. If you are unsure of the running mode, attach with a native configuration (no filter corrections) or let the app reinitialize to a known mode (which interrupts the existing session).
0 commit comments