Skip to content

eegdb/go-edflib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-edflib

Go bindings for Teuniz EDFlib — read and write EDF, EDF+, BDF, BDF+, including annotations/events.

Standalone library (cgo). EEGDB uses it for import/export.

Features

  • EDF / EDF+ (16-bit) and BDF / BDF+ (24-bit) via vendored edflib/edflib.c
  • Read: header metadata, per-record or random-access samples, time-range queries, annotations
  • Write: EDF+/BDF+ with patient/recording fields, multi-channel data records, UTF-8 annotations
  • Idiomatic Go types: Header, SignalHeader, Annotation, Reader, Writer

Requirements

  • Go 1.23+
  • C compiler (gcc or clang)
  • CGO_ENABLED=1
sudo apt install build-essential   # Debian/Ubuntu
export CGO_ENABLED=1

Install

go get github.com/eegdb/go-edflib

Local development:

# In your go.mod
replace github.com/eegdb/go-edflib => ../go-edflib

Quick start

Read a file

import (
    "fmt"
    "github.com/eegdb/go-edflib"
)

r, err := edflib.OpenRead("recording.edf", true) // true = load all annotations
if err != nil {
    panic(err)
}
defer r.Close()

hdr := r.Header()
fmt.Println(hdr.NumSignals, hdr.FileDurationSec)

// Physical samples on channel 0, from 1s to 2s (seconds from recording start)
phys, err := r.ReadPhysicalInterval([]int{0}, 1.0, 2.0)
if err != nil {
    panic(err)
}
_ = phys[0]

for _, a := range hdr.Annotations {
    fmt.Println(a.OnsetUs, a.Text)
}

Write EDF+

import (
    "time"
    "github.com/eegdb/go-edflib"
)

w, err := edflib.CreateWriter("out.edf", edflib.FileTypeEDFPlus, 1)
if err != nil {
    panic(err)
}
defer w.Close()

w.SetStartTime(time.Now().UTC())
w.SetPatientCode("P001")
w.ConfigureSignal(0, edflib.SignalHeader{
    Label:            "EEG",
    PhysicalDim:      "uV",
    PhysicalMin:      -500,
    PhysicalMax:      500,
    DigitalMin:       -32768,
    DigitalMax:       32767,
    SamplesPerRecord: 256, // per 1s data record (default duration)
    SampleRate:       256,
})

samples := make([]int16, 256)
// fill samples...
w.WriteRecord([][]int16{samples})
w.WriteAnnotation(1_000_000, 0, "stimulus") // onset µs, duration µs (0 = instant)

Important notes

Topic Behavior
Writing CreateWriter / CreateWriterWithParams only create EDF+ or BDF+ files (EDFlib limitation). Classic EDF/BDF are read-only here.
Signal index NumSignals and signal[i] are data channels only; EDF+/BDF+ annotation channels are excluded from the count.
Annotation channel In the raw file, labels like EDF Annotations / BDF Annotations appear as the last signal; do not use them as EEG indices.
Data records Default record duration is 1 second. SamplesPerRecord = sample rate (Hz) when duration is 1s.
Read order ReadRecordDigital must read signals 0, 1, …, N−1 in order for each data record.
Write order WriteRecord writes one record: one buffer per data channel, in signal order.
BDF samples ReadDigital returns 24-bit values as int32; ReadRecordDigital clamps to int16 for convenience.

Read vs write file types

EDFlib (and therefore go-edflib) treats reading and writing differently.

Reading — all four formats

OpenRead / ReadHeader accept any file EDFlib recognizes:

FileType Format Bit depth
FileTypeEDF Classic EDF 16-bit
FileTypeEDFPlus EDF+ 16-bit + annotations
FileTypeBDF Classic BDF 24-bit
FileTypeBDFPlus BDF+ 24-bit + annotations

Writing — EDF+ and BDF+ only

CreateWriter calls EDFlib’s edfopen_file_writeonly, which rejects classic EDF/BDF:

// edflib/edflib.c — only these two types are allowed
if (filetype != EDFLIB_FILETYPE_EDFPLUS && filetype != EDFLIB_FILETYPE_BDFPLUS)
    return EDFLIB_FILETYPE_ERROR;

Passing FileTypeEDF or FileTypeBDF to CreateWriter returns edflib: file type error. This is an upstream EDFlib design choice, not a go-edflib restriction.

Why EDFlib does this

  1. The write API is built around EDF+/BDF+ features: structured patient/recording fields (SetPatientCode, SetRecordingAdditional, …), automatic annotation channel(s), and WriteAnnotation (TAL). Classic EDF/BDF use a single 80-byte patient/recording string and have no standard annotation channel.
  2. There is no edfopen_file_writeonly path that emits byte-accurate classic EDF or BDF from scratch.

What to do in practice

Goal Approach
New recording with annotations Write FileTypeEDFPlus or FileTypeBDFPlus
Tool compatibility EDF+ is widely opened by EDFbrowser and similar tools; many workflows treat it as the default interchange format
Convert classic → plus OpenRead the classic file, CreateWriter as EDF+/BDF+, copy header fields and samples (see test/edflib_test.go exportEDF)
Must output classic EDF/BDF only Not supported by EDFlib’s writer; use another library or implement the fixed header layout yourself

Types

FileType

Constant Meaning
FileTypeEDF Classic EDF (16-bit), read
FileTypeEDFPlus EDF+ with annotations
FileTypeBDF Classic BDF (24-bit), read
FileTypeBDFPlus BDF+ with annotations

Header

File-level metadata returned by Reader.Header() / ReadHeader().

Field Description
FileType EDF / EDF+ / BDF / BDF+
NumSignals Number of data signals (no annotation channel)
DataRecords Number of data records in file
DataRecordDurSec Duration of one data record (seconds)
FileDurationSec Total recording length (seconds)
StartTime Recording start (UTC)
Patient, Recording Legacy EDF fields
PatientCode, PatientName, Sex, BirthDate, PatientAdditional EDF+ patient fields
AdminCode, Technician, Equipment, RecordingAdditional EDF+ recording fields
Signals Per-channel SignalHeader slice
Annotations Parsed EDF+/BDF+ events (if loaded)

SignalHeader

Field Description
Label, Transducer, PhysicalDim, Prefilter Channel metadata strings
PhysicalMin, PhysicalMax Physical value range
DigitalMin, DigitalMax Stored digital range
SamplesPerRecord Samples per data record for this channel
SampleRate SamplesPerRecord / DataRecordDurSec
TotalSamples Total samples in file for this channel

Annotation

Field Description
OnsetUs Microseconds from recording start
DurationUs Duration in µs; 0 if instantaneous
Text UTF-8 description

ReadAnnotationMode

Constant Description
DoNotReadAnnotations Skip annotation parsing
ReadAnnotations Read annotations (default for OpenRead(..., false))
ReadAllAnnotations Read all annotations (OpenRead(..., true))

Constants

Name Description
MaxSignals Max data signals per file (EDFlib limit)
MaxAnnotationLen Max UTF-8 annotation text length
EDF16DigitalMin, EDF16DigitalMax Standard 16-bit EDF digital range
SeekSet, SeekCur, SeekEnd Reader.Seek whence values
AnnotChanIdxPosEnd, AnnotChanIdxPosMiddle, AnnotChanIdxPosStart Annotation channel placement when writing
SexMale, SexFemale Writer.SetSex values

Package-level functions

Function Description
Version() Linked EDFlib version number
IsBDF(ft FileType) bool Whether file type uses 24-bit BDF samples
IsFileUsed(path string) bool Whether path is open in EDFlib
NumberOfOpenFiles() int Count of open EDFlib handles
HandleAt(fileNumber int) int EDFlib handle for N-th open file, or -1
EDF16DigitalRange(digMin, digMax int) (int, int) Clamp/validate digital range for 16-bit EDF
ClampInt16(v int32) int16 Clamp 24-bit value to int16

Reader

Open with OpenRead or OpenReadWithMode. Always Close() when done.

Function Description
OpenRead(path, readAllAnnotations bool) (*Reader, error) Open file; trueReadAllAnnotations
OpenReadWithMode(path, mode ReadAnnotationMode) (*Reader, error) Open with explicit annotation mode
ReadHeader(path, readAllAnnotations bool) (Header, error) Read header only (opens and closes file)
ReadHeaderWithMode(path, mode) (Header, error) Header with explicit annotation mode
(r *Reader) Header() Header Cached file metadata
(r *Reader) Close() error Close file
(r *Reader) Annotation(n int) (Annotation, error) Get n-th annotation by index
(r *Reader) Seek(sig, offset, whence) (int64, error) Set sample position for one signal
(r *Reader) Tell(sig int) (int64, error) Current sample index
(r *Reader) Rewind(sig int) error Seek signal to start
(r *Reader) ReadDigital(sig, n int) ([]int32, error) Read n raw digital samples from current position
(r *Reader) ReadPhysical(sig, n int) ([]float64, error) Read n physical-unit samples
(r *Reader) ReadDigitalAt(sig, startSample, n int) ([]int32, error) Seek then read digital
(r *Reader) ReadPhysicalAt(sig, startSample, n int) ([]float64, error) Seek then read physical
(r *Reader) ReadDigitalInterval(signals []int, startSec, endSec float64) ([][]int32, error) Time-range digital read (per channel)
(r *Reader) ReadPhysicalInterval(signals []int, startSec, endSec float64) ([][]float64, error) Time-range physical read
(r *Reader) ReadRecordDigital(sig int) ([]int16, error) One data record for one signal; read sig 0..N−1 per record

