Nicolet .e/.eeg → EDF
A Python tool to convert Nicolet/Nervus .e EEG files into standard EDF+ format. No vendor DLLs, no MATLAB (which costs money!), just Python! I couldn't find a native Python way to get .e files out of their vendor format, so me and Opus 4.5 wrote this.
Acknowledgment: This project wouldn't exist without the excellent FieldTrip toolbox. Their MATLAB implementation of the Nervus/Nicolet file format (
read_nervus_header.mandread_nervus_data.m) was the foundation for this Python port. Since then, we've added substantial GUID/event and channel ID parsing logic through our own reverse‑engineering work. Thank you to the FieldTrip team!
Note: Some of our reverse‑engineered event labels are (unfortunately) in Norwegian.
Quick Start
Clone the repository (or download it as a ZIP) from GitHub:
git clone https://github.com/haukurtg/e2edfconverter.git
cd e2edfconverter
The easiest way — no manual environment/dependency setup needed (uv handles it for you):
# Install uv if you don't have it (https://docs.astral.sh/uv/)
brew install uv # or: curl -LsSf https://astral.sh/uv/install.sh | sh
# Convert a single file
uv run --isolated nicolet-e2edf --in /path/to/recording.e --out ./edf_output
# Convert a folder of .e/.eeg files
uv run --isolated nicolet-e2edf --in ./my_eeg_folder --out ./edf_output
If you want a local environment for repeated use/development:
uv sync
uv run nicolet-e2edf --help
Optional faster reads (0.4.0)
Conversion got faster in 0.4.0 without changing the output bytes. There is also an experimental reader that merges adjacent disk reads into bigger ones (capped at 8 MiB per read). It produces identical output but is off by default for now; turn it on if you want the extra speed:
NICOLET_E2EDF_COALESCE_READS=1 uv run --isolated nicolet-e2edf \
--in /path/to/recording.e --out ./edf_output
or from Python: read_nervus_data(path, header, coalesce_reads=True).
Interactive Mode
For a guided experience with menus and progress bars:
uv run --isolated --with rich nicolet-e2edf --ui
CLI Options
| Option | Description |
|---|---|
--in |
Input .e/.eeg file or folder |
--out |
Output directory for EDF files |
--glob |
Filter pattern when input is a folder (e.g. recording_*) |
--json-sidecar |
Also emit a .json with metadata (channels, events, etc.) |
--split-by-segment |
Output one EDF per segment if the recording contains multiple segments |
--vendor-style |
Suppress system events to better match vendor EDF exports |
--resample-to |
Resample to a specific rate (Hz) (requires scipy) |
--lowcut |
High-pass filter cutoff in Hz (requires scipy) |
--highcut |
Low-pass filter cutoff in Hz (requires scipy) |
--notch |
Notch filter for powerline noise, e.g. 50 or 60 Hz (requires scipy) |
--ui |
Launch interactive terminal UI (requires rich) |
--verbose |
Show detailed logging |
Filtering example:
# Clinical defaults: 0.5–35 Hz bandpass + 50 Hz notch
uv run --isolated --with scipy nicolet-e2edf \
--in ./data --out ./edf_output \
--lowcut 0.5 --highcut 35 --notch 50
Vendor-style comparison example:
# Match vendor-style exports (split per segment + suppress system events)
uv run --isolated nicolet-e2edf \
--in /path/to/recording.e --out ./edf_output \
--split-by-segment --vendor-style --json-sidecar
Viewing the Results
There's a bundled viewer script that shows your EDF in a double-banana montage:
uv run --isolated --with mne python inspect_edf.py ./edf_output/recording.edf
Note: When using the interactive TUI (--ui), the viewer is automatically launched with MNE in an isolated environment if needed. No manual installation required!
Options: --lowcut, --highcut, --notch, --snapshot out.png (for headless systems).
Filtering during conversion (--lowcut, --highcut, --notch) is lossy. In most cases, keep exports unfiltered and only use conversion-time filtering when you intentionally want a preprocessed output for direct downstream use (for example, an ML pipeline).
Limitations
- Mixed sampling rates: default exports only dominant-rate channels; use
--resample-toto include all "on" channels. - When
--resample-tois used, channels are resampled to the requested integer EDF rate. - Events are written as EDF+ annotations
- EVENTTYPEINFOGUID labels are reverse-engineered; unknown GUIDs may be exported as UNKNOWN.
.eegsupport is currently not reliable; we need a larger.eegdataset to implement and validate it properly.- Some
.erecordings store only numeric channel IDs (e.g.,1..64). The numeric-channel fix and montage-recovery strategy (fromv0.2.5) are mainly aimed at recovering channel names in atypical multi-channel EEG setups (32,64,128, etc.) using source montage derivations, fixed DERIVATION tables, and hidden montage catalogs. - The CLI supports folder input, but processes files serially. For large cohorts, it is usually more efficient to call the CLI from a small batch wrapper that runs multiple workers and tracks progress/errors.
Contributing
Contributions are welcome! If you're working on the EDF writer or want to understand the file format:
- EDF+ Specification: A copy of the full EDF+ specification is included at
docs/EDF+ specification.pdf. The official spec is also available at edfplus.info. - Tests: Run
uv run pytestto verify EDF+ compliance. We use PyEDFlib as a strict validator.
Profiling And Regression Checks
Two helper scripts are included for speed work that must not change output:
tools/profile_conversion_stages.py- Runs an in-process conversion profile for one or more
.efiles. - Breaks runtime into rough stages such as header read, waveform read, EDF write, and JSON write.
- Runs an in-process conversion profile for one or more
tools/validate_regression_equivalence.py- Re-converts a regression corpus and compares the result against a known-good baseline.
- Checks EDF byte equality plus exact equality of sidecar
channels,events, andannotations(ignoring only the expectededf_fileoutput path field).
Recommended workflow for performance changes:
- Profile on a small representative local corpus.
- Make the optimization.
- Run
uv run pytest. - Run the regression-equivalence validator before merging.
License
GPL-3.0 — see LICENSE.
This project adapts logic from the FieldTrip toolbox (GPL-3.0).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nicolet_e2edf-0.4.0.tar.gz.
File metadata
- Download URL: nicolet_e2edf-0.4.0.tar.gz
- Upload date:
- Size: 126.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
adef9090713df209bebbe7a15b80d5ef680c6e355a5ebd588f287bc99ec10c77
|
|
| MD5 |
1e3ed7988926f34832d5116dcdd84d57
|
|
| BLAKE2b-256 |
193965151bd181cbbf50de9f1b645fa501b289b0d038691c862628e821e12392
|
Provenance
The following attestation bundles were made for nicolet_e2edf-0.4.0.tar.gz:
Publisher:
publish-pypi.yml on haukurtg/e2edfconverter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nicolet_e2edf-0.4.0.tar.gz -
Subject digest:
adef9090713df209bebbe7a15b80d5ef680c6e355a5ebd588f287bc99ec10c77 - Sigstore transparency entry: 2341954595
- Sigstore integration time:
-
Permalink:
haukurtg/e2edfconverter@527e295cca964b247ff35f73e3b17ff4a41cb2dd -
Branch / Tag:
refs/heads/main - Owner: https://github.com/haukurtg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@527e295cca964b247ff35f73e3b17ff4a41cb2dd -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file nicolet_e2edf-0.4.0-py3-none-any.whl.
File metadata
- Download URL: nicolet_e2edf-0.4.0-py3-none-any.whl
- Upload date:
- Size: 123.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6167d07016bea1162f0ee793dd3c5a260520957a9fe8b3fb873ba212c68396f3
|
|
| MD5 |
c5555d2f4bd220cf22a80641db81bd2d
|
|
| BLAKE2b-256 |
4f7b476aec981fd9fc2c06cceee31490d27ceb181ae6c6a718edf4cab0cbe536
|
Provenance
The following attestation bundles were made for nicolet_e2edf-0.4.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on haukurtg/e2edfconverter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nicolet_e2edf-0.4.0-py3-none-any.whl -
Subject digest:
6167d07016bea1162f0ee793dd3c5a260520957a9fe8b3fb873ba212c68396f3 - Sigstore transparency entry: 2341954660
- Sigstore integration time:
-
Permalink:
haukurtg/e2edfconverter@527e295cca964b247ff35f73e3b17ff4a41cb2dd -
Branch / Tag:
refs/heads/main - Owner: https://github.com/haukurtg
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@527e295cca964b247ff35f73e3b17ff4a41cb2dd -
Trigger Event:
workflow_dispatch
-
Statement type: