Subject: Analog input channels appear to be missing from BDF recordings (Cyton, GUI v6.0.0-beta.1)
Hi OpenBCI team,
We've recently been experimenting with using a light sensor connected to the Cyton board to record visual stimulus onsets during EEG experiments.
The setup is working well, but we've encountered what appears to be a limitation of the BDF recording format in the OpenBCI GUI (v6.0.0-beta.1).
Setup
OpenBCI Cyton, 8 channels
Windows
Light sensor connected to D12 (A6)
Analog Read mode enabled in the GUI
EEG/EMG recorded simultaneously with the light-sensor signal
Observations
The light-sensor signal is clearly visible in the Analog Read widget during acquisition.
We have also confirmed that the sensor signal is saved correctly in both the native OpenBCI RAW format and the BrainFlow CSV format, where it appears in column 21 (Analog Channel 1).
However, when recording in BDF format, the resulting file contains only 10 channels: eight EEG/EMG channels and two additional channels labelled "Accel X" and "Accel Y". D12 signal does not appear to be present.
We can currently work around this by recording in OpenBCI RAW or BrainFlow format and converting the data afterward, but being able to save EEG and analog trigger signals together in BDF format would be particularly useful for experimental research, as BDF can be imported directly into common EEG analysis software such as EEGLAB.
Thank you for developing and maintaining the software! We're happy to provide example recordings or perform additional tests if that would be helpful.
Best regards,
Richard
Comments
There's a specific code path worth checking in the v6.0.0-beta.1 BDF writer:
writeAuxDataValues()readsgetAccelerometerChannels(), whileBoardCytonexposes the analog channels separately throughgetAnalogChannels(). The BDF header also hard-codes the Accel labels. That suggests checking which data rows reach the writer, not just renaming its channels. I haven't reproduced this on hardware.Could you share the BDF header's complete signal list and sample rates as read by EDFBrowser, without participant details? That writer declares three auxiliary signals plus an annotation signal, so the reported total of ten deserves a separate check. Your working RAW/BrainFlow recording would be a useful comparison for a short light-on/light-off test.
Thank you! I checked the files more closely and the code path you pointed out seems very consistent with what we are seeing.
The BDF header contains 12 signals in total: EEG 1–8, Accel X, Accel Y, Accel Z, and BDF Annotations. EEG 1–8 and Accel X/Y/Z each have 250 samples per 1-second data record (250 Hz); BDF Annotations has 20 samples per record.
In the BDF recording, Accel X, Y and Z are zero throughout. This initially made us wonder whether the BDF auxiliary channels were simply mislabelled, but given that the BDF Accel channels contain only zeros while the RAW analog channel contains the expected light-sensor signal, it appears plausible that the writer is reading the accelerometer rows rather than the analog rows.
The RAW file contains the three analog channels separately from the accelerometer channels. The accelerometer channels are zero, whereas the analog channels contain data, and the light-sensor response is clearly visible in Analog Channel 1. This was recorded with the Cyton in Analog mode and the light sensor connected to D12.
We used this RAW recording for a short timing test with a 240-Hz OLED display and the Cyton recording at 250 Hz. All 20 light pulses were detected. For programmed pulse durations of 50, 100, 150 and 200 ms, the measured optical durations were consistently around 8–14 ms shorter than programmed. We do not yet know what causes this systematic difference. Apart from the first pulse, the measurements within each programmed duration were very consistent, generally varying by no more than one 250-Hz sample (4 ms).
Across all 20 pulses, onset timing was highly consistent except for the first trial, could be something to do with the stimulus presentation. The interval following the anomalous first pulse was 976 ms, whereas the remaining onset-to-onset intervals averaged 999.98 ms (SD = 2.05 ms; range 996.4–1002.3 ms).
Thanks, the 12-signal header clears up the count. The zero BDF auxiliary channels versus a working RAW analog signal strengthens the case for checking row selection, rather than just labels.
I'd treat the pulse-duration discrepancy separately. A 240-Hz frame is about 4.17 ms and a 250-Hz sample is 4 ms, but those numbers alone don't explain a consistent 8–14 ms shortening. Which stimulus package are you using, and are the programmed durations based on actual display-flip timestamps or a timer? Comparing onset and offset flip logs with the photodiode threshold crossings would help locate the difference. I haven't inspected the recordings or reproduced this on hardware.
Thank you, that makes sense.
The stimulus was presented using a recent version of PsychoPy (2026.1.3). In an earlier version we timed the pulses using elapsed time rather than frame counts, and that indeed produced intervals that were consistently too short by roughly 10–12 ms. Switching to frame-based timing substantially decreased this error.
The remaining issue is that the measured optical pulse durations are still around 8–14 ms shorter than programmed. We will keep looking for an explanation. The encouraging part is that the timing appears very consistent.
One possibility we considered is the response time of the light sensor and/or the display. Do you know the approximate rise/fall time or temporal response characteristics of this type of light sensor? In principle, if the onset and offset responses are asymmetric, the threshold crossings could shift the apparent pulse duration.
I can't put a rise/fall number on it without the exact sensor/module and readout circuit. Which part, load resistor and filtering are you using?
It's the difference between the two edge delays that changes pulse width; a delay shared by onset and offset cancels. For a light-only bench test, I'd measure widths at several thresholds on the same trace and compare them with the actual onset/offset flip timestamps. If the width changes with threshold, that's a useful clue about the analogue response, although it won't separate sensor behaviour from display behaviour on its own.
We are using the Photoresistor Module supplied with the current OpenBCI External Trigger Add-on for the Cyton, connected to D12 exactly as described in the OpenBCI guide. We have not added any external components or filtering.
I checked the product information, but I could not find the exact photoresistor part, load-resistor value, filtering, or rise/fall specifications for the supplied module.
One additional observation is that the detection threshold does not seem to explain the shorter measured durations. I tried a wide range of thresholds on the RAW trace and obtained the same pulse boundaries and durations. The signal is almost square-wave-like, switching from approximately 1020–1023 to about 31 within one 250-Hz sample, so there is very little ambiguity about where either edge occurs. This would seem to make the threshold itself an unlikely explanation for the 8–14 ms difference.
That threshold sweep is useful: it makes threshold choice a less convincing explanation, though an edge contained within one 4 ms sample still doesn't establish the sensor's response time.
Could you post a few paired rows of actual onset/offset flip times and optical pulse widths, with no participant information? I'd compare the logged flip-to-flip duration with the requested frame count first, then compare that duration with the sensor measurement. PsychoPy can record frame intervals during the stimulus. That would help distinguish a short software-timed pulse from a discrepancy farther along the measurement chain, without assigning a cause yet.
PsychoPy's timing notes: https://psychopy.org/general/timing/detectingFrameDrops.html
Thank you! You are right that a transition within one 4-ms sample does not establish the sensor’s actual response time.
I don’t currently have paired onset/offset flip timestamps for that recording. We are, however, planning to compare the OpenBCI photoresistor module with a BioSemi light sensor, measuring the same visual events simultaneously. I think this could provide another useful clue about the systematic shortening, particularly if the two sensors produce different pulse durations.
It would also be helpful to know whether the specifications of the photoresistor module supplied with the OpenBCI trigger kit are available somewhere. I could not find information about its response time or circuit components in the documentation.
Regarding the original BDF issue, would it make sense to raise the suspected row-selection problem as a GitHub issue so that the GUI developers can investigate it?
Yes, I'd raise the BDF recording issue on GitHub, after checking for an existing report. Your corrected 12-signal header and the working RAW analog channel give the developers a much clearer starting point. Include the GUI version, Cyton mode, exact reproduction steps and a short participant-free bench recording in both formats, with the header readout. Describe row selection as a suspected cause, rather than a confirmed diagnosis, and link this thread.
I'd keep the pulse-width investigation separate. Recording both sensors on the same visual events is a useful comparison, but agreement alone would not establish the true optical duration. If possible, capture the actual flip timestamps in that next test too. I still don't have a verified response-time specification for the supplied module.
Mentioning Richard Waltman @retiutut, the GUI developer. He may have insights on your BDF issue. As Alex mentioned, posting an issue on the Github is available. But may have less visibility since there are already about 40 issues posted there.