/
in_fopen_fif.m
285 lines (266 loc) · 10.9 KB
/
in_fopen_fif.m
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
function [sFile, ChannelMat] = in_fopen_fif(DataFile, ImportOptions, nLink)
% IN_FOPEN_FIF: Open a FIF file, and get all the data and channel information.
%
% USAGE: [sFile, ChannelMat] = in_fopen_fif(DataFile, ImportOptions, nLink=1)
% [sFile, ChannelMat] = in_fopen_fif(DataFile)
%
% INPUT:
% - ImportOptions : Structure that describes how to import the recordings. Fields directly used:
% => Fields used: ChannelAlign, ChannelReplace, EventsMode, DisplayMessages
% - nLink : Index in the chain of linked files
% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% 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
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2009-2022
%% ===== PARSE INPUTS =====
if (nargin < 3) || isempty(nLink)
nLink = 1;
end
if (nargin < 2) || isempty(ImportOptions)
ImportOptions = db_template('ImportOptions');
end
%% ===== CHECK FILE TYPE =====
% Check if file is an event file
[fPath, fBase, fExt] = bst_fileparts(DataFile);
if ~strcmpi(fExt, '.fif')
error(['File is not in FIF format: ' 10 '"' DataFile '"']);
elseif (length(fBase) > 4) && strcmpi(fBase(end-3:end), '-eve')
% Event file => try to find the regular file
DataFile = bst_fullfile(fPath, [fBase(1:end-4), fExt]);
if ~file_exist(DataFile)
error('You are trying to import an event file.');
else
warning(['Selected file was an event file. Now reading file:' 10 DataFile]);
end
end
%% ===== OPEN FIF FILE =====
% Open file
[ fid, tree ] = fiff_open(DataFile);
if (fid < 0)
error(['Cannot open FIFF file : "' DataFile '"']);
end
% Read info structure
try
[ info, meas ] = fiff_read_meas_info(fid, tree);
catch
err = lasterror;
disp([10 'BST> Error: Could not find measurement data: ' 10 err.message 10]);
info.chs = [];
info.dig = fif_read_headpoints(fid, tree);
meas = [];
end
% Check if file was opened successfully
isNoData = ~isfield(info, 'sfreq');
if isNoData && isempty(info.chs) && isempty(info.dig)
sFile = [];
ChannelMat = [];
fclose(fid);
return
end
% Initialize file structure
sFile = db_template('sfile');
% Fill this structure
sFile.filename = DataFile;
sFile.format = 'FIF';
% Part reserved to FIF files with recordings
if ~isNoData
sFile.prop.sfreq = double(info.sfreq);
end
% Header of the FIF file
sFile.header.tree = tree;
sFile.header.info = info;
sFile.header.meas = meas;
sFile.header.fif_list = {DataFile};
sFile.header.fif_times = [];
sFile.header.fif_headers = [];
%% ===== READ CHANNEL FILE =====
% Read channel file from FIF and default coils file
[ChannelMat, Device] = in_channel_fif(sFile, ImportOptions);
% Get bad channels
ChannelFlag = ones(length(ChannelMat.Channel),1);
if isfield(info, 'bads') && ~isempty(info.bads)
% For each bad channel (referenced by channel name): find its indice
for iBad = 1:length(info.bads)
iChan = find(strcmpi(info.bads(iBad), info.ch_names));
ChannelFlag(iChan) = -1;
end
end
% Find if results are already compensated
if ~isempty(ChannelMat.MegRefCoef)
% Get current level of compensation
iMeg = good_channel(ChannelMat.Channel, [], 'MEG');
currentComp = bitshift(double([info.chs(iMeg).coil_type]), -16);
% If not all the same value: error
if ~all(currentComp == currentComp(1))
error('CTF compensation is not set equally on all MEG channels');
end
% Current compensation order
sFile.prop.currCtfComp = currentComp(1);
% Destination compensation order (keep compensation order, unless it is 0)
if (currentComp(1) == 0)
sFile.prop.destCtfComp = 3;
else
sFile.prop.destCtfComp = currentComp(1);
end
end
% Store results in sFile structure
sFile.device = Device;
sFile.channelflag = ChannelFlag;
sFile.byteorder = 'b';
% Acquisition date (saved in POSIX format in FIF file)
if isfield(info, 'meas_date') && ~isempty(info.meas_date)
sFile.acq_date = str_date(info.meas_date(1), 'posix');
end
%% ===== READ DATA DESCRIPTION =====
% Get number of epochs
[epochs, sFile.header.epochData] = fif_get_epochs(sFile, fid);
nEpochs = length(epochs);
% === CHANNELS ONLY ===
% No recordings in FIF file, only channels => Nothing to read
if isempty(meas)
sFile.format = 'channels';
% === RAW FILE ===
elseif (nEpochs == 0)
% Read RAW file information
raw = fif_setup_raw(sFile, fid, 1);
% Fill sFile structure
sFile.prop.times = double([raw.first_samp, raw.last_samp]) ./ sFile.prop.sfreq;
sFile.header.raw = raw;
sFile.header.fif_times = sFile.prop.times;
sFile.header.fif_headers = {rmfield(sFile.header, {'fif_list', 'fif_times', 'fif_headers'})};
% Read events information
if iscell(ImportOptions.EventsMode) || ~strcmpi(ImportOptions.EventsMode, 'ignore')
[sFile.events, ImportOptions] = fif_read_events(sFile, ChannelMat, ImportOptions);
% Operation cancelled by user
if isequal(sFile.events, -1)
sFile = [];
fclose(fid);
return;
end
end
% === EVOKED/EPOCHED FILE ===
else
% Build epochs structure
for i = 1:length(epochs)
sFile.epochs(i).label = epochs(i).label;
sFile.epochs(i).times = epochs(i).times;
sFile.epochs(i).nAvg = epochs(i).nAvg;
sFile.epochs(i).select = isempty(strfind(epochs(i).label, 'std err'));
sFile.epochs(i).bad = 0;
sFile.epochs(i).channelflag = [];
end
% Extract global min/max for time and samples indices
sFile.prop.times = [min([sFile.epochs.times]), max([sFile.epochs.times])];
end
%% ===== GET LINKED FILES =====
% Recordings bigger than 2Gb can't be stored in FIF format, and need to be split in multiple files.
% We expect to call in_fopen_fif.m on the first .fif file in the list, and then the files should be
% chained using the fields FIFF_REF_FILE_NAME.
% If linked files are found, in_fopen_fif is called recursively and appended to the definition of the first file.
% Only RAW files can be linked
if ~isempty(meas) && (nEpochs == 0) && (~isempty(raw.next_fname) || ~isempty(raw.next_num))
NextFile = [];
% 1) If there is already a file name, try to use it
if ~isempty(raw.next_fname)
NextFile = bst_fullfile(fPath, raw.next_fname);
% File doesn't exist...
if ~file_exist(NextFile)
NextFile = [];
end
end
% 2) Try to find the format of the files using the file number
% (major problem: sometimes the first file is not numbered)
if isempty(NextFile) && ~isempty(raw.next_num)
% Trying with -0
numTag = sprintf('-%d', raw.next_num - 1);
iNum = strfind(sFile.filename, numTag);
if ~isempty(iNum)
% Keep only the last occurrence (the same string may appear before in the filename)
iNum = iNum(end);
NextFile = [sFile.filename(1:iNum-1), sprintf('-%d', raw.next_num), sFile.filename(iNum+length(numTag):end)];
else
% Trying with -00 (for BIDS-compatible split naming: https://github.com/mne-tools/mne-python/blob/cd0eff12535880cd7a6551ad4ceeff771ea8b3a9/mne/io/utils.py#L323)
numTag = sprintf('-%02d', raw.next_num - 1);
iNum = strfind(sFile.filename, numTag);
if ~isempty(iNum)
iNum = iNum(end);
NextFile = [sFile.filename(1:iNum-1), sprintf('-%02d', raw.next_num), sFile.filename(iNum+length(numTag):end)];
end
end
% File doesn't exist...
if ~file_exist(NextFile)
NextFile = [];
end
end
% Get the other FIF files in the folder, to look for file #1 (in case number #0 is not numbered)
if isempty(NextFile) && (raw.next_num == 1)
dirFif = dir(bst_fullfile(fPath, ['*', fExt]));
curFile = [fBase, fExt];
listFif = setdiff({dirFif.name}, curFile);
numTag = sprintf('-%d', raw.next_num);
% Remove the num tag in all the filenames, and see if we find the current file
for iFile = 1:length(listFif)
iNum = strfind(listFif{iFile}, numTag);
if ~isempty(iNum)
iNum = iNum(end);
if strcmp(curFile, [listFif{iFile}(1:iNum-1), listFif{iFile}(iNum+length(numTag):end)])
NextFile = bst_fullfile(fPath, listFif{iFile});
break;
end
end
end
end
% Read linked file
if ~isempty(NextFile)
% Display linked file
disp([10 'FIF> Linking next file: ' NextFile]);
% Load the header of the linked file recursively
sFileNext = in_fopen_fif(NextFile, ImportOptions, nLink + 1);
% Concatenate files (check time compatibility)
if isempty(sFileNext)
% File could not be read...
warning(['FIF> Missing link: Could not read file: ' NextFile]);
elseif (sFile.prop.sfreq ~= sFileNext.prop.sfreq) || (sFileNext.prop.times(1) - sFile.prop.times(2) - 1/sFile.prop.sfreq > 0.001)
warning(['FIF> Missing link: Recordings in the following files are not contiguous:' 10 ...
DataFile ': ' sprintf('%1.3fs - %1.3fs', sFile.prop.times) 10 ...
NextFile ': ' sprintf('%1.3fs - %1.3fs', sFileNext.prop.times)]);
else
sFile.prop.times = [sFile.prop.times(1), sFileNext.prop.times(2)];
% Add to list of files
sFile.header.fif_list = [sFile.header.fif_list, sFileNext.header.fif_list];
sFile.header.fif_times = cat(1, sFile.header.fif_times, sFileNext.header.fif_times);
sFile.header.fif_headers = [sFile.header.fif_headers, sFileNext.header.fif_headers];
% Import events from the next file
if ~isempty(sFileNext.events)
sFile = import_events(sFile, [], sFileNext.events);
end
end
else
warning(['FIF> Missing link: Could not find the file following "' DataFile '".']);
end
end
% Removing chaining information if one file only
if (nLink == 1) && (length(sFile.header.fif_list) == 1)
sFile.header.fif_list = [];
sFile.header.fif_times = [];
sFile.header.fif_headers = [];
end
% Close file
if ~isempty(fopen(fid))
fclose(fid);
end