import csv, math, os
# Synonyms
eeg_channel_synonyms = {
'I1': 'O9',
'I2': 'O10',
'O9': 'I1',
'O10': 'I2',
'T3': 'T7',
'T4': 'T8',
'T7': 'T3',
'T8': 'T4',
'T5': 'P7',
'T6': 'P8'
}
[docs]def get_standard_montage(standard, dim, coord_system):
"""Loads a standard from the ones included in MEDUSA
"""
# Check errors
assert standard == '10-20' or standard == '10-10' or standard == '10-05', \
'Incorrect input on standard parameter. Possible values {"10-20", ' \
'"10-10", "10-05"}'
assert dim == '2D' or dim == '3D', \
'Incorrect input on dim parameter. Possible values {"2D", "3D"}'
assert coord_system == 'cartesian' or coord_system == 'spherical', \
'Incorrect input on coord_system parameter. Possible values ' \
'{"cartesian", "spherical"}'
folder = os.path.dirname(__file__)
montage = read_montage_file(
path='%s/eeg_standard_%s_%s.tsv' % (folder, standard, dim),
file_format='tsv', dim=dim, coord_system='cartesian')
if coord_system == 'spherical':
montage = switch_coordinates_system(montage, dim, 'cartesian')
return montage
[docs]def switch_coordinates_system(montage, dim, coord_system):
"""Switch the coordinates system of the montage. If the the coordinates are
cartesian, it switches to spherical and viceversa
Parameters
----------
montage: dict
Montage data
dim: str {'2D', '3D'}
Current dimensions of the montage
coord_system: str {'cartesian', 'spherical'}
Current coordinates system
"""
# Make copy to not modify the original dict
new_montage = dict()
# Make conversion
for label, coordinates in montage.items():
if coord_system == 'cartesian':
cha_data = dict()
if dim == '2D':
cha_data['r'] = math.sqrt(
math.pow(montage[label]['x'], 2) +
math.pow(montage[label]['y'], 2))
cha_data['theta'] = math.atan2(montage[label]['y'],
montage[label]['x'])
elif dim == '3D':
cha_data['r'] = math.sqrt(
math.pow(montage[label]['x'], 2) +
math.pow(montage[label]['y'], 2) +
math.pow(montage[label]['z'], 2))
cha_data['theta'] = math.atan2(
math.sqrt(math.pow(montage[label]['x'], 2) +
math.pow(montage[label]['y'], 2)),
montage[label]['z']
)
cha_data['phi'] = math.atan2(montage[label]['y'],
montage[label]['x'])
else:
raise ValueError('Unknown number of dimensions')
else:
raise NotImplemented
new_montage[label] = cha_data
return new_montage
[docs]def read_montage_file(path, file_format, dim, coord_system):
"""This function reads a montage file.
Parameters
----------
path: str
Path of the file
file_format: str
File format. Current accepted file formats:
- csv (.csv): Comma separated values. Example:
Format csv (cartesian, 2D):
label, x, y
C3, -0.3249, 0.0000
C4, 0.3249, 0.0000
Cz, 0.0000, 0.0000
..., ..., ...
- tsv (.tsv): Tab separated values. Example:
Format tsv (cartesian, 2D):
label x y
C3 -0.3249 0.0000
C4 0.3249 0.0000
Cz 0.0000 0.0000
... ... ...
- eeglab (.xyz): EEGLAB toolbox format. Example:
Format eeglab (cartesian, 3D):
1 -3.277285 14.159082 -0.441064 AG001
2 -5.717206 13.304894 0.167594 AG002
3 -7.723112 12.035844 0.923781 AG003
... ... ...
- brainstorm (.txt): Brainstorm toolbox format. Example:
Format brainstorm (cartesian, 3D):
C3 -4.025389 68.267399 112.962637
C4 -3.409963 -70.131718 111.811990
O1 -86.474171 34.971167 48.738844
... ... ...
dim: str
Dimensions. Must be '2D' or '3D'
coord_system: str
For 2D, "cartesian" or "polar". For 3D "cartesian" or "spherical"
"""
# ACCEPTED PARAMSN
file_formats = ['csv', 'tsv', 'eeglab', 'brainstorm']
dims = ['2D', '3D']
coord_systems = ['cartesian', 'spherical']
# CHECK ERRORS
if file_format not in file_formats:
raise ValueError("Parameter file_format must be one of %s"
% file_formats)
if dim not in dims:
raise ValueError("Parameter dim must be one of %s" % dims)
if coord_system not in coord_systems:
raise ValueError("Parameter coord_system must be one of %s"
% coord_systems)
# LOAD COORDINATES FILES
if file_format == 'csv':
montage = __read_txt_file(path, coord_system, dim, delimiter=',')
elif file_format == 'tsv':
montage = __read_txt_file(path, coord_system, dim, delimiter='\t')
elif file_format == 'eeglab':
montage = __read_eeglab_file(path, dim)
elif file_format == 'brainstorm':
montage = __read_brainstorm_file(path, dim)
else:
raise ValueError("Parameter file_format must be one of %s"
% file_formats)
return montage
def __read_txt_file(path, coord_system, dim, delimiter='\t'):
data = dict()
# 2D coordinates
with open(path) as f:
tsvreader = csv.reader(f, delimiter=delimiter)
for l, line in enumerate(tsvreader):
if dim == '2D':
label, coord1, coord2 = line
if l == 0:
# Check format errors
if coord_system == 'cartesian':
if coord1 != 'x' or coord2 != 'y':
raise ValueError(
'Invalid header. '
'For dim=%s, '
'coord_system=%s, '
'the header must be [label, x, y]' %
(dim, coord_system)
)
elif coord_system == 'spherical':
if coord1 != 'r' or coord2 != 'theta':
raise ValueError(
'Invalid header. '
'For dim=%s, '
'coord_system=%s, '
'the header must be [label, r, theta]' %
(dim, coord_system)
)
else:
raise ValueError('Invalid coordinate system')
continue
else:
lcha = line[0].upper()
data[lcha] = dict()
if coord_system == 'cartesian':
data[lcha]['x'] = float(coord1)
data[lcha]['y'] = float(coord2)
elif coord_system == 'spherical':
data[lcha]['r'] = float(coord1)
data[lcha]['theta'] = float(coord2)
else:
raise ValueError('Invalid coordinate system')
elif dim == '3D':
label, coord1, coord2, coord3 = line
if l == 0:
# Check format errors
if coord_system == 'cartesian':
if coord1 != 'x' or coord2 != 'y' or coord3 != 'z':
raise ValueError(
'Invalid header. '
'For dim=%s, '
'coord_system=%s, '
'the header must be [label, x, y, z]' %
(dim, coord_system)
)
elif coord_system == 'spherical':
if coord1 != 'r' or coord2 != 'theta' \
or coord3 != 'phi':
raise ValueError(
'Invalid header. '
'For dim=%s, '
'coord_system=%s, '
'the header must be [label, r, theta, phi]' %
(dim, coord_system)
)
else:
raise ValueError('Invalid coordinate system')
continue
else:
lcha = label.upper()
data[lcha] = dict()
if coord_system == 'cartesian':
data[lcha]['x'] = float(coord1)
data[lcha]['y'] = float(coord2)
data[lcha]['z'] = float(coord3)
elif coord_system == 'spherical':
data[lcha]['r'] = float(coord1)
data[lcha]['theta'] = float(coord2)
data[lcha]['phi'] = float(coord3)
else:
raise ValueError('Invalid coordinate system')
return data
def __read_eeglab_file(path, dim):
data = dict()
# 2D coordinates
with open(path) as f:
tsvreader = csv.reader(f, delimiter='\t')
for l, line in enumerate(tsvreader):
if dim == '2D':
index, coord1, coord2, label = line
# Save data
lcha = label.upper()
data[lcha] = dict()
data[lcha]['x'] = float(coord1)
data[lcha]['y'] = float(coord2)
elif dim == '3D':
index, coord1, coord2, coord3, label = line
lcha = line[0].upper()
data[lcha] = dict()
data[lcha]['x'] = float(coord1)
data[lcha]['y'] = float(coord2)
data[lcha]['z'] = float(coord3)
return data
def __read_brainstorm_file(path, dim):
data = dict()
# 2D coordinates
with open(path) as f:
tsvreader = csv.reader(f, delimiter='\t')
for l, line in enumerate(tsvreader):
if dim == '2D':
label, coord1, coord2 = line
# Save data
lcha = label.upper()
data[lcha] = dict()
data[lcha]['x'] = float(coord1)
data[lcha]['y'] = float(coord2)
elif dim == '3D':
label, coord1, coord2, coord3 = line
lcha = line[0].upper()
data[lcha] = dict()
data[lcha]['x'] = float(coord1)
data[lcha]['y'] = float(coord2)
data[lcha]['z'] = float(coord3)
return data
[docs]class ChannelNotFound(Exception):
[docs] def __init__(self, l_cha):
super().__init__(
'Channel %s is not defined in the current montage' % l_cha)
[docs]class UnlocatedChannel(Exception):
[docs] def __init__(self, l_cha):
super().__init__(
'Channel %s does not contain location coordinates' % l_cha)
if __name__ == '__main__':
montage = get_standard_montage('10-20', '2D', 'spherical')
print(montage)