Skip to main content

A buffer-then-flush EDF/EDF+ writer and a library-independent round-trip integrity test for real-time biopotential streaming.

Project description

edf-buffered-write

A buffer-then-flush EDF/EDF+ writer and a library-independent round-trip integrity test that prevent and detect silent corruption of European Data Format recordings during real-time biopotential streaming.

CI DOI License: MIT

The DOI above is the concept DOI and always resolves to the latest version.

Licensing at a glance: the software (the edf_buffered_write library and all .py scripts) is under the MIT License (LICENSE); the two de-identified .edf recordings are in the public domain under CC0-1.0 (LICENSE-CC0-1.0.txt). See DATA_LICENSE.md for exactly which files each licence covers.

The problem

Real-time acquisition hardware delivers short blocks (often 10–100 samples per read), but an incremental EDF/EDF+ writer such as pyedflib commits one full data record per call, padding the unused remainder. Handing each sub-record device block straight to the writer therefore produces a structurally valid EDF+ file that is silently inflated in duration by the factor fs·d / b, attenuated in RMS amplitude by √(b / fs·d), and distorted in spectrum — and no error is raised, so the damage propagates undetected into downstream analysis.

What this package provides

  1. A reusable buffer-then-flush writer (edf_buffered_write.BufferedEdfWriter) that wraps a pyedflib.EdfWriter, accepts blocks of any size, commits only complete records, and pads just the final record with the last acquired value (not zero). It is a drop-in fix local to the acquisition loop; readers and already-written files are unaffected.
  2. A library-independent round-trip integrity test that writes a signal of known statistics, reads it back and compares reported duration, RMS amplitude and power spectral density, so the failure can be caught in a continuous-integration pipeline regardless of the writing library.

It also bundles the characterisation harness (configuration sweep and three-writer comparison), the real-data round-trip, and two de-identified real surface-EMG recordings, so every figure and number in the accompanying software metapaper can be reproduced.

Install

pip install -e .          # installs the edf_buffered_write library

Usage

import pyedflib
from edf_buffered_write import BufferedEdfWriter

writer = pyedflib.EdfWriter("out.edf", n_channels)
writer.setSignalHeaders(headers)          # your per-signal headers
writer.setDatarecordDuration(1.0)         # 1 s records

bw = BufferedEdfWriter(writer)
while acquiring:
    block = device.read(100)              # any block size is fine
    bw.write_samples([block])             # one array per signal
bw.close()                                # flushes and closes the writer

write_samples takes one array per signal; close flushes the remainder (padding the final record with the last value) and closes the underlying writer.

Contents

Path Purpose
edf_buffered_write/ The reusable library (BufferedEdfWriter).
pyproject.toml Packaging metadata; makes the library pip install-able.
test_buffered_writer.py pytest tests of the library API (no inflation, amplitude preserved, multichannel, input validation).
test_roundtrip.py pytest round-trip integrity tests via the harness (naive corrupts, buffered preserves, inflation law holds across configs).
.github/workflows/ci.yml Continuous integration on Python 3.10–3.12.
harness_multilibrary.py Configuration sweep (Table 1) and three-implementation comparison (Table 2). Functions importable via a guarded main().
make_figures.py Regenerates the synthetic-signal characterisation figures (time-domain, inflation law, PSD contamination).
make_pseudocode.py Regenerates the side-by-side pseudocode figure (shown inline as Listing 1 in the paper).
realdata_roundtrip.py Round-trip of a real EDF recording through the naive and buffered patterns; produces the PSD figures.
emgteach_real_sEMG_2ch_1kHz_58s.edf De-identified real surface-EMG recording, BITalino, 2 ch, 1 kHz, 58 s (manuscript Figure 2).
emgteach_real_sEMG_MyoWare_1ch_1kHz_24s.edf De-identified real surface-EMG recording, MyoWare 2.0 + Arduino-compatible board, 1 ch, 1 kHz, 24 s (manuscript Figure 3).
Fig_*.png Manuscript and characterisation figures (300 dpi).
requirements.txt Pinned dependency versions.
DATA_LICENSE.md / LICENSE-CC0-1.0.txt Data licence (CC0-1.0) for the de-identified .edf recordings, separate from the MIT-licensed code.

Manuscript figure map: Figure 1 = Fig_timedomain_burst_padding.png (time-domain bursts + padding vs. continuous signal); Figure 2 = Fig_realdata_PSD.png (BITalino recording); Figure 3 = Fig_realdata_PSD_MyoWare.png (MyoWare recording). Fig_inflation_law.png and Fig_PSD_contamination.png are additional synthetic-characterisation figures.

Requirements

