返回项目
已完成·

go-edflib - Go 语言 EDF/BDF 读写库

Teuniz EDFlib 的 Go 语言绑定,支持 EDF/EDF+/BDF/BDF+ 四种格式的读取与 EDF+/BDF+ 写入,提供 idiomatic 的 Reader/Writer API 与注释事件解析。

go-edflib - Go 语言 EDF/BDF 读写库
GoCGOEDFlibC

简介

在脑电(EEG)数据工程中,EDF(European Data Format)及其扩展格式 EDF+BDFBDF+ 是最广泛使用的 interchange 格式之一。无论是临床归档、多中心数据整合,还是算法流水线中的读写环节,对 EDF 族格式的可靠支持都是基础设施。

go-edflibTeuniz EDFlib 的 Go 语言绑定,通过 CGO 调用 vendored 的 edflib/edflib.c,提供 idiomatic 的 Go API。它可以读取EDF/EDF+/BDF/BDF+四种格式,写入 EDF+/BDF+,并完整支持注释(annotation)/事件通道。

为什么需要 go-edflib

Go 在服务端、CLI 工具和数据管道中越来越常见,但 EDF 生态长期被 C/Python/MATLAB 主导。现有方案各有局限:

方案局限
纯 Go 手写解析EDF 头结构、TAL 注释通道、BDF 24-bit 编码细节繁琐,易出兼容性问题
Python pyEDFlib适合脚本与科研,但不适合编译为单一二进制、高并发服务
直接调用 C EDFlib需要手动管理 handle、字符串转换、错误码,开发体验差

go-edflib 在 EDFlib 的稳定性与 Go 的工程化之间取平衡:底层复用经过广泛验证的 EDFlib,上层封装 HeaderSignalHeaderAnnotationReaderWriter 等类型,让 Go 项目能以自然的方式读写脑电文件。

支持的格式

读取 — 四种格式均可

格式常量位深注释
Classic EDFFileTypeEDF16-bit无标准注释通道
EDF+FileTypeEDFPlus16-bit含 TAL 注释
Classic BDFFileTypeBDF24-bitBioSemi 格式
BDF+FileTypeBDFPlus24-bit含 TAL 注释

写入 — 仅 EDF+ / BDF+

这是 EDFlib 上游的设计限制,而非 go-edflib 刻意收窄能力。EDFlib 的 edfopen_file_writeonly 只接受 EDF+ 和 BDF+,因为写入 API 围绕结构化 patient/recording 字段、自动注释通道和 WriteAnnotation(TAL)构建,Classic EDF/BDF 使用单一 80 字节 patient/recording 字符串且无标准注释通道。

目标推荐做法
新建带注释的录制写入 FileTypeEDFPlusFileTypeBDFPlus
与 EDFbrowser 等工具互操作EDF+ 已被广泛支持,可作为默认 interchange 格式
Classic → Plus 转换OpenRead 读取原文件,CreateWriter 写入 Plus,复制头字段与样本
必须输出 Classic EDF/BDFEDFlib 不支持,需其他库或自行实现固定头布局

核心 API

Reader — 读取文件

import (
    "fmt"
    "github.com/eegdb/go-edflib"
)
 
r, err := edflib.OpenRead("recording.edf", true) // true = 加载全部注释
if err != nil {
    panic(err)
}
defer r.Close()
 
hdr := r.Header()
fmt.Println(hdr.NumSignals, hdr.FileDurationSec)
 
// 读取通道 0 在 1s–2s 的物理量样本(秒,相对录制起点)
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)
}

Reader 提供多种读取模式,适应不同场景:

  • 顺序按 data record 读取ReadRecordDigital(sig),须按 record 遍历,每个 record 内 signal 0 → N−1 顺序读取
  • 按时间区间随机访问ReadPhysicalInterval / ReadDigitalInterval,适合截取片段
  • 按样本索引随机访问ReadPhysicalAt / ReadDigitalAt
  • 仅读头信息ReadHeader(path, readAllAnnotations),打开后立即关闭,适合元数据索引

Writer — 写入 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, // 默认 1s data record 时 = 采样率 Hz
    SampleRate:       256,
})
 
samples := make([]int16, 256)
// 填充样本...
w.WriteRecord([][]int16{samples})
w.WriteAnnotation(1_000_000, 0, "stimulus") // onset µs, duration µs (0 = 瞬时)

