European Data Format (EDF) conversion#
Install NeuroConv with the additional dependencies necessary for reading EDF data.
pip install "neuroconv[edf]"
Converting EDF Electrode Channels#
The EDFRecordingInterface is designed specifically for electrode recording channels that will be stored as an ElectricalSeries in the NWB file.
Other auxiliary signal channels should be excluded using the channels_to_skip parameter.
from datetime import datetime
from zoneinfo import ZoneInfo
from pathlib import Path
from neuroconv.datainterfaces import EDFRecordingInterface
file_path = f"{ECEPHY_DATA_PATH}/edf/electrode_and_analog_data/electrode_and_analog_data.edf"
# Get all channel IDs to identify which ones to skip
all_channels = EDFRecordingInterface.get_available_channel_ids(file_path)
print(f"Available channels: {all_channels}")
# Identify non-electrode channels that should be skipped
# We don't have an automatic way to detect non-electrode channels, user passes this knowledge here
channels_to_skip = ["TRIG", "OSAT", "PR", "Pleth"] # Example: trigger and physiological monitoring
# Create interface with channels to skip
interface = EDFRecordingInterface(
file_path=file_path,
channels_to_skip=channels_to_skip
)
# Extract what metadata we can from the source files
metadata = interface.get_metadata()
# For data provenance we add the time zone information to the conversion
session_start_time = metadata["NWBFile"]["session_start_time"].replace(tzinfo=ZoneInfo("US/Pacific"))
metadata["NWBFile"].update(session_start_time=session_start_time)
# Choose a path for saving the nwb file and run the conversion
nwbfile_path = f"{path_to_save_nwbfile}"
interface.run_conversion(nwbfile_path=nwbfile_path, metadata=metadata, overwrite=True)
Converting Auxiliary EDF Channels as TimeSeries#
Auxiliary signals such as physiological monitoring signals, triggers, or auxiliary data should be stored as generic TimeSeries objects
rather than ElectricalSeries. Use the dedicated EDFAnalogInterface
to handle these channels.
Important: TimeSeries objects in PyNWB require all channels to have the same physical unit. If your auxiliary channels have different units (e.g., triggers with no unit, OSAT in %, PR in bpm), you’ll need to create separate EDFAnalogInterface instances for each unit type:
from datetime import datetime
from zoneinfo import ZoneInfo
from pathlib import Path
from neuroconv.datainterfaces import EDFAnalogInterface
file_path = f"{ECEPHY_DATA_PATH}/edf/electrode_and_analog_data/electrode_and_analog_data.edf"
# Example: Trigger channels (no unit)
trigger_channels = ["TRIG"] # Trigger signals
interface = EDFAnalogInterface(
file_path=file_path,
channels_to_include=trigger_channels
)
# Extract metadata and add timezone information
metadata = interface.get_metadata()
# For data provenance we add the time zone information to the conversion
session_start_time = metadata["NWBFile"]["session_start_time"].replace(tzinfo=ZoneInfo("US/Pacific"))
metadata["NWBFile"].update(session_start_time=session_start_time)
# Choose a path for saving the nwb file and run the conversion
nwbfile_path = f"{path_to_save_nwbfile}"
interface.run_conversion(nwbfile_path=nwbfile_path, metadata=metadata, overwrite=True)
Combining Electrode and Auxiliary Channels#
To convert both electrode and auxiliary channels into a single NWB file, use the ConverterPipe with multiple interfaces. Remember to group auxiliary channels by their unit types:
from datetime import datetime
from zoneinfo import ZoneInfo
from pathlib import Path
from neuroconv import ConverterPipe
from neuroconv.datainterfaces import EDFRecordingInterface, EDFAnalogInterface
from neuroconv.utils import dict_deep_update
file_path = f"{ECEPHY_DATA_PATH}/edf/electrode_and_analog_data/electrode_and_analog_data.edf"
# Define the channels to process
all_non_electrical_channels = ["TRIG", "OSAT", "PR", "Pleth"] # All auxiliary channels
# Create electrode interface (skip all auxiliary channels)
recording_interface = EDFRecordingInterface(
file_path=file_path,
channels_to_skip=all_auxiliary_channels,
)
# Create separate analog interfaces for each unit type
trigger_channels_metadata_key = "time_series_trigger" # No unit
trigger_interface = EDFAnalogInterface(
file_path=file_path,
channels_to_include=["TRIG"], # No unit
metadata_key=trigger_channels_metadata_key
)
percent_channels_metadata_key = "time_series_oxygen" # Percentage unit
percent_interface = EDFAnalogInterface(
file_path=file_path,
channels_to_include=["OSAT"], # Percentage units
metadata_key=percent_channels_metadata_key,
)
# Combine all interfaces
converter = ConverterPipe(
data_interfaces=[recording_interface, trigger_interface, percent_interface],
)
# Extract metadata and add timezone information
metadata = converter.get_metadata()
# For data provenance we add the time zone information to the conversion
session_start_time = metadata["NWBFile"]["session_start_time"].replace(tzinfo=ZoneInfo("US/Pacific"))
metadata["NWBFile"].update(session_start_time=session_start_time)
# REQUIRED: Customize TimeSeries names when using multiple analog interfaces
user_edited_metadata = {
"TimeSeries": {
trigger_channels_metadata_key: {
"name": "TimeSeriesTrigger",
"description": "Trigger signals from EDF file"
},
percent_channels_metadata_key: {
"name": "TimeSeriesOxygen",
"description": "Oxygen saturation monitoring data"
}
}
}
# The metadata_key parameter ensures each interface creates entries with the correct names
metadata = dict_deep_update(metadata, user_edited_metadata)
# Convert all channel types to a single NWB file
nwbfile_path = f"{path_to_save_nwbfile}"
converter.run_conversion(nwbfile_path=nwbfile_path, metadata=metadata, overwrite=True)