EDF — the European Data Format — is the de facto interchange format for polysomnography and clinical EEG. It was defined by Kemp et al. in 1992 and extended as EDF+ in 2003. Almost every EEG amplifier can export to it; almost every analysis tool can read it. Understanding what is actually inside the file will save you from a lot of quiet, subtle bugs downstream.
The structure of an EDF file
An EDF file is two parts glued together: an ASCII header followed by a series of binary data records.
The header
The header is human-readable ASCII with fixed byte offsets. It declares, among other things:
- Version, patient ID, recording ID, start date and time
- Number of data records and duration of each record (in seconds)
- Number of signals (channels)
- Per channel: label (e.g.
EEG Fp1-Ref), transducer type, physical dimension (usuallyuV), physical minimum/maximum, digital minimum/maximum, prefiltering description, and number of samples per data record
A single EDF file can contain channels sampled at different rates — one channel might carry EEG at 256 Hz while another carries an ECG channel at 512 Hz and yet another carries respiration at 32 Hz. The per-channel "samples per record" field is how EDF encodes this. Assume a single sampling rate at your peril.
The data records
Data records are stored as 16-bit little-endian signed integers. To get physical units (microvolts, millivolts, whatever the channel's dimension declares), you apply a linear scaling derived from the header:
physical = (digital − digital_min) × (physical_max − physical_min) / (digital_max − digital_min) + physical_min
This is the calibration. Every channel has its own. Skip it and your signal is scaled arbitrarily — the shape is right, the amplitude is meaningless, and every downstream metric with units (µV², absolute band power) is wrong.
EDF+
EDF+ is a backward-compatible extension. It adds:
- A structured patient and recording ID field (space-delimited sub-fields for patient code, sex, birthdate, name)
- A dedicated annotations channel (labeled
EDF Annotations) using time-stamped TAL (Time-stamped Annotations Lists) — this is where sleep stages, event markers, and notes live - Support for discontinuous recordings (EDF+D) alongside the continuous case (EDF+C)
If your reader skips the annotations channel, you lose the event markers silently.
What to check before you analyze
Sampling rate — per channel
Do not assume it is uniform. Read it from the header, per channel. If you are going to combine channels (e.g. compute a bipolar derivation), resample first.
Units
The header declares physical units per channel. EEG is almost always microvolts (uV), but not always — some pediatric or intracranial recordings use millivolts. Confirm before interpreting amplitude thresholds.
Montage
The channel labels tell you the reference. Fp1-Ref is a referential montage against a common reference (often linked mastoids or Cz). Fp1-F3 is a bipolar derivation between two scalp sites. Bipolar and referential montages measure different things and should not be pooled without care.
Prefiltering
The header's prefiltering field tells you what the amplifier already did — for example HP:0.3Hz LP:70Hz N:50Hz. That defines the trustworthy frequency range. Trying to analyze delta below the recorded high-pass corner returns noise, not signal.
Common gotchas
- Units when using MNE-Python. MNE returns EEG data in volts, not microvolts. Multiply by 1e6 before comparing to microvolt-based thresholds — or leave the data in volts and be consistent everywhere.
- Heterogeneous sample rates. A single EDF can mix rates. Some libraries silently pick the first channel's rate and use it for all. Read per-channel or you will misinterpret timestamps.
- No standardized electrode coordinates. EDF stores labels, not 3D positions. If you want topographic maps, you have to attach a montage separately (e.g. the standard 10-20 layout by label). Bipolar montages can't be plotted topographically — the "location" of an
Fp1-F3channel is a pair of sites, not a point. - Annotations character encoding. The annotations channel is UTF-8 in EDF+, but very old files may contain other encodings. Odd characters in event labels often trace back to this.
- Physical/digital range mismatch. A rare exporter bug sets
physical_min == physical_max, which produces a divide-by-zero in the calibration formula. Guard against it.
BDF: EDF's 24-bit cousin
BDF (BioSemi Data Format) is structurally similar to EDF but stores samples as 24-bit integers instead of 16-bit. That gives it a much larger dynamic range, which is why high-density research systems favor it. Most EDF readers can handle BDF with a flag; the header layout is nearly identical.
A minimal analysis-ready checklist
- Read the header, one channel at a time.
- Note per-channel sample rate, units, and calibration.
- Confirm the montage and reference from the labels.
- Note the prefiltering, and don't ask questions of the signal outside that range.
- Parse annotations if you need event timing, sleep stages, or discontinuity markers.
Once the file is decoded correctly, the analysis stack — band power, spectral features, connectivity, and the rest — proceeds normally.
NeuroTrace parses EDF and EDF+ in the browser, respects per-channel sample rates and calibration, and shows the header fields alongside the signal so you can catch these issues before they become results.