Task 252: .GDF File Format

Task 252: .GDF File Format

1. List of All Properties Intrinsic to the .GDF File Format

The .GDF (General Data Format for Biosignals) version 2.0 is a binary file format designed for storing multidimensional biosignal data (e.g., EEG, ECG) with metadata, events, and annotations. It uses little-endian byte order for all multi-byte values. Files are structured as concatenated sections: fixed header (256 bytes), variable header (256 * NS bytes, where NS is the number of channels/signals), optional header (TLV-structured, variable length), data section (variable, up to 1 GB recommended), and event table (at the end or referenced). All headers are aligned to 256-byte blocks. Data types follow IEEE standards (e.g., float32/64). Obsolete fields are retained for compatibility.

Key intrinsic properties include:

  • Endianness: Little-endian for all multi-byte numeric fields.
  • Header Alignment: All headers are multiples of 256 bytes; total header length is specified in Header 1.
  • Version String: Fixed 8-byte ASCII "GDF 2.10" (or compatible variant) in Header 1 for identification.
  • Maximum File Size: Data section ≤ 1 GB; optional header ≤ remaining space after fixed/variable headers.
  • Data Organization: Records-based (NREC records, each DUR seconds long); each record contains NS channels with SPR (samples per record) values per channel.
  • Sampling: Equidistant (SPR > 0) or non-equidistant (SPR = 0, variable-length events).
  • Data Types (gdftyp codes from Table 7): uint8=1, int8=2, uint16=3, int16=4, uint32=5, int32=6, uint64=7, int64=8, float32=9, float64=10, daq-event=11, string=12, utf8-string=13, void=14, compressed=15 (16-bit base + compression flag).
  • Event Modes: Mode 1 (compact: 6 bytes/event), Mode 3 (extended: 12 bytes/event); position as sample index or timestamp.
  • TLV Structure in Optional Header: Tag (uint8, 0x00-0x7F), Length (uint8 or uint16/32 if >255), Value (variable); up to 255 tags.
  • Compression: Optional (gdftyp 15); uses LZMA or similar, with original type in low 4 bits.
  • Quality Control: Digital min/max allow overflow/underflow detection.
  • Geospatial Support: Latitude/longitude/altitude in Header 1 (RFC 1876 format).
  • Privacy Features: Birthday modifiable by up to 1 year; patient ID anonymized.

Header 1 (Fixed Header, Bytes 0-255)

  • Version ID (bytes 0-7, char[8]): ASCII "GDF 2.10".
  • Patient ID (bytes 8-73, char[66]): Anonymized patient code/name/classification (no spaces, "X" for empty).
  • Reserved (bytes 74-83, uint8[10]): Unused.
  • Smoking/Alcohol/Drug Abuse/Medication (byte 84, uint8 bits[4x2]): Bit flags (00=unknown, 01=no, 10=yes).
  • Weight (byte 85, uint8): kg (0=unknown, 255=>254 kg).
  • Height (byte 86, uint8): cm (0=unknown, 255=>254 cm).
  • Gender/Handedness/Visual Impairment/Heart Impairment (byte 87, uint8 bits[2x4]): Bit flags (00=unknown, 01=male/right/normal/normal, 10=female/left/impaired/impaired).
  • Recording ID (bytes 88-151, char[64]): Study ID + serial.
  • Recording Location (bytes 152-167, uint32[4]): Lat/lon/alt (RFC 1876), version in byte 156.
  • Start Date/Time (bytes 168-175, uint32[2]): 64-bit timestamp (days since 0000-01-01 + fractional seconds, ~20μs resolution).
  • Birthday (bytes 176-183, uint32[2]): 64-bit, modifiable for privacy.
  • Header Length (bytes 184-185, uint16): Total 256-byte blocks (≥1+NS).
  • Patient Classification (ICD) (bytes 186-191, uint8[6]): ICD9/10 code.
  • Equipment Provider ID (bytes 192-199, uint64): 8-byte vendor code.
  • Reserved (bytes 200-205, uint8[6]): Unused.
  • Headsize (bytes 206-211, uint16[3]): Circumference/nasion-inion/left-right (mm, 0=unknown).
  • Reference Electrode Position [X,Y,Z] (bytes 212-223, float32[3]): Coordinates (m).
  • Ground Electrode Position [X,Y,Z] (bytes 224-235, float32[3]): Coordinates (m).
  • Reserved (bytes 236-243, int64): Number of data records (-1=unknown/continuous).
  • Record Duration (bytes 244-251, uint32[2]): Rational seconds (numerator/denominator).
  • NS (Number of Channels) (bytes 252-253, uint16): Signal count.
  • Reserved (bytes 254-255, uint16): Unused.