Writer 支持 EDF+ 结构化元数据:SetPatientCodeSetPatientNameSetSexSetBirthdateSetTechnicianSetEquipment 等。注释可通过 WriteAnnotation 写入 UTF-8 TAL 事件。

关键类型

文件级元数据,由 Reader.Header()ReadHeader() 返回:

字段说明
FileTypeEDF / EDF+ / BDF / BDF+
NumSignals数据通道数量(不含注释通道)
DataRecordsdata record 总数
DataRecordDurSec单个 data record 时长(秒)
FileDurationSec录制总时长
StartTime录制起始时间(UTC)
PatientCode, PatientName, Sex, BirthDateEDF+ 患者字段
AdminCode, Technician, EquipmentEDF+ 录制字段
Signals各通道 SignalHeader
Annotations解析后的 EDF+/BDF+ 事件

SignalHeader

字段说明
Label, Transducer, PhysicalDim, Prefilter通道元数据
PhysicalMin, PhysicalMax物理量范围
DigitalMin, DigitalMax数字量范围
SamplesPerRecord每个 data record 的样本数
SampleRateSamplesPerRecord / DataRecordDurSec
TotalSamples文件中该通道总样本数

Annotation

字段说明
OnsetUs相对录制起点的 onset(微秒)
DurationUs持续时间(微秒),0 表示瞬时事件
TextUTF-8 描述文本

使用注意事项

以下行为来自 EDFlib 规范,在使用 go-edflib 时需要留意:

主题行为
信号索引NumSignalssignal[i] 仅指数据通道;EDF+/BDF+ 的注释通道不计入
注释通道原始文件中 EDF Annotations / BDF Annotations 标签出现在最后一个 signal,不要当作 EEG 通道索引
Data record默认时长 1 秒;此时 SamplesPerRecord = 采样率(Hz)
顺序读取ReadRecordDigital 须对每个 record 按 signal 0, 1, …, N−1 顺序读取
BDF 样本ReadDigital 返回 24-bit 值为 int32ReadRecordDigital 为便利起见 clamp 到 int16
写入格式CreateWriter 仅创建 EDF+ / BDF+;传入 Classic 类型会返回 edflib: file type error

技术架构

┌─────────────────────────────────────────┐
│           Go Application                │
│  (EEGDB import/export, CLI, services)   │
└─────────────────┬───────────────────────┘
                  │ idiomatic API
┌─────────────────▼───────────────────────┐
│              go-edflib                  │
│  Reader / Writer / Header / Annotation  │
│  reader.go  writer.go  edflib.go  cgo.go│
└─────────────────┬───────────────────────┘
                  │ CGO
┌─────────────────▼───────────────────────┐
│         edflib/edflib.c (vendored)      │
│         Teuniz EDFlib (BSD-3-Clause)    │
└─────────────────────────────────────────┘
  • CGO 绑定:通过 cgo.go 桥接 Go 与 C,vendored 的 edflib/ 目录确保版本锁定、无需系统级安装 EDFlib
  • 类型安全:Go 侧将 C 结构体映射为 HeaderSignalHeader 等,避免调用方直接操作 raw handle
  • 注释模式ReadAnnotationMode 支持跳过、按需读取或一次性加载全部注释,平衡内存与便利性

环境与安装

要求:

  • Go 1.23+
  • C 编译器(gccclang
  • CGO_ENABLED=1
# Debian/Ubuntu
sudo apt install build-essential
export CGO_ENABLED=1
 
# 安装
go get github.com/eegdb/go-edflib

运行测试:

CGO_ENABLED=1 go test ./test/...
 
# 检查任意 EDF/BDF 文件(头信息 + 通道 0 的 1s–2s 物理样本)
EDF_FILE=/path/to/file.edf go test ./test -run TestInspectEDF -v

应用场景

高性能 EDF 处理服务

对于需要并发处理大量 EDF 文件的场景(批量元数据索引、格式校验、Classic → Plus 转换),Go 的 goroutine 模型配合 go-edflib 的随机访问 API(ReadPhysicalInterval)可以高效截取片段而无需顺序扫描整个文件。

CLI 与边缘部署

编译为静态链接(或 mostly-static)CLI 工具后,可在无 Python 环境的机器上直接 inspect、转换 EDF 文件,适合实验室工作站、数据采集端或 CI 流水线中的格式检查步骤。

许可证

组件许可证
Go wrapperMIT
edflib/edflib.c, edflib.hBSD-3-Clause(Teunis van Beelen)

项目地址