Skip to content

Commit efa13f0

Browse files
Shyamal-Dhariaretiutut
authored andcommitted
Updated code based on GUIv6 file format
1 parent 5d0178b commit efa13f0

1 file changed

Lines changed: 98 additions & 40 deletions

File tree

sd_file_conversion/txt_to_csv_conversion.py

Lines changed: 98 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#OPENBCI HEX TO CSV CONVERSION REFERENCE: https://github.com/roflecopter/openbci-psg
2-
# GIVES output same as OpenBCI GUI 4.2
32

43
import numpy as np
54
import pandas as pd
@@ -82,20 +81,20 @@ def processLine(split_line):
8281
return values_array
8382

8483

85-
86-
87-
def process_file(file_path:str,
88-
n_ch:int = 16,
89-
n_acc:int = 3,
90-
save_path:str = None):
84+
def process_file(file_path: str,
85+
n_acc: int = 3,
86+
save_path: str = None,
87+
board_type: str = "CytonDaisy",
88+
board_mode: str = "Analog"):
9189
"""
9290
Process the OpenBCI hex file and convert it to a CSV file.
9391
9492
Parameters:
9593
- file_path (str): Path to the input hex file.
96-
- n_ch (int): Number of EEG channels.
9794
- n_acc (int): Number of accelerometer channels.
9895
- save_path (str): Path to save the converted CSV file.
96+
- board_type (str): Type of OpenBCI board used (CytonDaisy, Cyton, Ganglion).
97+
- board_mode (str): Mode of the board data (Analog, Digital, Mixed).
9998
10099
Returns:
101100
- None
@@ -104,6 +103,17 @@ def process_file(file_path:str,
104103
current_time = datetime.now()
105104
formatted_time = current_time.strftime("%Y-%m-%d_%H-%M-%S")
106105

106+
# Determine the number of channels based on the board type
107+
if board_type == "CytonDaisy":
108+
n_ch = 16
109+
board_name = "OpenBCI_GUI$BoardCytonDaisySerial"
110+
elif board_type == "Cyton":
111+
n_ch = 8
112+
board_name = "OpenBCI_GUI$BoardCytonSerial"
113+
elif board_type == "Ganglion":
114+
n_ch = 4
115+
board_name = "OpenBCI_GUI$BoardGanglionSerial"
116+
107117
# Open the hex file for reading
108118
with open(file_path, 'r') as file:
109119
result = [] # List to store parsed values
@@ -146,54 +156,80 @@ def process_file(file_path:str,
146156
bci_signals = np.array(result)
147157

148158
# Apply OpenBCI scaling to EEG signals and accelerometer scaling to accelerometer signals
149-
signals_V = np.vectorize(adc_v_bci)(bci_signals[:, :16])
150-
accel_data = np.vectorize(accel_scale)(bci_signals[:, 16:])
159+
accel_data = np.vectorize(accel_scale)(bci_signals[:, n_ch:])
151160

152-
# Concatenate EEG and accelerometer data along the columns
153-
data = np.concatenate((signals_V, accel_data), axis=1)
161+
exg_channel_cols = [f'EXG Channel {i}' for i in range(n_ch)]
162+
163+
additional_cols = ['Accel Channel 0', 'Accel Channel 1', 'Accel Channel 2',
164+
'Not Used', 'Digital Channel 0 (D11)', 'Digital Channel 1 (D12)',
165+
'Digital Channel 2 (D13)', 'Digital Channel 3 (D17)', 'Not Used',
166+
'Digital Channel 4 (D18)', 'Analog Channel 0', 'Analog Channel 1',
167+
'Analog Channel 2', 'Timestamp', 'Marker Channel', 'Timestamp (Formatted)']
168+
169+
all_columns = ['Sample Index'] + exg_channel_cols + additional_cols
170+
171+
# create columns
172+
df = pd.DataFrame(columns=all_columns)
173+
174+
# sample index values
175+
num_repeats = len(bci_signals) // 256
176+
df['Sample Index'] = list(range(256)) * num_repeats + list(range(len(bci_signals) % 256))
177+
178+
# EXG channels values
179+
for i in range(n_ch):
180+
df[exg_channel_cols[i]] = np.vectorize(adc_v_bci)(bci_signals[:, i])
181+
182+
# Sensor values
183+
analog_sensor_columns = [-6, -5, -4]
184+
digital_sensor_columns = [-11, -10, -7]
185+
accel_sensor_columns = [-16, -15, -14]
186+
187+
# Map data to correct columns based on board_mode
188+
if board_mode == "Analog":
189+
df.iloc[:, analog_sensor_columns] = accel_data
190+
elif board_mode == "Digital":
191+
df.iloc[:, digital_sensor_columns] = accel_data
192+
else:
193+
df.iloc[:, accel_sensor_columns] = accel_data
154194

155-
# Generate an index for the output CSV file
156-
index = [str(i) for i in range(1, len(data) + 1)]
195+
df = df.fillna(0)
157196

158197
# Additional information to be added at the beginning of the CSV file
159198
additional_info = [
160-
f"%OBCI SD Convert - {formatted_time}",
161-
"%",
162-
"%Sample Rate = 250.0 Hz",
163-
"%First Column = SampleIndex",
164-
"%Last Column = Timestamp",
165-
"%Other Columns = EEG data in microvolts followed by Accel Data (in G) interleaved with Aux Data",
199+
"%OpenBCI Raw EXG Data",
200+
f"%Number of channels = {n_ch}",
201+
"%Sample Rate = 250 Hz",
202+
f"%Board = {board_name}"
166203
]
167204

168-
# Create a Pandas DataFrame from the data and save it to a CSV file
169-
make_csv = pd.DataFrame(data, index=index)
170205
file_name_with_extension = os.path.basename(file_path)
171-
file_name, file_extension = os.path.splitext(file_name_with_extension)
206+
file_name, _ = os.path.splitext(file_name_with_extension)
172207

173208
if save_path:
174209
file_path = os.path.join(save_path, file_name)
175-
make_csv.to_csv(f"{file_path}_converted.csv")
210+
df.to_csv(f"{file_path}_converted.csv", index=False)
176211

177212
# Write additional information to the CSV file
178213
with open(f"{file_path}_converted.csv", 'w') as file:
179214
for line in additional_info:
180215
file.write(line + '\n')
181216

182217
# Append the index to the CSV file
183-
make_csv.to_csv(f"{file_path}_converted.csv", mode='a', index=index)
218+
df.to_csv(f"{file_path}_converted.csv", mode='a', index=False)
184219
else:
185-
make_csv.to_csv(f"./{file_name}_converted.csv")
220+
df.to_csv(f"./{file_name}_converted.csv", index=False)
186221

187222
# Write additional information to the CSV file
188223
with open(f"{file_name}_converted.csv", 'w') as file:
189224
for line in additional_info:
190225
file.write(line + '\n')
191226

192227
# Append the index to the CSV file
193-
make_csv.to_csv(f"{file_path}_converted.csv", mode='a', index=index)
228+
df.to_csv(f"{file_path}_converted.csv", mode='a', index=False)
194229

195230
return None
196231

232+
197233

198234
def adc_v_bci(signal,
199235
ADS1299_VREF:float = 4.5,
@@ -229,8 +265,8 @@ def adc_v_bci(signal,
229265

230266
def start_converting(sd_dir:str = "./",
231267
gain:int = 24,
232-
n_ch:int = 16,
233-
n_acc:int = 3,
268+
board_type:str = "CytonDaisy",
269+
board_mode: str = "Analog",
234270
save_path:str = ""):
235271
"""
236272
Batch process OpenBCI hex files in a directory and convert them to CSV.
@@ -247,8 +283,6 @@ def start_converting(sd_dir:str = "./",
247283
"""
248284
# Assign parameters to local variables
249285
gain = gain
250-
n_ch = n_ch
251-
n_acc = n_acc
252286
save_path = save_path
253287

254288
# Get a list of files in the specified directory with a '.txt' extension
@@ -267,15 +301,18 @@ def start_converting(sd_dir:str = "./",
267301
print(f'converting: {file_name}')
268302

269303
# Call the process_file function to convert the hex file to CSV
270-
process_file(file_path=file_path, save_path=save_path)
304+
process_file(file_path=file_path,
305+
save_path=save_path,
306+
board_type=board_type,
307+
board_mode=board_mode)
271308

272309
return None
273310

274311

275312

276313
def file_type_conversion(csv_path:str = "./",
277314
save_path:str = "./",
278-
num_channels:int = 16,
315+
board_type:str = "CytonDaisy",
279316
ch_names:[str] = None,
280317
sfreq:int = 250,
281318
file_type:str = "brainvision"):
@@ -293,6 +330,16 @@ def file_type_conversion(csv_path:str = "./",
293330
Returns:
294331
- None
295332
"""
333+
334+
if board_type == "CytonDaisy":
335+
n_ch = 16
336+
337+
elif board_type == "Cyton":
338+
n_ch = 8
339+
340+
elif board_type == "Ganglion":
341+
n_ch = 4
342+
296343

297344
# Get a list of files in the specified directory with a '.csv' extension
298345
files = [file for file in os.listdir(csv_path) if file.endswith('.csv')]
@@ -305,12 +352,13 @@ def file_type_conversion(csv_path:str = "./",
305352
for file_name in files:
306353
# Construct the full path to the CSV file
307354
file_path = os.path.join(csv_path, file_name)
355+
name, _ = os.path.splitext(file_name)
308356

309357
# Read the CSV file, skipping the header rows
310-
csv_file = pd.read_csv(file_path ,header=None,skiprows=[0,1,2,3,4,5,6],index_col=0,sep=',',engine='python')
358+
csv_file = pd.read_csv(file_path ,header=None,skiprows=[0,1,2,3,4],index_col=0,sep=',',engine='python')
311359

312360
# Remove the last 3 columns from the CSV file
313-
csv_file = csv_file.iloc[:,:-3]
361+
csv_file = csv_file.iloc[:,:-16]
314362

315363
# Transpose the DataFrame
316364
csv_file = pd.DataFrame.transpose(csv_file)
@@ -319,29 +367,39 @@ def file_type_conversion(csv_path:str = "./",
319367
csv_file = csv_file / 1e6
320368

321369
# Define EEG channel types
322-
ch_types = (['eeg'] * num_channels)
370+
ch_types = (['eeg'] * n_ch)
323371

324372
# If channel names are not provided, generate default names
325373
if ch_names is None:
326-
ch_names = [str(i) for i in range(1, num_channels + 1)]
374+
ch_names = [str(i) for i in range(1, n_ch + 1)]
327375

328376
# Create MNE Info object
329377
info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types)
330378

331379
# Create MNE RawArray
332380
raw = mne.io.RawArray(csv_file, info)
333381

382+
#getting file extension
383+
if file_type == "brainvision":
384+
extension = ".eeg"
385+
elif file_type == "edf":
386+
extension = ".edf"
387+
elif file_type == "EEGLAB":
388+
extension = ".set"
389+
334390
# Export the MNE RawArray to the specified file type
335-
mne.export.export_raw(f"{save_path}/{file_name}.eeg", raw, fmt=file_type, overwrite=True)
391+
mne.export.export_raw(f"{save_path}/{name}{extension}", raw, fmt=file_type, overwrite=True)
336392

337393
return None
338394

339395
if __name__ == "__main__":
340396
start_converting(sd_dir="./hex_data/",
341-
save_path="raw_data")
397+
save_path="raw_data",
398+
board_type = "CytonDaisy",
399+
board_mode = "Analog")
342400

343401
file_type_conversion(csv_path = "./raw_data/",
344402
save_path = "./raw_data/",
345-
num_channels = 16,
403+
board_type = "CytonDaisy",
346404
sfreq = 250,
347405
file_type = "brainvision")

0 commit comments

Comments
 (0)