Initial commit

Esse commit está contido em:
Diego Alvarez-Estevez
2019-06-04 13:22:02 +02:00
commit 334216b636
6 arquivos alterados com 450 adições e 0 exclusões
+30
Ver Arquivo
@@ -0,0 +1,30 @@
% Created by Diego Alvarez-Estevez (http://dalvarezestevez.com)
% Last modified 19/01/2018
% LogFloat conversion (see www.edfplus.info/specs/edffloat.html for
% more information)
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
function out = LogFloatVector(vector, Y0, A)
out = zeros(size(vector));
r = (log10(vector(vector > Y0)) - log10(Y0)) / A;
out(vector > Y0) = int16(round(min(r, intmax('int16'))));
r = (-log10((-1)*vector(vector < Y0)) + log10(Y0)) / A;
out(vector < Y0) = int16(round(max(r, -intmax('int16'))));
% Those with zero value at input remain zero at output
+127
Ver Arquivo
@@ -0,0 +1,127 @@
% Created by Diego Alvarez-Estevez (http://dalvarezestevez.com)
% Last modified 19/01/2018
% Extracts EEG data from an OpenBCI exported file and saves it to EDF format (check www.edfplus.info for more information on EDF/EDF+ format)
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
%%----------------------------------------------------------------------
% Clean possible previously-existing results
clear all;
clc;
%% USER CONFIGURABLE PARAMETERS
% Skip first (possible invalid, such as comments, etc.) rows of the OpenBCI
% export file. Note this is file-dependent, use a "more" (windows) or "cat" (unix/linux) alike commands
% to check the real beginning of the recording
skipFirstLines = 0;
% Sampling frequency of the signals, depends on OpenBCI configuration, default is 250 Hz
fs = 250;
% Number of channels (default in OpenBCI is 8)
nchannels = 8;
% Signal filtering paramaters
highpass_f = 0.1; % High-pass cut-off frequency
notch_fc = 50; % Frequency of mains interference (50 Hz for Europe)
notch_w = 1; % Notch filter bandwith
%% CONVERSION SCRIPT STARTS
% Ask the user to select the source file
[filename, path] = uigetfile(['.', filesep, '*.*'], 'Select file in OpenBCI format');
if (filename == 0)
return;
end
fullfilename = fullfile(path, filename);
% Asks some necessary data to the user
disp('Please provide the following information to complete the header information of the EDF file');
disp('Check www.edfplus.info/specs/edf.html for detailed information');
% Examples
% patId = 'XXX';
% recId = 'XXX';
% startdate = '23.06.17';
% starttime = '00.08.52';
patId = input('\nSpecify the local patient identification string\n(max 80 characters, press [ENTER] to continue): ', 's');
recId = input('\nSpecify the local recording identification string\n(max 80 characters, press [ENTER] to continue): ' , 's');
startdate = input('\nSpecify the Startdate of recording\n(DD.MM.YY, press [ENTER] to continue): ', 's');
starttime = input('\nSpecify the Starttime of recording\n(HH.MM.SS, press [ENTER] to continue): ', 's');
% Read OpenBCI data
fprintf(1, '\nReading OpenBCI data...');
data = csvread(fullfilename, skipFirstLines, 0);
eegdata = data(:, 2:9);
% Apply signal filtering
fprintf(1, '\nFiltering signals...\n');
for k = 1:nchannels
f_eeg(:, k) = polymanHighPassFilter(eegdata(:, k), fs, 1, highpass_f);
f_eeg(:, k) = polymanNotchFilter(f_eeg(:, k), fs, notch_fc, notch_w);
end
% Export unfiltered data
disp('Writing signals (unfiltered) to EDF file...');
filename1 = [filename, '-raw.edf'];
signals = [];
snames = [];
units = [];
prefilterings = [];
for k = 1:nchannels
signals{k} = eegdata(:, k);
snames{k} = sprintf('EEG CH%d', k);
fsamps(k) = fs;
Pmins(k) = min(eegdata(:, k));
Pmaxs(k) = max(eegdata(:, k));
Dmins(k) = -32768;
Dmaxs(k) = 32767;
units{k} = 'uV';
prefilterings{k} = '';
end
statusOK = signals2EDF(signals, fsamps, patId, recId, snames, startdate, starttime, Pmins, Pmaxs, Dmins, Dmaxs, units, prefilterings, filename1);
if not(statusOK)
error('Problem exporting unfiltered signals to EDF');
else
disp('...done!');
end
% Export filtered data
disp('Writing signals (filtered) to EDF file...');
filename1 = [filename, '-filt.edf'];
signals = [];
snames = [];
units = [];
prefilterings = [];
for k = 1:nchannels
signals{k} = f_eeg(:, k);
snames{k} = sprintf('EEG CH%d', k);
fsamps(k) = fs;
Pmins(k) = min(f_eeg(:, k));
Pmaxs(k) = max(f_eeg(:, k));
Dmins(k) = -32768;
Dmaxs(k) = 32767;
units{k} = 'uV';
prefilterings{k} = sprintf('HP(%g) Notch(%g/%g)', highpass_f, notch_fc, notch_w);
end
statusOK = signals2EDF(signals, fsamps, patId, recId, snames, startdate, starttime, Pmins, Pmaxs, Dmins, Dmaxs, units, prefilterings, filename1);
if not(statusOK)
error('Problem exporting filtered signals to EDF');
else
disp('...done!')
end
+38
Ver Arquivo
@@ -0,0 +1,38 @@
% Created by Diego Alvarez-Estevez (http://dalvarezestevez.com)
% Last modified 19/01/2018
% Implements high pass filter from Polyman
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
function signalFilt = polymanHighPassFilter(signal, sr, gain, hpFreq)
% Check for valid settings
if ((hpFreq <= 0) || (hpFreq > sr/2))
disp('Error');
return;
end
r = 2 * pi * hpFreq * ((1/sr)/2);
t = gain / (r + 1);
b = [t, - t]; % b coeffs according to filter function description (for x(n))
a = [1, -(r-1)/(r+1)]; % a coeffs according to filter function description (for y(n))
% We multiply a(2:end)*(-1) because filter function is implemented as a direct
% form II transposed structure
a(2:end) = -a(2:end);
signalFilt = filter(b, a, signal);
+43
Ver Arquivo
@@ -0,0 +1,43 @@
% Created by Diego Alvarez-Estevez (http://dalvarezestevez.com)
% Last modified 19/01/2018
% Implements Notch filter from Polyman
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
function signalFilt = polymanNotchFilter(signal, sr, centerFreq, bandWidth)
% Check for valid settings
if ((centerFreq <= 0) || (centerFreq > sr/2))
error('Center frequency is not in the Nyquist interval');
elseif ((bandWidth <= 0) || (bandWidth > sr/4))
error('Not valid bandwidth');
end
r = 2 * pi * centerFreq/sr;
s = 2 * pi * (centerFreq - (bandWidth/2))/sr;
t = 1 - sqrt((cos(r)-cos(s))^2 + (sin(r)-sin(s))^2) * sqrt(exp(1)^2-1);
x = cos(r);
y = sin(r);
b = [1, -2*x, x^2+y^2]; % b coeffs according to filter function description (for x(n))
a = [1, 2*t*x, -((t*x)^2 + (t*y)^2)]; % a coeffs according to filter function description (for y(n))
% We multiply a(2:end)*(-1) because filter function is implemented as a direct
% form II transposed structure
a(2:end) = -a(2:end);
signalFilt = filter(b, a, signal);
+178
Ver Arquivo
@@ -0,0 +1,178 @@
% Created by Diego Alvarez-Estevez (http://dalvarezestevez.com)
% Last modified 19/01/2018
% Saves signals contained on a Matlab structure to EDF format
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
function statusok = signals2EDF(signals, srs, patId, recId, snames, startdate, starttime, Pmins, Pmaxs, Dmins, Dmaxs, units, prefilterings, filename)
fid = fopen(filename, 'w', 'ieee-le');
if (fid == -1)
fprintf(1, 'Error creating output file %s\n', filename);
return;
end
numSignals = length(srs);
% Calculate recording length (in seconds)
recLength = -1;
for k = 1:numSignals
recLength = max(recLength, length(signals{k})/srs(k));
end
recLength = ceil(recLength); % Ceil to an integer number
% Asuming 1s databloack duration. Check possible incompatibilities
blockSizeBytes = sum(2*srs(:));
if (blockSizeBytes > 61440)
error('Yet to be implemented: Signals cannot fit on a 1s datablock. Check for other block size possibilities');
else
blockSize = 1;
numBlocks = recLength;
end
general_header_size = 256; %bytes
one_signal_header_size = 256; %bytes
% Write edf
% FIXED HEADER
header.version = 0;
header.local_patient_identification = patId;
header.local_recording_identification = recId;
header.startdate_recording = startdate;
header.starttime_recording = starttime;
header.num_signals = numSignals;
header.num_bytes_header = general_header_size + one_signal_header_size * numSignals;
header.reserved = '';
header.duration_data_record = blockSize;
header.num_data_records = numBlocks;
fprintf(fid, trimAndFillWithBlanks(num2str(header.version), 8)); % version
fprintf(fid, '%-80s', header.local_patient_identification);
fprintf(fid, '%-80s', header.local_recording_identification);
fprintf(fid, '%-8s', header.startdate_recording);
fprintf(fid, '%-8s', header.starttime_recording);
fprintf(fid, trimAndFillWithBlanks(num2str(header.num_bytes_header), 8));
fprintf(fid, '%-44s', header.reserved);
fprintf(fid, trimAndFillWithBlanks(num2str(header.num_data_records), 8));
fprintf(fid, trimAndFillWithBlanks(num2str(header.duration_data_record), 8));
fprintf(fid, trimAndFillWithBlanks(num2str(header.num_signals), 4));
% SIGNAL DEPENDENT HEADER
signalOffsets = zeros(1, numSignals); % In bytes
for k = 1:numSignals
header.signals_info(k).label = snames{k};
header.signals_info(k).transducer_type = '';
header.signals_info(k).physical_dimension = units{k};
header.signals_info(k).physical_min = Pmins(k);
header.signals_info(k).physical_max = Pmaxs(k);
header.signals_info(k).digital_min = Dmins(k);
header.signals_info(k).digital_max = Dmaxs(k);
header.signals_info(k).prefiltering = prefilterings{k};
header.signals_info(k).num_samples_datarecord = srs(k)*blockSize;
header.signals_info(k).reserved = '';
% NOTE: The two following are not specific EDF header fields, but are practical for EDF handling
header.signals_info(k).sample_rate = header.signals_info(k).num_samples_datarecord / header.duration_data_record;
if (k > 1)
signalOffsets(k) = signalOffsets(k - 1) + 2 * header.signals_info(k - 1).num_samples_datarecord;
end
header.signals_info(k).signalOffset = signalOffsets(k);
end
% Write signal-dependent header to file
for k = 1:numSignals
fprintf(fid, '%-16s', header.signals_info(k).label);
end
for k = 1:numSignals
fprintf(fid, '%-80s', header.signals_info(k).transducer_type);
end
for k = 1:numSignals
fprintf(fid, '%-8s', header.signals_info(k).physical_dimension);
end
for k = 1:numSignals
fprintf(fid, trimAndFillWithBlanks(num2str(header.signals_info(k).physical_min), 8));
end
for k = 1:numSignals
fprintf(fid, trimAndFillWithBlanks(num2str(header.signals_info(k).physical_max), 8));
end
for k = 1:numSignals
fprintf(fid, trimAndFillWithBlanks(num2str(header.signals_info(k).digital_min), 8));
end
for k = 1:numSignals
fprintf(fid, trimAndFillWithBlanks(num2str(header.signals_info(k).digital_max), 8));
end
for k = 1:numSignals
fprintf(fid, '%-80s', header.signals_info(k).prefiltering);
end
for k = 1:numSignals
fprintf(fid, trimAndFillWithBlanks(num2str(header.signals_info(k).num_samples_datarecord), 8));
end
for k = 1:numSignals
fprintf(fid, '%-32s', header.signals_info(k).reserved);
end
% Check data starting point
current_position = ftell(fid); % in bytes
if ne(header.num_bytes_header, current_position)
disp('Something wrong could be happening: unexpected position at the beginning of the first data block');
end
bytes_full_data_record = 2 * sum([header.signals_info.num_samples_datarecord]);
% DATA WRITING
for k = 1:numBlocks
% Initialize datablock
data = zeros(1, bytes_full_data_record/2); % Num samples per data record
for k1 = 1:numSignals
offsetSignal = (k - 1) * header.signals_info(k1).num_samples_datarecord + 1;
onsetSignal = min(offsetSignal + header.signals_info(k1).num_samples_datarecord - 1, length(signals{k1}));
offsetDataBlock = header.signals_info(k1).signalOffset/2 + 1;
onsetDataBlock = offsetDataBlock + length(offsetSignal:onsetSignal) - 1;
logConversion = strcmp(header.signals_info(k1).physical_dimension', 'Filtered');
if logConversion
% Parse prefiltering to set this values
tokens = textscan(header.signals_info(k1).prefiltering, 'sign*LN[sign*(at %.1fHz)/(%.5f)]/(%.5f)(Kemp:J Sleep Res 1998-supp2:132)');
if isempty(tokens{2})
disp('Warning: assigned default values to logConversion');
LogFloatY0 = 0.0001; % default value
else
LogFloatY0 = tokens{2};
end
if isempty(tokens{3})
disp('Warning: assigned default values to logConversion');
LogFloatA = 0.001; % default value
else
LogFloatA = tokens{3};
end
% Actual writing
data(offsetDataBlock:onsetDataBlock) = Dmins(k1) + (Dmaxs(k1) - Dmins(k1)) * (LogFloatVector(signals{k1}(offsetSignal:onsetSignal), LogFloatY0, LogFloatA) - Pmins(k1))/(Pmaxs(k1) - Pmins(k1));
else
data(offsetDataBlock:onsetDataBlock) = Dmins(k1) + (Dmaxs(k1) - Dmins(k1)) * (signals{k1}(offsetSignal:onsetSignal) - Pmins(k1))/(Pmaxs(k1) - Pmins(k1));
end
end
data = typecast(int16(data), 'uint16'); % From double to 16bit unsigned integers
fwrite(fid, data, 'uint16');
end
statusok = (fclose(fid) == 0);
+34
Ver Arquivo
@@ -0,0 +1,34 @@
% Created by Diego Alvarez-Estevez (http://dalvarezestevez.com)
% Last modified 19/01/2018
% Trims, justifies, and fills with blanks a character vector
%% This program is free software: you can redistribute it and/or modify
%% it under the terms of the GNU General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%% This program is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU General Public License for more details.
%% You should have received a copy of the GNU General Public License
%% along with this program. If not, see <http://www.gnu.org/licenses/>.
function result = trimAndFillWithBlanks(str, maxLength, justify)
if (nargin == 2)
justify = 'left';
end
if length(str) > maxLength
result = str(1:maxLength);
else
if strcmp(justify, 'right')
result = [blanks(maxLength - length(str)), str];
else
% default is left
result = [str, blanks(maxLength - length(str))];
end
end