or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

edf-reading.mdedf-writing.mdhigh-level-functions.mdindex.mdlow-level-interface.md

low-level-interface.mddocs/

0

# Low-Level Interface

1

2

Direct access to underlying C library functions for maximum performance and fine-grained control over EDF/BDF file operations and data handling. These functions provide the foundation for the high-level classes and enable custom implementations with minimal overhead.

3

4

## Capabilities

5

6

### Low-Level Reader Class

7

8

Direct interface to the underlying C-based EDF reader with minimal Python overhead.

9

10

```python { .api }

11

class CyEdfReader:

12

def __init__(self, file_name: str, annotations_mode: int = READ_ANNOTATIONS,

13

check_file_size: int = CHECK_FILE_SIZE):

14

"""

15

Initialize low-level EDF reader.

16

17

Parameters:

18

- file_name: str, path to EDF/BDF file

19

- annotations_mode: int, annotation reading mode

20

- check_file_size: int, file size checking mode

21

"""

22

23

def open(self, file_name: str, annotations_mode: int = READ_ANNOTATIONS,

24

check_file_size: int = CHECK_FILE_SIZE) -> bool:

25

"""

26

Open EDF file for reading.

27

28

Parameters:

29

- file_name: str, path to file

30

- annotations_mode: int, annotation mode

31

- check_file_size: int, file size check mode

32

33

Returns:

34

bool: True if successful

35

"""

36

37

def _close(self):

38

"""Close file and release resources."""

39

40

def read_digital_signal(self, signalnum: int, start: int, n: int, sigbuf: np.ndarray):

41

"""

42

Read digital signal data directly into buffer.

43

44

Parameters:

45

- signalnum: int, signal number

46

- start: int, starting sample

47

- n: int, number of samples

48

- sigbuf: numpy.ndarray, output buffer (pre-allocated)

49

"""

50

51

def readsignal(self, signalnum: int, start: int, n: int, sigbuf: np.ndarray):

52

"""

53

Read physical signal data directly into buffer.

54

55

Parameters:

56

- signalnum: int, signal number

57

- start: int, starting sample

58

- n: int, number of samples

59

- sigbuf: numpy.ndarray, output buffer (pre-allocated)

60

"""

61

62

def read_annotation(self) -> List[List[str]]:

63

"""

64

Read annotations as list of string lists.

65

66

Returns:

67

List[List[str]]: Annotation data

68

"""

69

70

def load_datarecord(self, db: np.ndarray, n: int = 1):

71

"""

72

Load data records directly.

73

74

Parameters:

75

- db: numpy.ndarray, data buffer

76

- n: int, number of records to load

77

"""

78

```

79

80

### CyEdfReader Properties

81

82

Direct access to file metadata through properties (read-only).

83

84

```python { .api }

85

# File properties

86

@property

87

def file_name(self) -> str: ...

88

@property

89

def handle(self) -> int: ...

90

@property

91

def datarecords_in_file(self) -> int: ...

92

@property

93

def signals_in_file(self) -> int: ...

94

@property

95

def file_duration(self) -> int: ...

96

@property

97

def filetype(self) -> int: ...

98

@property

99

def datarecord_duration(self) -> int: ...

100

@property

101

def annotations_in_file(self) -> int: ...

102

103

# Patient properties

104

@property

105

def patient(self) -> str: ...

106

@property

107

def patientcode(self) -> str: ...

108

@property

109

def sex(self) -> int: ...

110

@property

111

def gender(self) -> int: ...

112

@property

113

def birthdate(self) -> str: ...

114

@property

115

def patientname(self) -> str: ...

116

@property

117

def patient_additional(self) -> str: ...

118

119

# Recording properties

120

@property

121

def recording(self) -> str: ...

122

@property

123

def startdate_year(self) -> int: ...

124

@property

125

def startdate_month(self) -> int: ...

126

@property

127

def startdate_day(self) -> int: ...

128

@property

129

def starttime_hour(self) -> int: ...

130

@property

131

def starttime_minute(self) -> int: ...

132

@property

133

def starttime_second(self) -> int: ...

134

@property

135

def starttime_subsecond(self) -> int: ...

136

@property

137

def admincode(self) -> str: ...

138

@property

139

def technician(self) -> str: ...

140

@property

141

def equipment(self) -> str: ...

142

@property

143

def recording_additional(self) -> str: ...

144

```

145

146

### CyEdfReader Signal Methods

147

148

Access signal-specific information using method calls with signal index.

149

150

```python { .api }

151

def signal_label(self, signalnum: int) -> str:

152

"""Get signal label."""

153

154

def samples_in_file(self, signalnum: int) -> int:

155

"""Get total samples in file for signal."""

156

157

def samples_in_datarecord(self, signalnum: int) -> int:

158

"""Get samples per data record for signal."""

159

160

def physical_dimension(self, signalnum: int) -> str:

161

"""Get physical dimension (units) for signal."""

162

163

def physical_max(self, signalnum: int) -> float:

164

"""Get physical maximum value for signal."""

165

166

def physical_min(self, signalnum: int) -> float:

167

"""Get physical minimum value for signal."""

168

169

def digital_max(self, signalnum: int) -> int:

170

"""Get digital maximum value for signal."""

171

172

def digital_min(self, signalnum: int) -> int:

173

"""Get digital minimum value for signal."""

174

175

def prefilter(self, signalnum: int) -> str:

176

"""Get prefilter description for signal."""

177

178

def transducer(self, signalnum: int) -> str:

179

"""Get transducer description for signal."""

180

181

def samplefrequency(self, signalnum: int) -> float:

182

"""Get sampling frequency for signal."""

183

184

def smp_per_record(self, signalnum: int) -> int:

185

"""Get samples per record for signal."""

186

```

187

188

Usage example:

189

190

```python

191

import pyedflib

192

import numpy as np

193

194

# Open file with low-level reader

195

reader = pyedflib.CyEdfReader('data.edf')

196

197

# Get file info

198

print(f"File: {reader.file_name}")

199

print(f"Signals: {reader.signals_in_file}")

200

print(f"Duration: {reader.file_duration} seconds")

201

print(f"Patient: {reader.patientname}")

202

203

# Read signal data directly into pre-allocated buffer

204

signal_samples = reader.samples_in_file(0)

205

buffer = np.zeros(signal_samples, dtype=np.float64)

206

reader.readsignal(0, 0, signal_samples, buffer)

207

208

# Get signal properties

209

label = reader.signal_label(0)

210

sfreq = reader.samplefrequency(0)

211

phys_min = reader.physical_min(0)

212

phys_max = reader.physical_max(0)

213

214

print(f"Signal 0: {label}, {sfreq} Hz, range [{phys_min}, {phys_max}]")

215

216

reader._close()

217

```

218

219

### File Operations

220

221

Low-level file management functions for writing operations.

222

223

```python { .api }

224

def lib_version() -> str:

225

"""

226

Get EDFlib version.

227

228

Returns:

229

str: Library version string

230

"""

231

232

def open_file_writeonly(path: str, filetype: int, number_of_signals: int) -> int:

233

"""

234

Open file for writing.

235

236

Parameters:

237

- path: str, file path

238

- filetype: int, file type constant

239

- number_of_signals: int, number of signal channels

240

241

Returns:

242

int: File handle (>0 on success, <0 on error)

243

"""

244

245

def close_file(handle: int) -> int:

246

"""

247

Close file by handle.

248

249

Parameters:

250

- handle: int, file handle from open_file_writeonly

251

252

Returns:

253

int: 0 on success, error code on failure

254

"""

255

256

def get_number_of_open_files() -> int:

257

"""

258

Get number of currently open files.

259

260

Returns:

261

int: Number of open files

262

"""

263

264

def get_handle(file_number: int) -> int:

265

"""

266

Get handle for file number.

267

268

Parameters:

269

- file_number: int, file number

270

271

Returns:

272

int: File handle

273

"""

274

275

def is_file_used(path: str) -> bool:

276

"""

277

Check if file is currently in use.

278

279

Parameters:

280

- path: str, file path

281

282

Returns:

283

bool: True if file is in use

284

"""

285

```

286

287

### Data Reading Functions

288

289

Low-level functions for reading signal data with direct buffer access.

290

291