Read patterns

Sequential (by data record) — required order: for each record, signal 0, then 1, …:

hdr := r.Header()
for rec := int64(0); rec < hdr.DataRecords; rec++ {
    for sig := 0; sig < hdr.NumSignals; sig++ {
        samples, err := r.ReadRecordDigital(sig)
        // ...
    }
}

Random access by time (recommended for snippets):

// Channel 1 (e.g. "CH1"), 1.0s ≤ t < 2.0s
data, err := r.ReadPhysicalInterval([]int{1}, 1.0, 2.0)

Random access by sample index:

phys, err := r.ReadPhysicalAt(0, 250, 250) // channel 0, sample 250, count 250

Writer

Create with CreateWriter or CreateWriterWithParams. Configure each signal, then write records. Close() finalizes the file.

Function Description
CreateWriter(path, fileType, numSignals int) (*Writer, error) New EDF+/BDF+ file; default 1s data records
CreateWriterWithParams(path, fileType, numSignals, sampleFreq int, physMaxMin float64, physDim string) (*Writer, error) Create with shared sample rate and symmetric physical range
(w *Writer) Close() error Finalize and close
(w *Writer) SetStartTime(t time.Time) error Recording start date/time
(w *Writer) SetSubsecondStartTime(subsecond100ns int) error Sub-second offset (100 ns units)
(w *Writer) SetPatientName, SetPatientCode, SetSex, SetBirthdate, SetPatientAdditional EDF+ patient fields
(w *Writer) SetAdminCode, SetTechnician, SetEquipment, SetRecordingAdditional EDF+ recording fields
(w *Writer) SetDataRecordDuration(duration10us int) error Record duration in 10 µs units (100000 = 1 s)
(w *Writer) SetMicroDataRecordDuration(durationUs int) error Record duration 1–9999 µs
(w *Writer) SetNumberOfAnnotationSignals(n int) error Annotation channel count (default 1)
(w *Writer) SetAnnotChanIdxPos(pos int) error AnnotChanIdxPosEnd / Middle / Start
(w *Writer) ConfigureSignal(edfSignal int, sig SignalHeader) error Per-channel header before writing data
(w *Writer) WriteRecord(buf [][]int16) error One data record, all channels
(w *Writer) WritePhysical(buf []float64) error One channel, physical samples, one record
(w *Writer) BlockWritePhysical(buf []float64) error All channels, physical, one record
(w *Writer) WriteDigitalShort(buf []int16) error One channel, 16-bit digital, one record
(w *Writer) WriteDigital(buf []int32) error One channel, digital (BDF 24-bit), one record
(w *Writer) BlockWriteDigitalShort(buf []int16) error All channels, 16-bit, one record
(w *Writer) BlockWriteDigital(buf []int32) error All channels, digital, one record
(w *Writer) BlockWriteDigital3Byte(buf []byte) error All channels, 24-bit LE bytes, one record
(w *Writer) WriteAnnotation(onsetUs, durationUs int64, text string) error UTF-8 annotation
(w *Writer) WriteAnnotationLatin1(onsetUs, durationUs int64, text string) error Latin-1 annotation

Low-level write APIs (WriteDigitalShort, BlockWrite*, …) call EDFlib once per signal or once per block; prefer WriteRecord unless you need fine-grained control.

Tests

CGO_ENABLED=1 go test ./test/...

Inspect any EDF/BDF file (header + channel 0 physical samples 1s–2s):

EDF_FILE=/path/to/file.edf go test ./test -run TestInspectEDF -v
Environment variable Default Description
EDF_FILE test/testdata/source.edf Path to inspect
EDF_INSPECT_MAX_ANNOT 10 Max annotation lines printed; 0 = print all

License

  • Go wrapper: MIT (LICENSE)
  • edflib/edflib.c, edflib.h: BSD-3-Clause (Teunis van Beelen)

References

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors