ImaGIN_spm_eeg_converteeg2mat.m
14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
function D = ImaGIN_spm_eeg_converteeg2mat(S)
% User interface for conversion of EEG-files to ImaGIN/SPM's data structure
%
% USAGE: D = spm_eeg_converteeg2mat(S)
% D = spm_eeg_converteeg2mat()
%
% INPUTS:
% - S: Optional struct with the followint (optional) fields:
% |- dataset : Full path of the file to convert
% |- SelectChannels : Channels to keep in the conversion:
% | Array of indices ([1,2,...]), Array of cells ({'A1','A2',...}) or String ('A1 A2 ...')
% | If empty: the list of selected channels is auto-detected based on standard setups
% |- FileOut : Full path to the output .mat/.dat file (without file extension)
% |- isSEEG : 0 if importing surface EEG, 1 if importing SEEG/ECOG
% |- Fdata
% |- Fchannels - String containing name of channel template file
% | ... other format-specific fields
% -=============================================================================
% This function is part of the ImaGIN software:
% https://f-tract.eu/
%
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE AUTHORS
% DO NOT ASSUME ANY LIABILITY OR RESPONSIBILITY FOR ITS USE IN ANY CONTEXT.
%
% Copyright (c) 2000-2018 Inserm U1216
% =============================================================================-
%
% Authors: Stefan Kiebel, 2005
% Olivier David, 2010-2017
% Francois Tadel, 2017
% Parse inputs
S2 = struct();
if (nargin < 1) || isempty(S)
S = struct();
else
S2 = ImaGIN_copy_fields(S2, S, {'dataset', 'Atlas', 'FileOut'});
end
% Ask dataset (if not defined in input)
if ~isfield(S2, 'dataset')
[S.dataset, sts] = spm_select(1, '.*', 'Select M/EEG data file');
S2.dataset = S.dataset;
if ~sts
return;
end
end
% Selected channels: if not specified, auto-detect
if isfield(S, 'SelectChannels')
SelectChannels = S.SelectChannels;
else
SelectChannels = str2num(spm_input('Index of channels (empty=detect)', '+1', 's'));
end
% Ask if the input data is SEEG
if isfield(S, 'isSEEG') && ~isempty(S.isSEEG)
isSEEG = S.isSEEG;
else
isSEEG = isequal(spm_input('SEEG? ','+1','Yes|No'), 'Yes');
end
% Is output file defined
isOutputSet = isfield(S2, 'FileOut') && ~isempty(S2.FileOut);
% Processing starts
spm('Pointer','Watch'); drawnow;
% Loop on input files
Nfiles = size(S.dataset, 1);
D = cell(1,Nfiles);
for i1 = 1:Nfiles
% Input file
S2.Fdata = deblank(S.dataset(i1, :));
% Get extension
[fPath, fBase, fExt] = fileparts(S2.Fdata);
% Default output filename
if ~isOutputSet
S2.FileOut = fullfile(fPath, fBase);
end
% Old versions of the readers
% BrainAmp .eeg
% S2 = ImaGIN_copy_fields(S2, S, {'CreateTemplate', 'Fchannels', 'Bipolar', 'Montage', 'epochlength', 'coarse', 'channel', 'SaveFile'}); % MISSING FUNCTION EEG2MAT
% D{i1} = ImaGIN_spm_eeg_rdata_elan(S2);
% Micromed .trc
% Old version: JPL & OD
% S2 = ImaGIN_copy_fields(S2, S, {'pts', 'System', 'CreateTemplate', 'Fchannels', 'channel', 'coarse', 'Montage', 'MontageName', 'NeventType', 'event_file' , 'Bipolar', 'bipole', 'loadevents'});
% D{i1} = ImaGIN_spm_eeg_rdata_micromed_mono(S2);
% EDF
% S2 = ImaGIN_copy_fields(S2, S, {'channel', 'Atlas', 'SEEG', 'Bipolar', 'coarse', 'SizeMax'});
% D = ImaGIN_spm_eeg_rdata_edf(S2);
% Nicolet .e
% S2 = ImaGIN_copy_fields(S2, S, {'CreateTemplate', 'Montage', 'coarse', 'SEEG', 'filenamePos', 'filenameName', 'MontageName', 'SaveFile', 'channel'});
% D = ImaGIN_spm_eeg_rdata_nicolet_mono(S2);
% Deltamed .bin
% S2 = ImaGIN_copy_fields(S2, S, {'Bipolar', 'Bipole', 'CreateTemplate', 'Montage', 'coarse', 'Nevent', 'SEEG', 'filenamePos', 'filenameName', 'MontageName', 'SaveFile'});
% D{i1} = ImaGIN_spm_eeg_rdata_deltamedbin_mono(S2);
% Switch between file formats
BstFormat = [];
switch lower(fExt)
case '.smr'
S.channel = S.SelectChannels;
S2 = ImaGIN_copy_fields(S2, S, {'CreateTemplate', 'Fchannels', 'Bipolar', 'Montage', 'epochlength', 'coarse', 'channel'});
D{i1} = ImaGIN_spm_eeg_rdata_spike2_mono(S2);
case {'.asc','.txt'}
S2 = ImaGIN_copy_fields(S2, S, {'CreateTemplate', 'Fchannels', 'coarse', 'Bipolar', 'channel', 'Radc', 'Nevent', 'filenamePos', 'filenameName', 'MontageName'});
D{i1} = ImaGIN_spm_eeg_rdata_ascii(S2);
case {'.msm', '.msr'}
S2 = ImaGIN_copy_fields(S2, S, {'Atlas', 'SEEG', 'Bipolar', 'coarse', 'SaveFile'});
D{i1} = ImaGIN_spm_eeg_rdata_msm_mono(S2);
case '.eeg' % BrainAmp or Nihon Kohden
% BrainAmp: There is a header in the same folder (.vhdr or .ahdr)
if exist(fullfile(fPath, [fBase, '.vhdr']), 'file') || exist(fullfile(fPath, [fBase, '.ahdr']), 'file')
BstFormat = 'EEG-BRAINAMP';
% Nihon Kohden
else
BstFormat = 'EEG-NK';
end
case '.bin'
BstFormat = 'EEG-DELTAMED';
case '.trc'
BstFormat = 'EEG-MICROMED';
case '.edf'
BstFormat = 'EEG-EDF';
case '.e'
BstFormat = 'EEG-NICOLET';
otherwise
error('Unknown format');
end
% Read file with Brainstorm functions
if ~isempty(BstFormat)
D{i1} = ImaGIN_convert_brainstorm(S2.Fdata, BstFormat, [S2.FileOut, '.mat'], SelectChannels, isSEEG);
end
end
% Processing stops
spm('Pointer','Arrow');
end
%% ===== CONVERT WITH BRAINSTORM I/O =====
% Converts EEG data from a native file to SPM-format, and saves a log of the channel selection
function D = ImaGIN_convert_brainstorm(InputFile, FileFormat, OutputFile, SelChannels, isSEEG) %#ok<STOUT>
% Open file with Brainstorm fuctions
switch (FileFormat)
% MICROMED .TRC
case 'EEG-MICROMED'
ImportOptions = db_template('ImportOptions');
ImportOptions.DisplayMessages = 0;
[sFileIn, ChannelMat] = in_fopen_micromed(InputFile, ImportOptions);
% Fix the names of some channels
% Code copied from original function: ImaGIN_spm_eeg_rdata_micromed_mono
for i = 1:length(ChannelMat.Channel)
chName = ChannelMat.Channel(i).Name;
% Lyon
chName = lower(chName(~isspace(chName)));
% Salpetriere: Can have both "C 4" and "c4" in the file: If a channel is duplicated, add a "s" after (the scalp EEG is always at the end)
if ismember(chName, {ChannelMat.Channel(1:i-1).Name})
chName = [chName, 's'];
end
% Rennes
chName = chName(setdiff(1:length(chName), strfind(chName,'.')));
% REMOVED 28/09/2018: for similar processing with other file formats
% % Remove zeros
% v_num = find((chName=='0') | (chName=='1') | (chName=='2') | (chName=='3') | (chName=='4') | (chName=='5') | (chName=='6') | (chName=='7') | (chName=='8') | (chName=='9'));
% if (length(v_num) > 1)
% if (v_num(2) == v_num(1) + 1) && strcmp(chName(v_num(1)), '0')
% chName = chName(setdiff(1:length(chName), v_num(1)));
% end
% end
ChannelMat.Channel(i).Name = chName;
end
% DELTAMED .bin
case 'EEG-DELTAMED'
[sFileIn, ChannelMat] = in_fopen_deltamed(InputFile);
% NIHON KOHDEN .EEG
case 'EEG-NK'
[sFileIn, ChannelMat] = in_fopen_nk(InputFile);
% EDF / XLTEK
case 'EEG-EDF'
ImportOptions = db_template('ImportOptions');
ImportOptions.DisplayMessages=0;
[sFileIn, ChannelMat] = in_fopen_edf(InputFile,ImportOptions);
% For XLTEK files exported as EDF + events in .txt file: reading the events
XltekEvtFile = [InputFile(1:end-3) 'txt'];
if exist(XltekEvtFile, 'file')
% Read events
sFileIn.events = in_events_xltek(sFileIn, XltekEvtFile);
% If there are more than 3 events: do some cleaning
if (length(sFileIn.events) > 3)
% Remove the first 3 event types
sFileIn.events(1:3) = [];
% Remove some other event types based on their names
iDel = find(ismember({sFileIn.events.label}, {'XLEvent','XLSpike','Gain/Changement de filtre','e','h','s','Opened relay','Closed relay'}) | ...
~cellfun(@(c)isempty(strfind(c, 'Closed relay')), {sFileIn.events.label}));
if ~isempty(iDel)
sFileIn.events(iDel) = [];
end
end
end
% For recordings from YUQ: many channels are classified as "POL" => Changing the type manually to EEG
iPOL = find(strcmpi({ChannelMat.Channel.Type}, 'POL'));
if ~isempty(iPOL)
[ChannelMat.Channel(iPOL).Type] = deal('EEG');
end
% BrainVision BrainAmp
case 'EEG-BRAINAMP'
[sFileIn, ChannelMat] = in_fopen_brainamp(InputFile);
% Nicolet
case 'EEG-NICOLET'
[sFileIn, ChannelMat] = in_fopen_nicolet(InputFile);
otherwise
error(['Unsupported file format: ', FileFormat]);
end
% Get list of available channel names
ChanLabelsIn = {ChannelMat.Channel.Name};
% Auto-detect good SEEG channels
if isempty(SelChannels)
% Get channels classified as EEG
iEEG = channel_find(ChannelMat.Channel, 'EEG,SEEG,ECOG,ECG,EKG');
% If there are no channels classified at EEG, take all the channels
if isempty(iEEG)
disp('ImaGIN> Warning: Channel types are not defined: not selecting by types.');
iEEG = 1:length(ChannelMat.Channel);
end
% Detect channels of interest
[iSelEeg, iEcg] = ImaGIN_select_channels({ChannelMat.Channel(iEEG).Name}, isSEEG);
% If no SEEG channels, read as EEG
if isempty(iSelEeg) && (isSEEG == 1)
disp('ImaGIN> No SEEG channels selected by name, reading as regular EEG instead.');
iSelEeg = ImaGIN_select_channels({ChannelMat.Channel(iEEG).Name}, 0);
isSEEG = 0;
end
% Convert indices back to the original list of channels
if isempty(iSelEeg)
disp('ImaGIN> No channels selected by name, reading as regular EEG instead of SEEG.');
iSel = iEEG;
isSEEG = 0;
else
iSel = iEEG(iSelEeg);
iEcg = iEEG(iEcg);
end
% Selected channels are passed in input
else
% Find channels: string, cell array of strings, or array of indices
iSel = ImaGIN_find_channels(ChanLabelsIn, SelChannels);
iEcg = [];
end
% If no channels are selected
if isempty(iSel)
error('No valid channel names were found.');
end
% Set channels to ECG
if ~isempty(iEcg)
[ChannelMat.Channel(iEcg).Type] = deal('ECG');
end
% Keep only these ones in the data
ChannelMat.Channel = ChannelMat.Channel(iSel);
% Get number of epochs
if isempty(sFileIn.epochs)
nEpochs = 1;
else
nEpochs = length(sFileIn.epochs);
end
% Loop on the epochs
for iEpoch = 1:nEpochs
% Read entire segment with Brainstorm functions
F = in_fread(sFileIn, ChannelMat, iEpoch, []);
F = F(iSel,:);
% File pointer to the output SPM file: keep only on epoch and a subset of the channels
sFileInSpm = sFileIn;
sFileInSpm.channelflag = sFileIn.channelflag(iSel);
% Remove all the events that are not in this segment
if (nEpochs > 1)
sFileInSpm.prop.times = sFileIn.epochs(iEpoch).times;
for iEvt = 1:length(sFileInSpm.events)
iEvtEpoch = find(sFileInSpm.events(iEvt).epochs == iEpoch);
sFileInSpm.events(iEvt).times = sFileInSpm.events(iEvt).times(iEvtEpoch);
sFileInSpm.events(iEvt).epochs = sFileInSpm.events(iEvt).epochs(iEvtEpoch);
end
sFileInSpm.epochs = [];
end
% Output name of the SPM .mat/.dat file
if (nEpochs == 1)
SpmFile = OutputFile;
else
SpmFile = strrep(OutputFile, '.mat', sprintf('-%d.mat', iEpoch));
end
% Export to SPM format
sFileOut = out_fopen_spm(SpmFile, sFileInSpm, ChannelMat);
out_fwrite_spm(sFileOut, [], [], F);
% Load new file to return the D structure
load(SpmFile);
% Save the original list of channels in a log file
ImaGIN_save_log(SpmFile, ['Convert: Available channels (' InputFile ')'], ChanLabelsIn);
% Save the list of channels in the output file
ChanLabelsOut = {ChannelMat.Channel.Name};
ImaGIN_save_log(SpmFile, ['Convert: Selected channels (' SpmFile ')'], ChanLabelsOut);
% Save the list of channels in the output file
ImaGIN_save_log(SpmFile, 'Convert: Removed channels:', sort(setdiff(ChanLabelsIn, ChanLabelsOut)));
end
set_final_status('OK');
end
%% ===== COPY FIELDS =====
% Copy fields from one structure to another.
%
% USAGE: sDest = ImaGIN_copy_fields(sDest, sSrc, fieldNames) % Copy requested fields
% sDest = ImaGIN_copy_fields(sDest, sSrc) % Copy all fields
%
% INPUT:
% - sDest : Destination structure
% - sSrc : Source structure
% - fieldNames : Cell array, list of fields to copy from sSrc to sDest
%
% Author: Francois Tadel, 2017
function sDest = ImaGIN_copy_fields(sDest, sSrc, fieldNames)
if isempty(sSrc)
return;
end
if (nargin < 3) || isempty(fieldNames)
fieldNames = fieldnames(sSrc);
end
for i = 1:length(fieldNames)
if isfield(sSrc, fieldNames{i})
sDest.(fieldNames{i}) = sSrc.(fieldNames{i});
end
end
end