Python ≥ 3.10. The library itself needs only numpy and pyedflib; the harness, figures and cross-implementation comparison additionally use scipy, matplotlib, EDFlib-Python and edfio.

python -m venv venv
source venv/bin/activate          # Windows: venv\Scripts\activate
pip install -r requirements.txt
pip install -e .

Reproduce

# Table 1 (configuration sweep) + Table 2 (three implementations)
python harness_multilibrary.py

# Synthetic-signal characterisation figures
python make_figures.py

# Real-data round-trip, using an included de-identified recording
python realdata_roundtrip.py emgteach_real_sEMG_2ch_1kHz_58s.edf 1 100
python realdata_roundtrip.py emgteach_real_sEMG_MyoWare_1ch_1kHz_24s.edf 1 100

Testing

pip install -e . pytest
pytest -v

test_buffered_writer.py exercises the shipped BufferedEdfWriter API; test_roundtrip.py asserts that the naive per-block writer corrupts the recording (duration inflated by fs·d/b, RMS attenuated by its square root), that the buffer-then-flush writer preserves duration and amplitude, and that the deterministic inflation law holds across a range of sampling rates, record durations and block sizes. The same tests run in continuous integration on Python 3.10–3.12 (.github/workflows/ci.yml).

All synthetic results use a fixed random seed (42), so the numbers are reproducible across machines. Base configuration (fs = 1 kHz, 1 s records, 100-sample blocks):

Quantity Naive Buffered
Reported file duration (s) 100.0 10.0
Read-back RMS amplitude (mV) 0.069 0.218
Samples within ±1 LSB of zero (%) 90.0 0.1
PSD attenuation factor at 80 Hz (vs original) ×51 ×1.0

Real surface-EMG recordings

Both recordings are de-identified EDF+ files (all patient header sub-fields are X, unknown) and are the author's own signals, recorded from himself during software development:

  • emgteach_real_sEMG_2ch_1kHz_58s.edf — BITalino board, 2 channels, 1 kHz, 58 s, physical units mV.
  • emgteach_real_sEMG_MyoWare_1ch_1kHz_24s.edf — Arduino-compatible board with a MyoWare 2.0 sensor, 1 channel, 1 kHz, 24 s, physical units mV.

Power spectral density protocol

Welch's method on each read-back signal (1024-sample segments, 50 % overlap, physical units mV). The attenuation factor is the ratio of PSD between the original in-memory signal and the naive file in the EMG band.

Upstream contribution

The buffered writer has also been proposed upstream to pyedflib (issue #284 and pull request #285), which adds an opt-in buffered writer to the library itself, so downstream projects may inherit the fix directly.

How to cite

  • Agis-Torres, Á. (2026). Silent corruption of European Data Format recordings during real-time biopotential streaming: a buffered-write solution. Manuscript submitted.
  • Agis-Torres, Á. (2026). edf-buffered-write [Software]. Zenodo. https://doi.org/10.5281/zenodo.21163099

A CITATION.cff file is provided for automatic citation export.

Declaration of generative AI

The code in this repository was developed with the assistance of Claude (Anthropic), and was reviewed and tested by the author.

License

This repository is dual-licensed:

  • Code (library, scripts, CI workflow, configuration) under the MIT License — see LICENSE. A permissive licence is deliberate: the buffer-then-flush pattern is meant to be copied directly into other acquisition loops.
  • Data (the two de-identified .edf surface-EMG recordings) released into the public domain under CC0-1.0 — see DATA_LICENSE.md and LICENSE-CC0-1.0.txt.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

edf_buffered_write-2.5.0.tar.gz (11.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

edf_buffered_write-2.5.0-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file edf_buffered_write-2.5.0.tar.gz.

File metadata

  • Download URL: edf_buffered_write-2.5.0.tar.gz
  • Upload date:
  • Size: 11.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for edf_buffered_write-2.5.0.tar.gz
Algorithm Hash digest
SHA256 26f51cf2b6a1a2c036855d4800f26ed7bad72e1a243823b3e90c89727016b084
MD5 0f4cc612fdfc44ed8e2c05bebe2af43f
BLAKE2b-256 f11a7c394271f98219e7805732893c92671ce4025e59eeaf5d78ad293e568d63

See more details on using hashes here.

File details

Details for the file edf_buffered_write-2.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for edf_buffered_write-2.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 88ff1ac679017c677e7ea7f27b1615e18b3be1b2f3a5b4e2ca419c27c12c5198
MD5 570e488f4cb7b033f9cf855a9eaf2ea8
BLAKE2b-256 628e3c127c7470c71a291be3514ffc2e4a7341b83c93fbc754d6169cbfc05cd0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page