```python { .api }

292

def read_int_samples(handle: int, edfsignal: int, n: int, buf: np.ndarray) -> int:

293

"""

294

Read integer samples directly into buffer.

295

296

Parameters:

297

- handle: int, file handle

298

- edfsignal: int, signal number

299

- n: int, number of samples to read

300

- buf: numpy.ndarray, pre-allocated buffer

301

302

Returns:

303

int: Number of samples read

304

"""

305

306

def read_physical_samples(handle: int, edfsignal: int, n: int, buf: np.ndarray) -> int:

307

"""

308

Read physical samples directly into buffer.

309

310

Parameters:

311

- handle: int, file handle

312

- edfsignal: int, signal number

313

- n: int, number of samples to read

314

- buf: numpy.ndarray, pre-allocated buffer

315

316

Returns:

317

int: Number of samples read

318

"""

319

320

def get_annotation(handle: int, n: int, edf_annotation: EdfAnnotation) -> int:

321

"""

322

Get annotation by index.

323

324

Parameters:

325

- handle: int, file handle

326

- n: int, annotation index

327

- edf_annotation: EdfAnnotation, annotation object to populate

328

329

Returns:

330

int: 0 on success, error code on failure

331

"""

332

```

333

334

### Data Writing Functions

335

336

Low-level functions for writing signal data with different data types and block modes.

337

338

```python { .api }

339

def write_digital_samples(handle: int, buf: np.ndarray) -> int:

340

"""

341

Write digital samples to file.

342

343

Parameters:

344

- handle: int, file handle

345

- buf: numpy.ndarray, digital sample data

346

347

Returns:

348

int: Number of samples written

349

"""

350

351

def write_digital_short_samples(handle: int, buf: np.ndarray) -> int:

352

"""

353

Write digital short samples to file.

354

355

Parameters:

356

- handle: int, file handle

357

- buf: numpy.ndarray, digital short sample data

358

359

Returns:

360

int: Number of samples written

361

"""

362

363

def write_physical_samples(handle: int, buf: np.ndarray) -> int:

364

"""

365

Write physical samples to file.

366

367

Parameters:

368

- handle: int, file handle

369

- buf: numpy.ndarray, physical sample data

370

371

Returns:

372

int: Number of samples written

373

"""

374

375

def blockwrite_digital_samples(handle: int, buf: np.ndarray) -> int:

376

"""

377

Block write digital samples.

378

379

Parameters:

380

- handle: int, file handle

381

- buf: numpy.ndarray, digital sample data

382

383

Returns:

384

int: Number of samples written

385

"""

386

387

def blockwrite_digital_short_samples(handle: int, buf: np.ndarray) -> int:

388

"""

389

Block write digital short samples.

390

391

Parameters:

392

- handle: int, file handle

393

- buf: numpy.ndarray, digital short sample data

394

395

Returns:

396

int: Number of samples written

397

"""

398

399

def blockwrite_physical_samples(handle: int, buf: np.ndarray) -> int:

400

"""

401

Block write physical samples.

402

403

Parameters:

404

- handle: int, file handle

405

- buf: numpy.ndarray, physical sample data

406

407

Returns:

408

int: Number of samples written

409

"""

410

```

411

412

### File Header Setting Functions

413

414

Low-level functions for configuring file-level header information.

415

416

```python { .api }

417

def set_patientcode(handle: int, patientcode: Union[str, bytes]) -> int:

418

"""Set patient identification code."""

419

420

def set_patientname(handle: int, name: Union[str, bytes]) -> int:

421

"""Set patient name."""

422

423

def set_patient_additional(handle: int, patient_additional: Union[str, bytes]) -> int:

424

"""Set additional patient information."""

425

426

def set_admincode(handle: int, admincode: Union[str, bytes]) -> int:

427

"""Set administration code."""

428

429

def set_technician(handle: int, technician: Union[str, bytes]) -> int:

430

"""Set technician name."""

431

432

def set_equipment(handle: int, equipment: Union[str, bytes]) -> int:

433

"""Set equipment description."""

434

435

def set_recording_additional(handle: int, recording_additional: Union[str, bytes]) -> int:

436

"""Set additional recording information."""

437

438

def set_birthdate(handle: int, birthdate_year: int, birthdate_month: int,

439

birthdate_day: int) -> int:

440

"""

441

Set patient birthdate.

442

443

Parameters:

444

- handle: int, file handle

445

- birthdate_year: int, birth year

446

- birthdate_month: int, birth month (1-12)

447

- birthdate_day: int, birth day (1-31)

448

449

Returns:

450

int: 0 on success, error code on failure

451

"""

452

453

def set_sex(handle: int, sex: Optional[int]) -> int:

454

"""

455

Set patient sex.

456

457

Parameters:

458

- handle: int, file handle

459

- sex: int or None, sex code (0=female, 1=male)

460

461

Returns:

462

int: 0 on success, error code on failure

463

"""

464

465

def set_startdatetime(handle: int, startdate_year: int, startdate_month: int,

466

startdate_day: int, starttime_hour: int, starttime_minute: int,

467

starttime_second: int) -> int:

468

"""

469

Set recording start date and time.

470

471

Parameters:

472

- handle: int, file handle

473

- startdate_year: int, start year

474

- startdate_month: int, start month (1-12)

475

- startdate_day: int, start day (1-31)

476

- starttime_hour: int, start hour (0-23)

477

- starttime_minute: int, start minute (0-59)

478

- starttime_second: int, start second (0-59)

479

480

Returns:

481

int: 0 on success, error code on failure

482

"""

483

484

def set_starttime_subsecond(handle: int, subsecond: int) -> int:

485

"""

486

Set subsecond precision for start time.

487

488

Parameters:

489

- handle: int, file handle

490

- subsecond: int, subsecond value

491

492

Returns:

493

int: 0 on success, error code on failure

494

"""

495

496

def set_datarecord_duration(handle: int, duration: Union[int, float]) -> int:

497

"""

498

Set data record duration.

499

500

Parameters:

501

- handle: int, file handle

502

- duration: int or float, record duration in seconds

503

504

Returns:

505

int: 0 on success, error code on failure

506

"""

507

508

def set_number_of_annotation_signals(handle: int, annot_signals: int) -> int:

509

"""

510

Set number of annotation signals.

511

512

Parameters:

513

- handle: int, file handle

514

- annot_signals: int, annotation signal count

515

516

Returns:

517

int: 0 on success, error code on failure

518

"""

519

```