Header 2 (Variable Header, Bytes 256 to 256*(NS+1)-1, 256 bytes/channel)

  • Label (16 bytes/channel, char[16]): Channel name (e.g., "EEG C3").
  • Transducer Type (80 bytes/channel, char[80]): Sensor description.
  • Physical Dimension (obsolete) (8 bytes/channel, char[8]): Unit string.
  • Physical Dimension Code (2 bytes/channel, uint16): Unit code (Table 5/6, e.g., V=21).
  • Physical Min (8 bytes/channel, float64): Min physical value (e.g., -500e-6).
  • Physical Max (8 bytes/channel, float64): Max physical value (e.g., 500e-6).
  • Digital Min (8 bytes/channel, float64): Min digital value (for QC).
  • Digital Max (8 bytes/channel, float64): Max digital value (for QC).
  • Prefiltering (obsolete) (68 bytes/channel, char[68]): Filter string.
  • Lowpass (4 bytes/channel, float32): Hz (NaN=unknown).
  • Highpass (4 bytes/channel, float32): Hz (NaN=unknown).
  • Notch (4 bytes/channel, float32): Hz (<0=off, NaN=unknown).
  • Samples per Record (SPR) (4 bytes/channel, uint32): Samples/record (0=non-equidistant).
  • Data Type (gdftyp) (4 bytes/channel, uint32): Type code (see above).
  • Electrode Position [X,Y,Z] (12 bytes/channel, float32[3]): Coordinates (m).
  • Sensor Info (20 bytes/channel, uint8[20]): Impedance/reserved (version-dependent).

Optional Header 3 (TLV, Starts at 256*(NS+1), Length = HeaderLen256 - 256(NS+1))

  • NT (Number of Tags) (first 1 byte if NT<256): uint8.
  • Tags (0x00-0x7F):
  • 0x00: Terminator (no length/value).
  • 0x01: Event code descriptions (char[], null-terminated strings).
  • 0x02: BCI2000 header (char[]).
  • 0x03: Manufacturer (char[4 strings], ≤128 bytes).
  • 0x04: MEG orientation (float32[NS*3]).
  • 0x05: IP address (uint8[4/16], IPv4/IPv6, big-endian).
  • 0x06: Technician ID (char[]).
  • 0x07: Hospital ID (char[]).
  • 0x20: Event table offset (uint32, from file start).
  • 0x21-0x2F: Reserved for events.
  • 0x30-0x3F: Manufacturer extensions.
  • 0x40-0x7F: User-defined.

Data Section (After Header 3, Length = NREC * (sum(SPR_i * sizeof(gdftyp_i) for i=1 to NS)))

  • Interleaved by channel per record; compressed if gdftyp=15.

Event Table (At end or offset in tag 0x20, Length = 8 + NEV * (6 or 12 bytes))

  • Header (8 bytes): Mode (uint8: 1=compact, 3=extended), Size (uint8), Reserved (uint16), NEV (uint32).
  • Events (per event):
  • Mode 1: Position (uint32 sample index), Type (uint16 code).
  • Mode 3: Position (uint64 timestamp), Type (uint32 code), Channel (uint16).

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .GDF Parsing

Embed this as a Ghost blog post HTML card. It uses drag-and-drop to read the file via FileReader/DataView, parses key properties from Headers 1/2 (simplified for brevity; extend for full TLV/events), and dumps to a

element. Assumes browser environment.

Drag and drop a .GDF file here to parse its properties.



4. Python Class for .GDF Read/Write and Property Dump

Uses struct for binary parsing. Read parses headers and prints properties; write reconstructs from a dict (simplified, assumes same structure). Install nothing (uses stdlib).

import struct
import json

class GDFHandler:
    def __init__(self, filename=None):
        self.filename = filename
        self.props = {}

    def read(self):
        with open(self.filename, 'rb') as f:
            buffer = f.read()
        view = memoryview(buffer)

        # Header 1
        self.props['version'] = buffer[:8].decode('ascii').rstrip('\x00')
        self.props['patient_id'] = buffer[8:74].decode('ascii', errors='ignore').rstrip('\x00')
        self.props['weight'] = struct.unpack('<B', buffer[85:86])[0]
        self.props['height'] = struct.unpack('<B', buffer[86:87])[0]
        byte87 = struct.unpack('<B', buffer[87:88])[0]
        self.props['gender'] = 'Male' if (byte87 & 0x03) == 1 else 'Female' if (byte87 & 0x03) == 2 else 'Unknown'
        self.props['header_len'] = struct.unpack('<H', buffer[184:186])[0] * 256
        self.props['nrec'] = struct.unpack('<q', buffer[236:244])[0]
        self.props['ns'] = struct.unpack('<H', buffer[252:254])[0]

        # Header 2 (first channel example)
        ch_offset = 256
        self.props['ch1_label'] = buffer[ch_offset:ch_offset+16].decode('ascii', errors='ignore').rstrip('\x00')
        self.props['ch1_phys_min'] = struct.unpack('<d', buffer[ch_offset+104:ch_offset+112])[0]
        self.props['ch1_phys_max'] = struct.unpack('<d', buffer[ch_offset+112:ch_offset+120])[0]
        self.props['ch1_gdftyp'] = struct.unpack('<I', buffer[ch_offset+220:ch_offset+224])[0]
        self.props['ch1_spr'] = struct.unpack('<I', buffer[ch_offset+216:ch_offset+220])[0]

        # Print all
        print(json.dumps(self.props, indent=2))

    def write(self, props, output_filename):
        # Simplified: Reconstruct Header 1 (extend for full)
        header1 = b'GDF 2.10' + b'X' * 66  # Patient ID placeholder
        header1 += struct.pack('<B', props.get('weight', 0))  # ... Pack other fields similarly
        header1 += struct.pack('<H', (props.get('header_len', 512) // 256))  # etc.
        # Pad to 256 bytes
        header1 += b'\x00' * (256 - len(header1))
        # Header 2, data, etc. (stub; implement full packing)
        with open(output_filename, 'wb') as f:
            f.write(header1)  # Extend for full write
        print(f"Wrote {output_filename}")

# Usage
handler = GDFHandler('sample.gdf')
handler.read()
# handler.write({'weight': 70, ...}, 'output.gdf')

5. Java Class for .GDF Read/Write and Property Dump

Uses ByteBuffer for parsing. Read prints to console; write reconstructs (simplified).

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;

public class GDFHandler {
    private String filename;
    private Map<String, Object> props = new LinkedHashMap<>();

    public GDFHandler(String filename) {
        this.filename = filename;
    }

    public void read() throws IOException {
        try (RandomAccessFile file = new RandomAccessFile(filename, "r");
             FileChannel channel = file.getChannel()) {
            ByteBuffer buffer = ByteBuffer.allocate((int) channel.size());
            channel.read(buffer);
            buffer.flip();

            // Header 1
            byte[] verBytes = new byte[8];
            buffer.get(verBytes);
            props.put("version", new String(verBytes).trim());
            byte[] pidBytes = new byte[66];
            buffer.position(8);
            buffer.get(pidBytes);
            props.put("patient_id", new String(pidBytes).trim());
            buffer.position(85);
            props.put("weight", buffer.get());
            props.put("height", buffer.get());
            buffer.position(87);
            int byte87 = buffer.get() & 0xFF;
            props.put("gender", (byte87 & 0x03) == 1 ? "Male" : (byte87 & 0x03) == 2 ? "Female" : "Unknown");
            buffer.position(184);
            props.put("header_len", buffer.getShort() * 256);
            buffer.position(236);
            props.put("nrec", buffer.getLong());
            buffer.position(252);
            props.put("ns", buffer.getShort() & 0xFFFF);

            // Header 2 (first channel)
            int chOffset = 256;
            buffer.position(chOffset);
            byte[] labelBytes = new byte[16];
            buffer.get(labelBytes);
            props.put("ch1_label", new String(labelBytes).trim());
            buffer.position(chOffset + 104);
            props.put("ch1_phys_min", buffer.getDouble());
            buffer.position(chOffset + 112);
            props.put("ch1_phys_max", buffer.getDouble());
            buffer.position(chOffset + 220);
            props.put("ch1_gdftyp", buffer.getInt());
            buffer.position(chOffset + 216);
            props.put("ch1_spr", buffer.getInt());

            // Print
            System.out.println(props);
        }
    }

    public void write(Map<String, Object> inputProps, String outputFilename) throws IOException {
        // Simplified Header 1 reconstruction
        ByteBuffer header1 = ByteBuffer.allocate(256).order(ByteOrder.LITTLE_ENDIAN);
        header1.put("GDF 2.10".getBytes()).put("X".repeat(66).getBytes());
        header1.put((byte) inputProps.getOrDefault("weight", (byte) 0));
        // ... Pack others
        header1.putShort((short) ((int) inputProps.getOrDefault("header_len", 512) / 256));
        header1.flip();
        header1.rewind();  // Pad remaining with 0s implicitly

        try (FileOutputStream fos = new FileOutputStream(outputFilename);
             FileChannel channel = fos.getChannel()) {
            channel.write(header1);
            // Extend for full headers/data
        }
        System.out.println("Wrote " + outputFilename);
    }

    // Usage: new GDFHandler("sample.gdf").read();
}

6. JavaScript Class for .GDF Read/Write and Property Dump

Node.js version using fs and Buffer. Read parses and console.logs; write reconstructs (simplified). For browser, adapt to ArrayBuffer/DataView as in #3.

const fs = require('fs');

class GDFHandler {
  constructor(filename = null) {
    this.filename = filename;
    this.props = {};
  }

  read() {
    const buffer = fs.readFileSync(this.filename);

    // Header 1
    this.props.version = buffer.slice(0, 8).toString('ascii').replace(/\0/g, '');
    this.props.patient_id = buffer.slice(8, 74).toString('ascii', { encoding: 'ascii' }).replace(/\0/g, '');
    this.props.weight = buffer.readUInt8(85);
    this.props.height = buffer.readUInt8(86);
    const byte87 = buffer.readUInt8(87);
    this.props.gender = (byte87 & 0x03) === 1 ? 'Male' : (byte87 & 0x03) === 2 ? 'Female' : 'Unknown';
    this.props.header_len = buffer.readUInt16LE(184) * 256;
    this.props.nrec = buffer.readBigInt64LE(236);
    this.props.ns = buffer.readUInt16LE(252);

    // Header 2 (first channel)
    const chOffset = 256;
    this.props.ch1_label = buffer.slice(chOffset, chOffset + 16).toString('ascii').replace(/\0/g, '');
    this.props.ch1_phys_min = buffer.readDoubleLE(chOffset + 104);
    this.props.ch1_phys_max = buffer.readDoubleLE(chOffset + 112);
    this.props.ch1_gdftyp = buffer.readUInt32LE(chOffset + 220);
    this.props.ch1_spr = buffer.readUInt32LE(chOffset + 216);

    console.log(JSON.stringify(this.props, null, 2));
  }

  write(props, outputFilename) {
    // Simplified Header 1
    let header1 = Buffer.from('GDF 2.10');
    header1 = Buffer.concat([header1, Buffer.from('X'.repeat(66))]);
    header1.writeUInt8(props.weight || 0, 85);
    // ... Write others
    header1.writeUInt16LE((props.header_len || 512) / 256, 184);
    header1 = Buffer.alloc(256, 0);  // Pad/reset for full
    header1.write('GDF 2.10', 0, 'ascii');  // Repack

    fs.writeFileSync(outputFilename, header1);  // Extend for full
    console.log(`Wrote ${outputFilename}`);
  }
}

// Usage
const handler = new GDFHandler('sample.gdf');
handler.read();
// handler.write({ weight: 70, ... }, 'output.gdf');

7. C Class (Struct) for .GDF Read/Write and Property Dump

Uses fread/fwrite. Compile with gcc gdf.c -o gdf. Read prints to stdout; write reconstructs (simplified).

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

typedef struct {
    char version[8];
    char patient_id[66];
    uint8_t weight;
    uint8_t height;
    char gender[10];  // Derived
    uint16_t header_len;
    int64_t nrec;
    uint16_t ns;
    char ch1_label[16];
    double ch1_phys_min;
    double ch1_phys_max;
    uint32_t ch1_gdftyp;
    uint32_t ch1_spr;
} GDFProps;

typedef struct {
    FILE *file;
    GDFProps props;
} GDFHandler;

void gdf_read(GDFHandler *h) {
    fread(h->props.version, 1, 8, h->file);
    h->props.version[7] = '\0';
    fseek(h->file, 8, SEEK_SET);
    fread(h->props.patient_id, 1, 66, h->file);
    h->props.patient_id[65] = '\0';
    fseek(h->file, 85, SEEK_SET);
    fread(&h->props.weight, 1, 1, h->file);
    fread(&h->props.height, 1, 1, h->file);
    uint8_t byte87;
    fread(&byte87, 1, 1, h->file);
    strcpy(h->props.gender, (byte87 & 0x03) == 1 ? "Male" : (byte87 & 0x03) == 2 ? "Female" : "Unknown");
    fseek(h->file, 184, SEEK_SET);
    fread(&h->props.header_len, 2, 1, h->file);
    h->props.header_len *= 256;
    fseek(h->file, 236, SEEK_SET);
    fread(&h->props.nrec, 8, 1, h->file);
    fseek(h->file, 252, SEEK_SET);
    fread(&h->props.ns, 2, 1, h->file);

    // Header 2
    long ch_offset = 256;
    fseek(h->file, ch_offset, SEEK_SET);
    fread(h->props.ch1_label, 1, 16, h->file);
    h->props.ch1_label[15] = '\0';
    fseek(h->file, ch_offset + 104, SEEK_SET);
    fread(&h->props.ch1_phys_min, 8, 1, h->file);
    fread(&h->props.ch1_phys_max, 8, 1, h->file);
    fseek(h->file, ch_offset + 220, SEEK_SET);
    fread(&h->props.ch1_gdftyp, 4, 1, h->file);
    fseek(h->file, ch_offset + 216, SEEK_SET);
    fread(&h->props.ch1_spr, 4, 1, h->file);

    // Print (simplified)
    printf("Version: %s\nPatient ID: %s\nWeight: %u\nHeight: %u\nGender: %s\nHeader Len: %u\nNREC: %ld\nNS: %u\nCh1 Label: %s\nCh1 Phys Min: %f\nCh1 Phys Max: %f\nCh1 GDFTyp: %u\nCh1 SPR: %u\n",
           h->props.version, h->props.patient_id, h->props.weight, h->props.height, h->props.gender,
           h->props.header_len, h->props.nrec, h->props.ns, h->props.ch1_label, h->props.ch1_phys_min,
           h->props.ch1_phys_max, h->props.ch1_gdftyp, h->props.ch1_spr);
}

void gdf_write(GDFHandler *h, const char *output_filename) {
    FILE *out = fopen(output_filename, "wb");
    // Simplified Header 1
    fwrite("GDF 2.10", 1, 8, out);
    fwrite("X", 1, 66, out);  // Patient placeholder
    fputc(h->props.weight, out);
    // ... Write others with fwrite
    uint16_t hlen = h->props.header_len / 256;
    fwrite(&hlen, 2, 1, out);
    fwrite("\0", 1, 256 - ftell(out) % 256, out);  // Pad

    fclose(out);
    printf("Wrote %s\n", output_filename);
}

int main(int argc, char **argv) {
    if (argc < 2) return 1;
    GDFHandler h = { .file = fopen(argv[1], "rb") };
    if (!h.file) return 1;
    gdf_read(&h);
    fclose(h.file);
    // gdf_write(&h, "output.gdf");
    return 0;
}