bci_essentials.io.xdf_sources
1import pyxdf 2 3from .sources import EegSource, MarkerSource 4from ..utils.logger import Logger # Logger wrapper 5 6# Instantiate a logger for the module at the default level of logging.INFO 7# Logs to bci_essentials.__module__) where __module__ is the name of the module 8logger = Logger(name=__name__) 9 10__all__ = ["XdfMarkerSource", "XdfEegSource"] 11 12 13class XdfMarkerSource(MarkerSource): 14 def __init__(self, filename: str, stream_type: str = "LSL_Marker_Strings"): 15 """Create a MarkerSource object that obtains markers from an XDF file. 16 17 Parameters 18 ---------- 19 filename : str 20 The full name of file, including path. If file isn't found, an Exception is raised. 21 stream_type : str 22 The type identifier of the target stream. Use "BCI_Essentials_Markers" for newer recordings. 23 """ 24 samples, timestamps, info = load_xdf_stream(filename, stream_type) 25 self.__samples = samples 26 self.__timestamps = timestamps 27 self.__info = info 28 29 @property 30 def name(self) -> str: 31 return self.__info["name"][0] 32 33 def get_markers(self) -> tuple[list[list], list]: 34 """Read markers and related timestamps from the XDF file. 35 36 Returns 37 ------- 38 markers_from_xdf : tuple[markers, timestamps] 39 A tuple of (markers, timestamps). The contents of the file on the first call to get_markers(). 40 - Returns empty lists [[],[]] thereafter. 41 """ 42 # return all data on first get 43 samples = self.__samples 44 timestamps = self.__timestamps 45 46 # reset to empty for next get 47 self.__samples = [[]] 48 self.__timestamps = [] 49 50 return [samples, timestamps] 51 52 def time_correction(self) -> float: 53 return 0.0 54 55 56class XdfEegSource(EegSource): 57 """Create a MarkerSource object that obtains EEG from an XDF file 58 59 Parameters 60 ---------- 61 filename : str 62 The full name of file, including path. If file isn't found, an Exception is raised. 63 """ 64 65 def __init__(self, filename: str): 66 samples, timestamps, info = load_xdf_stream(filename, "EEG") 67 self.__samples = samples 68 self.__timestamps = timestamps 69 self.__info = info 70 71 @property 72 def name(self) -> str: 73 return self.__info["name"][0] 74 75 @property 76 def fsample(self) -> float: 77 return float(self.__info["nominal_srate"][0]) 78 79 @property 80 def n_channels(self) -> int: 81 return int(self.__info["channel_count"][0]) 82 83 @property 84 def channel_types(self) -> list[str]: 85 types = [] 86 try: 87 description = self.__info["desc"][0]["channels"][0]["channel"] 88 types = [channel["type"][0] for channel in description] 89 types = [str.lower(type) for type in types] 90 except Exception: 91 pass 92 93 return types 94 95 @property 96 def channel_units(self) -> list[str]: 97 units = [] 98 try: 99 description = self.__info["desc"][0]["channels"][0]["channel"] 100 units = [channel["unit"][0] for channel in description] 101 except Exception: 102 pass 103 return units 104 105 @property 106 def channel_labels(self) -> list[str]: 107 labels = [] 108 try: 109 description = self.__info["desc"][0]["channels"][0]["channel"] 110 labels = [channel["label"][0] for channel in description] 111 except Exception: 112 pass 113 return labels 114 115 def get_samples(self) -> tuple[list[list], list]: 116 """Read markers and related timestamps from the XDF file. Returns the contents of the file 117 on the first call to get_markers(), returns empty lists thereafter. 118 """ 119 # return all data on first get 120 samples = self.__samples 121 timestamps = self.__timestamps 122 123 # reset to empty for next get 124 self.__samples = [[]] 125 self.__timestamps = [] 126 127 return [samples, timestamps] 128 129 def time_correction(self) -> float: 130 return 0.0 131 132 133def load_xdf_stream(filepath: str, streamtype: str) -> tuple[list, list, list]: 134 """A helper function to load the contents of the XDF file and return stream data, timestamps and info""" 135 # Don't need the header returned by load_xdf() 136 streams, _ = pyxdf.load_xdf(filepath) 137 138 samples = [] 139 timestamps = [] 140 info = [] 141 142 for i in range(len(streams)): 143 stream = streams[i] 144 info = stream["info"] 145 type = info["type"][0] 146 if type == streamtype: 147 try: 148 # Check if the stream is empty 149 if len(stream["time_series"]) == 0: 150 logger.warning( 151 "%s stream is empty, there may be multiple streams including ghost streams so maybe this is okay", 152 streamtype, 153 ) 154 continue 155 156 samples = stream["time_series"] 157 timestamps = stream["time_stamps"] 158 159 except Exception: 160 logger.error("%s data not available", streamtype) 161 162 break 163 164 return (samples, timestamps, info)
14class XdfMarkerSource(MarkerSource): 15 def __init__(self, filename: str, stream_type: str = "LSL_Marker_Strings"): 16 """Create a MarkerSource object that obtains markers from an XDF file. 17 18 Parameters 19 ---------- 20 filename : str 21 The full name of file, including path. If file isn't found, an Exception is raised. 22 stream_type : str 23 The type identifier of the target stream. Use "BCI_Essentials_Markers" for newer recordings. 24 """ 25 samples, timestamps, info = load_xdf_stream(filename, stream_type) 26 self.__samples = samples 27 self.__timestamps = timestamps 28 self.__info = info 29 30 @property 31 def name(self) -> str: 32 return self.__info["name"][0] 33 34 def get_markers(self) -> tuple[list[list], list]: 35 """Read markers and related timestamps from the XDF file. 36 37 Returns 38 ------- 39 markers_from_xdf : tuple[markers, timestamps] 40 A tuple of (markers, timestamps). The contents of the file on the first call to get_markers(). 41 - Returns empty lists [[],[]] thereafter. 42 """ 43 # return all data on first get 44 samples = self.__samples 45 timestamps = self.__timestamps 46 47 # reset to empty for next get 48 self.__samples = [[]] 49 self.__timestamps = [] 50 51 return [samples, timestamps] 52 53 def time_correction(self) -> float: 54 return 0.0
MarkerSource objects send time synchronized markers/commands to bci_controller.
XdfMarkerSource(filename: str, stream_type: str = 'LSL_Marker_Strings')
15 def __init__(self, filename: str, stream_type: str = "LSL_Marker_Strings"): 16 """Create a MarkerSource object that obtains markers from an XDF file. 17 18 Parameters 19 ---------- 20 filename : str 21 The full name of file, including path. If file isn't found, an Exception is raised. 22 stream_type : str 23 The type identifier of the target stream. Use "BCI_Essentials_Markers" for newer recordings. 24 """ 25 samples, timestamps, info = load_xdf_stream(filename, stream_type) 26 self.__samples = samples 27 self.__timestamps = timestamps 28 self.__info = info
Create a MarkerSource object that obtains markers from an XDF file.
Parameters
- filename (str): The full name of file, including path. If file isn't found, an Exception is raised.
- stream_type (str): The type identifier of the target stream. Use "BCI_Essentials_Markers" for newer recordings.
def
get_markers(self) -> tuple[list[list], list]:
34 def get_markers(self) -> tuple[list[list], list]: 35 """Read markers and related timestamps from the XDF file. 36 37 Returns 38 ------- 39 markers_from_xdf : tuple[markers, timestamps] 40 A tuple of (markers, timestamps). The contents of the file on the first call to get_markers(). 41 - Returns empty lists [[],[]] thereafter. 42 """ 43 # return all data on first get 44 samples = self.__samples 45 timestamps = self.__timestamps 46 47 # reset to empty for next get 48 self.__samples = [[]] 49 self.__timestamps = [] 50 51 return [samples, timestamps]
Read markers and related timestamps from the XDF file.
Returns
- markers_from_xdf (tuple[markers, timestamps]): A tuple of (markers, timestamps). The contents of the file on the first call to get_markers(). - Returns empty lists [[],[]] thereafter.
def
time_correction(self) -> float:
Get the current time correction for timestamps.
Returns
- time_correction (float):
The current time correction estimate (seconds).
- This is the number that needs to be added to a time tamp that was remotely generated via local_clock() to map it into the local clock domain of the machine.
57class XdfEegSource(EegSource): 58 """Create a MarkerSource object that obtains EEG from an XDF file 59 60 Parameters 61 ---------- 62 filename : str 63 The full name of file, including path. If file isn't found, an Exception is raised. 64 """ 65 66 def __init__(self, filename: str): 67 samples, timestamps, info = load_xdf_stream(filename, "EEG") 68 self.__samples = samples 69 self.__timestamps = timestamps 70 self.__info = info 71 72 @property 73 def name(self) -> str: 74 return self.__info["name"][0] 75 76 @property 77 def fsample(self) -> float: 78 return float(self.__info["nominal_srate"][0]) 79 80 @property 81 def n_channels(self) -> int: 82 return int(self.__info["channel_count"][0]) 83 84 @property 85 def channel_types(self) -> list[str]: 86 types = [] 87 try: 88 description = self.__info["desc"][0]["channels"][0]["channel"] 89 types = [channel["type"][0] for channel in description] 90 types = [str.lower(type) for type in types] 91 except Exception: 92 pass 93 94 return types 95 96 @property 97 def channel_units(self) -> list[str]: 98 units = [] 99 try: 100 description = self.__info["desc"][0]["channels"][0]["channel"] 101 units = [channel["unit"][0] for channel in description] 102 except Exception: 103 pass 104 return units 105 106 @property 107 def channel_labels(self) -> list[str]: 108 labels = [] 109 try: 110 description = self.__info["desc"][0]["channels"][0]["channel"] 111 labels = [channel["label"][0] for channel in description] 112 except Exception: 113 pass 114 return labels 115 116 def get_samples(self) -> tuple[list[list], list]: 117 """Read markers and related timestamps from the XDF file. Returns the contents of the file 118 on the first call to get_markers(), returns empty lists thereafter. 119 """ 120 # return all data on first get 121 samples = self.__samples 122 timestamps = self.__timestamps 123 124 # reset to empty for next get 125 self.__samples = [[]] 126 self.__timestamps = [] 127 128 return [samples, timestamps] 129 130 def time_correction(self) -> float: 131 return 0.0
Create a MarkerSource object that obtains EEG from an XDF file
Parameters
- filename (str): The full name of file, including path. If file isn't found, an Exception is raised.
channel_types: list[str]
84 @property 85 def channel_types(self) -> list[str]: 86 types = [] 87 try: 88 description = self.__info["desc"][0]["channels"][0]["channel"] 89 types = [channel["type"][0] for channel in description] 90 types = [str.lower(type) for type in types] 91 except Exception: 92 pass 93 94 return types
The type of each channel, ex: eeg, or stim
channel_units: list[str]
96 @property 97 def channel_units(self) -> list[str]: 98 units = [] 99 try: 100 description = self.__info["desc"][0]["channels"][0]["channel"] 101 units = [channel["unit"][0] for channel in description] 102 except Exception: 103 pass 104 return units
The unit of each channel, ex: microvolts
channel_labels: list[str]
106 @property 107 def channel_labels(self) -> list[str]: 108 labels = [] 109 try: 110 description = self.__info["desc"][0]["channels"][0]["channel"] 111 labels = [channel["label"][0] for channel in description] 112 except Exception: 113 pass 114 return labels
The label for each channel, ex: FC3, C5
def
get_samples(self) -> tuple[list[list], list]:
116 def get_samples(self) -> tuple[list[list], list]: 117 """Read markers and related timestamps from the XDF file. Returns the contents of the file 118 on the first call to get_markers(), returns empty lists thereafter. 119 """ 120 # return all data on first get 121 samples = self.__samples 122 timestamps = self.__timestamps 123 124 # reset to empty for next get 125 self.__samples = [[]] 126 self.__timestamps = [] 127 128 return [samples, timestamps]
Read markers and related timestamps from the XDF file. Returns the contents of the file on the first call to get_markers(), returns empty lists thereafter.
def
time_correction(self) -> float:
Get the current time correction for timestamps.
Returns
- time_correction (float):
The current time correction estimate (seconds).
- This is the number that needs to be added to a time tamp that was remotely generated via local_clock() to map it into the local clock domain of the machine.