520

521

### Signal Header Setting Functions

522

523

Low-level functions for configuring signal-specific header information.

524

525

```python { .api }

526

def set_label(handle: int, edfsignal: int, label: Union[str, bytes]) -> int:

527

"""

528

Set signal label.

529

530

Parameters:

531

- handle: int, file handle

532

- edfsignal: int, signal number

533

- label: str or bytes, signal label

534

535

Returns:

536

int: 0 on success, error code on failure

537

"""

538

539

def set_physical_dimension(handle: int, edfsignal: int, phys_dim: Union[str, bytes]) -> int:

540

"""Set physical dimension (units) for signal."""

541

542

def set_physical_maximum(handle: int, edfsignal: int, phys_max: float) -> int:

543

"""Set physical maximum value for signal."""

544

545

def set_physical_minimum(handle: int, edfsignal: int, phys_min: float) -> int:

546

"""Set physical minimum value for signal."""

547

548

def set_digital_maximum(handle: int, edfsignal: int, dig_max: int) -> int:

549

"""Set digital maximum value for signal."""

550

551

def set_digital_minimum(handle: int, edfsignal: int, dig_min: int) -> int:

552

"""Set digital minimum value for signal."""

553

554

def set_samples_per_record(handle: int, edfsignal: int, smp_per_record: int) -> int:

555

"""

556

Set samples per record for signal.

557

558

Parameters:

559

- handle: int, file handle

560

- edfsignal: int, signal number

561

- smp_per_record: int, samples per data record

562

563

Returns:

564

int: 0 on success, error code on failure

565

"""

566

567

def set_transducer(handle: int, edfsignal: int, transducer: Union[str, bytes]) -> int:

568

"""Set transducer description for signal."""

569

570

def set_prefilter(handle: int, edfsignal: int, prefilter: Union[str, bytes]) -> int:

571

"""Set prefilter description for signal."""

572

```

573

574

### Navigation Functions

575

576

Low-level functions for positioning within signal data streams.

577

578

```python { .api }

579

def tell(handle: int, edfsignal: int) -> int:

580

"""

581

Get current read position in signal.

582

583

Parameters:

584

- handle: int, file handle

585

- edfsignal: int, signal number

586

587

Returns:

588

int: Current sample position

589

"""

590

591

def seek(handle: int, edfsignal: int, offset: int, whence: int) -> int:

592

"""

593

Seek to position in signal.

594

595

Parameters:

596

- handle: int, file handle

597

- edfsignal: int, signal number

598

- offset: int, offset in samples

599

- whence: int, seek mode (0=absolute, 1=relative, 2=from end)

600

601

Returns:

602

int: New position or error code

603

"""

604

605

def rewind(handle: int, edfsignal: int):

606

"""

607

Rewind signal to beginning.

608

609

Parameters:

610

- handle: int, file handle

611

- edfsignal: int, signal number

612

"""

613

```

614

615

### Annotation Functions

616

617

Low-level functions for writing annotations with different text encodings.

618

619

```python { .api }

620

def write_annotation_latin1(handle: int, onset: int, duration: int,

621

description: Union[str, bytes]) -> int:

622

"""

623

Write annotation with Latin-1 encoding.

624

625

Parameters:

626

- handle: int, file handle

627

- onset: int, onset time in 100ns units

628

- duration: int, duration in 100ns units

629

- description: str or bytes, annotation text

630

631

Returns:

632

int: 0 on success, error code on failure

633

"""

634

635

def write_annotation_utf8(handle: int, onset: int, duration: int,

636

description: Union[str, bytes]) -> int:

637

"""

638

Write annotation with UTF-8 encoding.

639

640

Parameters:

641

- handle: int, file handle

642

- onset: int, onset time in 100ns units

643

- duration: int, duration in 100ns units

644

- description: str or bytes, annotation text

645

646

Returns:

647

int: 0 on success, error code on failure

648

"""

649

```

650

651

### Annotation Class

652

653

Low-level annotation data structure.

654

655

```python { .api }

656

class EdfAnnotation:

657

"""

658

EDF annotation data structure.

659

660

Attributes:

661

- onset: int, annotation onset time

662

- duration: int, annotation duration

663

- annotation: str, annotation text

664

"""

665

666

onset: int

667

duration: int

668

annotation: str

669

```

670

671

### Error Handling

672

673

Error code dictionaries for interpreting function return values.

674

675

```python { .api }

676

# Error code mappings

677

open_errors: Dict[int, str] # File opening error codes

678

write_errors: Dict[int, str] # Writing error codes

679

```

680

681

## Complete Low-Level Writing Example

682

683

```python

684

import pyedflib

685

import numpy as np

686

from datetime import datetime

687

688

# Generate sample data

689

n_channels = 2

690

n_samples = 1000

691

sample_rate = 256

692

data = np.random.randn(n_channels, n_samples) * 100 # microvolts

693

694

# Open file for writing

695

handle = pyedflib.open_file_writeonly('lowlevel.edf', pyedflib.FILETYPE_EDFPLUS, n_channels)

696

if handle < 0:

697

raise RuntimeError(f"Failed to open file: {pyedflib.open_errors.get(handle, 'Unknown error')}")

698

699

try:

700

# Set file header

701

pyedflib.set_patientname(handle, b"Test Patient")

702

pyedflib.set_patientcode(handle, b"TP001")

703

pyedflib.set_technician(handle, b"Dr. Test")

704

pyedflib.set_equipment(handle, b"Test System")

705

706

# Set start date/time

707

now = datetime.now()

708

pyedflib.set_startdatetime(handle, now.year, now.month, now.day,

709

now.hour, now.minute, now.second)

710

711

# Set data record duration (1 second)

712

pyedflib.set_datarecord_duration(handle, 1.0)

713

714

# Configure each signal

715

for ch in range(n_channels):

716

pyedflib.set_label(handle, ch, f"CH{ch+1}".encode())

717

pyedflib.set_physical_dimension(handle, ch, b"uV")

718

pyedflib.set_physical_maximum(handle, ch, 500.0)

719

pyedflib.set_physical_minimum(handle, ch, -500.0)

720

pyedflib.set_digital_maximum(handle, ch, 32767)

721

pyedflib.set_digital_minimum(handle, ch, -32768)

722

pyedflib.set_samples_per_record(handle, ch, sample_rate)

723

pyedflib.set_transducer(handle, ch, b"Electrode")

724

pyedflib.set_prefilter(handle, ch, b"HP:0.1Hz")

725

726

# Write data using block write

727

data_interleaved = data.T.flatten() # Interleave channels

728

samples_written = pyedflib.blockwrite_physical_samples(handle, data_interleaved)

729

print(f"Wrote {samples_written} samples")

730

731

# Add annotations

732

onset_100ns = int(2.5 * 1e7) # 2.5 seconds in 100ns units

733

duration_100ns = 0 # Point annotation

734

pyedflib.write_annotation_utf8(handle, onset_100ns, duration_100ns, b"Stimulus")

735

736

finally:

737

# Always close the file

738

result = pyedflib.close_file(handle)

739

if result != 0:

740

print(f"Warning: Error closing file: {pyedflib.write_errors.get(result, 'Unknown error')}")

741

742

print("Low-level EDF file created successfully!")

743

```