-
Notifications
You must be signed in to change notification settings - Fork 159
/
process_convert_raw_to_lfp.m
398 lines (327 loc) · 17.6 KB
/
process_convert_raw_to_lfp.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
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
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
function varargout = process_convert_raw_to_lfp( varargin )
% PROCESS_CONVERT_RAW_TO_LFP: Convert the raw signals after spike sorting
% to LFP signals.
% The user has the option to perform Bayesian Spike Removal, a method
% described in: https://www.ncbi.nlm.nih.gov/pubmed/21068271
% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c)2000-2019 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: Konstantinos Nasiotis 2018
eval(macro_method);
end
%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
% Description the process
sProcess.Comment = 'Convert Raw to LFP';
sProcess.Category = 'custom';
sProcess.SubGroup = 'Electrophysiology';
sProcess.Index = 1203;
sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/e-phys/RawToLFP';
% Definition of the input accepted by this process
sProcess.InputTypes = {'raw'};
sProcess.OutputTypes = {'raw'};
sProcess.nInputs = 1;
sProcess.nMinFiles = 1;
sProcess.isSeparator = 1;
sProcess.processDim = 1; % Process channel by channel
sProcess.options.despikeLFP.Comment = 'Despike LFP <I><FONT color="#777777"> Highly Recommended if analysis uses SFC or STA</FONT></I>';
sProcess.options.despikeLFP.Type = 'checkbox';
sProcess.options.despikeLFP.Value = 1;
sProcess.options.filterbounds.Comment = 'LFP filtering limits';
sProcess.options.filterbounds.Type = 'range';
sProcess.options.filterbounds.Value = {[0.5, 150],'Hz',1};
% Definition of the options
% === Freq list
sProcess.options.freqlist.Comment = 'Notch filter Frequencies (Hz):';
sProcess.options.freqlist.Type = 'value';
sProcess.options.freqlist.Value = {[], 'list', 2};
sProcess.options.freqlistHelp.Comment = '<I><FONT color="#777777">Frequencies for notch filter (leave empty for no selection)</FONT></I>';
sProcess.options.freqlistHelp.Type = 'label';
sProcess.options.paral.Comment = 'Parallel processing';
sProcess.options.paral.Type = 'checkbox';
sProcess.options.paral.Value = 1;
sProcess.options.binsizeHelp.Comment = '<I><FONT color="#777777">The memory value below will be used in case the channels were not separated</FONT></I>';
sProcess.options.binsizeHelp.Type = 'label';
sProcess.options.binsize.Comment = 'Memory to use for demultiplexing';
sProcess.options.binsize.Type = 'value';
sProcess.options.binsize.Value = {1, 'GB', 1}; % This is used in case the electrodes are not separated yet (no spike sorting done), ot the temp folder was emptied
end
%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess) %#ok<DEFNU>
Comment = sProcess.Comment;
end
%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputs, method) %#ok<DEFNU>
OutputFiles = {};
for iInput = 1:length(sInputs)
sInput = sInputs(iInput);
%% Parameters
filterBounds = sProcess.options.filterbounds.Value{1}; % Filtering bounds for the LFP
notchFilterFreqs = sProcess.options.freqlist.Value{1}; % Notch Filter frequencies for the LFP
% Output frequency
NewFreq = 1000;
% Get method name
if (nargin < 3)
method = [];
end
%% Check for dependencies
% If DespikeLFP is selected, check if it is already installed
if sProcess.options.despikeLFP.Value
% Ensure we are including the DeriveLFP folder in the Matlab path
DeriveLFPDir = bst_fullfile(bst_get('BrainstormUserDir'), 'DeriveLFP');
if exist(DeriveLFPDir, 'dir')
addpath(genpath(DeriveLFPDir));
end
% Install DeriveLFP if missing
if ~exist('despikeLFP.m', 'file')
rmpath(genpath(DeriveLFPDir));
isOk = java_dialog('confirm', ...
['The DeriveLFP toolbox is not installed on your computer.' 10 10 ...
'Download and install the latest version?'], 'DeriveLFP');
if ~isOk
bst_report('Error', sProcess, sInput, 'This process requires the DeriveLFP toolbox.');
return;
end
downloadAndInstallDeriveLFP();
end
end
% Check for Signal Processing toolbox
if ~bst_get('UseSigProcToolbox')
bst_report('Warning', sProcess, [], [...
'The Signal Processing Toolbox is not available. Using the EEGLAB method instead (results may be much less accurate).' 10 ...
'This method is based on a FFT-based low-pass filter, followed by a spline interpolation.' 10 ...
'Make sure you remove the DC offset before resampling; EEGLAB function does not work well when the signals are not centered.']);
end
% Prepare parallel pool, if requested
if sProcess.options.paral.Value
try
poolobj = gcp('nocreate');
if isempty(poolobj)
parpool;
end
catch
sProcess.options.paral.Value = 0;
poolobj = [];
end
else
poolobj = [];
end
%% Initialize
% Prepare output file
ProtocolInfo = bst_get('ProtocolInfo');
newCondition = [sInput.Condition, '_LFP'];
sMat = in_bst(sInput.FileName, [], 0);
Fs = 1 / diff(sMat.Time(1:2)); % This is the original sampling rate
if mod(Fs,NewFreq) ~= 0
% This should never be an issue. Never heard of an acquisition
% system that doesn't record in multiples of 1kHz.
warning(['The downsampling might not be accurate. This process downsamples from ' num2str(Fs) ' to ' num2str(NewFreq) ' Hz'])
end
% Get new condition name
newStudyPath = file_unique(bst_fullfile(ProtocolInfo.STUDIES, sInput.SubjectName, newCondition));
% Output file name derives from the condition name
[tmp, rawBaseOut, rawBaseExt] = bst_fileparts(newStudyPath);
rawBaseOut = strrep([rawBaseOut rawBaseExt], '@raw', '');
% Full output filename
RawFileOut = bst_fullfile(newStudyPath, [rawBaseOut '.bst']); % ***
RawFileFormat = 'BST-BIN'; % ***
ChannelMat = in_bst_channel(sInput.ChannelFile); % ***
nChannels = length(ChannelMat.Channel);
% Get input study (to copy the creation date)
sInputStudy = bst_get('AnyFile', sInput.FileName);
sStudy = bst_get('ChannelFile', sInput.ChannelFile);
[tmp, iSubject] = bst_get('Subject', sStudy.BrainStormSubject, 1);
% Get new condition name
[tmp, ConditionName] = bst_fileparts(newStudyPath, 1);
% Create output condition
iOutputStudy = db_add_condition(sInput.SubjectName, ConditionName, [], sInputStudy.DateOfStudy);
ChannelMatOut = ChannelMat;
sFileTemplate = sMat.F;
%% Get the transformed channelnames that were used on the signal data naming. This is used in the derive lfp function in order to find the spike events label
% New channelNames - Without any special characters.
cleanChannelNames = str_remove_spec_chars({ChannelMat.Channel.Name});
%% Update fields before initializing the header on the binary file
sFileTemplate.prop.sfreq = NewFreq;
sFileTemplate.header.sfreq = NewFreq;
sFileTemplate.header.nsamples = round((sFileTemplate.prop.times(2) - sFileTemplate.prop.times(1)) .* NewFreq) + 1;
% Update file
sFileTemplate.CommentTag = sprintf('resample(%dHz)', round(NewFreq));
sFileTemplate.HistoryComment = sprintf('Filter [%0.1f-%0.1f]Hz - Resample from %0.2f Hz to %0.2f Hz (%s)', filterBounds(1), filterBounds(2), Fs, NewFreq, method);
sFileTemplate.despikeLFP = sProcess.options.despikeLFP.Value;
% Convert events to new sampling rate
newTimeVector = panel_time('GetRawTimeVector', sFileTemplate);
sFileTemplate.events = panel_record('ChangeTimeVector', sFileTemplate.events, Fs, newTimeVector);
%% Create an empty Brainstorm-binary file and assign the correct samples-times
% The sFileOut is what will be the final
[sFileOut, errMsg] = out_fopen(RawFileOut, RawFileFormat, sFileTemplate, ChannelMat);
%% Check if the files are separated per channel. If not do it now.
% These files will be converted to LFP right after
sFiles_temp_mat = in_spikesorting_rawelectrodes(sInput, sProcess.options.binsize.Value{1}(1) * 1e9, sProcess.options.paral.Value);
%% Filter and derive LFP
LFP = zeros(length(sFiles_temp_mat), length(downsample(sMat.Time,round(Fs/NewFreq)))); % This shouldn't create a memory problem
bst_progress('start', 'Spike-sorting', 'Converting RAW signals to LFP...', 0, (sProcess.options.paral.Value == 0) * nChannels);
if sProcess.options.despikeLFP.Value
if sProcess.options.paral.Value
parfor iChannel = 1:nChannels
LFP(iChannel,:) = BayesianSpikeRemoval(sFiles_temp_mat{iChannel}, filterBounds, sMat.F, ChannelMat, cleanChannelNames, notchFilterFreqs);
end
else
for iChannel = 1:nChannels
LFP(iChannel,:) = BayesianSpikeRemoval(sFiles_temp_mat{iChannel}, filterBounds, sMat.F, ChannelMat, cleanChannelNames, notchFilterFreqs);
bst_progress('inc', 1);
end
end
else
if sProcess.options.paral.Value
parfor iChannel = 1:nChannels
LFP(iChannel,:) = filter_and_downsample(sFiles_temp_mat{iChannel}, Fs, filterBounds, notchFilterFreqs);
end
else
for iChannel = 1:nChannels
LFP(iChannel,:) = filter_and_downsample(sFiles_temp_mat{iChannel}, Fs, filterBounds, notchFilterFreqs);
bst_progress('inc', 1);
end
end
end
% WRITE OUT
sFileOut = out_fwrite(sFileOut, ChannelMatOut, [], [], [], LFP);
% Import the RAW file in the database viewer and open it immediately
RawFile = import_raw({sFileOut.filename}, 'BST-BIN', iSubject);
RawFile = RawFile{1};
% Modify it slightly since this is an LFP raw file
[sStudy, iStudy] = bst_get('DataFile', RawFile);
RawMat = load(RawFile);
RawMat.Comment = 'Link to LFP file';
RawNewFile = strrep(RawFile, 'data_0raw', 'data_0lfp');
bst_save(RawNewFile, RawMat, 'v6');
OutputFiles{end + 1} = RawNewFile;
delete(RawFile);
db_reload_studies(iStudy);
end
end
function data = filter_and_downsample(inputFilename, Fs, filterBounds, notchFilterFreqs)
sMat = load(inputFilename); % Make sure that a variable named data is loaded here. This file is saved as an output from the separator
if ~isempty(notchFilterFreqs)
% Apply notch filter
data = process_notch('Compute', sMat.data, sMat.sr, notchFilterFreqs)';
else
data = sMat.data';
end
% Aplly final filter
data = bst_bandpass_hfilter(data, Fs, filterBounds(1), filterBounds(2), 0, 0);
data = downsample(data, round(Fs/1000)); % The file now has a different sampling rate (fs/30) = 1000Hz.
end
%% BAYESIAN DESPIKING
function data_derived = BayesianSpikeRemoval(inputFilename, filterBounds, sFile, ChannelMat, cleanChannelNames, notchFilterFreqs)
sMat = load(inputFilename); % Make sure that a variable named data is loaded here. This file is saved as an output from the separator
%% Instead of just filtering and then downsampling, DeriveLFP is used, as in:
% https://www.ncbi.nlm.nih.gov/pubmed/21068271
if ~isempty(notchFilterFreqs)
% Apply notch filter
data_deligned = process_notch('Compute', sMat.data, sMat.sr, notchFilterFreqs);
else
data_deligned = sMat.data;
end
Fs = sMat.sr;
% Assume that a spike lasts 3ms
nSegment = sMat.sr * 0.003;
Bs = eye(nSegment); % 60x60
opts.displaylevel = 0; % 0 gets rid of all the outputs
% 2 shows the optimization steps
%% Get the channel Index of the file that is imported
[tmp, ChannelName] = fileparts(inputFilename);
ChannelName = strrep(ChannelName, 'raw_elec_', '');
% I need to find the transformed channelname index that is used at the filename.
iChannel = find(ismember(cleanChannelNames, ChannelName));
% Get the index of the event that show this electrode's spikes
allEventLabels = {sFile.events.label};
spike_event_prefix = process_spikesorting_supervised('GetSpikesEventPrefix');
% First check if there is only one neuron on the channel
iEventforElectrode = find(ismember(allEventLabels, [spike_event_prefix ' ' ChannelMat.Channel(iChannel).Name])); % Find the index of the spike-events that correspond to that electrode (Exact string match)
%Then check if there are multiple
if isempty(iEventforElectrode)
iEventforElectrode = find(ismember(allEventLabels, [spike_event_prefix ' ' ChannelMat.Channel(iChannel).Name ' |1|']));% Find the index of the spike-events that correspond to that electrode (Exact string match)
if ~isempty(iEventforElectrode)
iEventforElectrode = find(not(cellfun('isempty', strfind(allEventLabels, [spike_event_prefix ' ' ChannelMat.Channel(iChannel).Name ' |']))));
end
end
% If there are no neurons picked up from that electrode, continue
% Apply despiking around the spiking times
if ~isempty(iEventforElectrode) % If there are spikes on that electrode
spkSamples = ([sFile.events(iEventforElectrode).times] .* sFile.prop.sfreq); % All spikes, from all neurons on that electrode
% We'd like to assume that a spike lasts
% from-10 samples to +19 samples (3 ms) for 10000 Hz sampling rate compared to its peak
% Since spktimes are the time of the peak of each spike, we subtract 15
% from spktimes to obtain the start times of the spikes
if mod(length(data_deligned),2)~=0
data_deligned_temp = [data_deligned;0];
g = fitLFPpowerSpectrum(data_deligned_temp,filterBounds(1),filterBounds(2),sFile.prop.sfreq);
S = zeros(length(data_deligned_temp),1);
iSpk = round(spkSamples - nSegment/2);
iSpk = iSpk(iSpk > 0); % Only keep positive indices
S(iSpk) = 1; % This assumes the spike starts at 1/2 before the trough of the spike
data_derived = despikeLFP(data_deligned_temp,S,Bs,g,opts);
data_derived = data_derived.z';
data_derived = bst_bandpass_hfilter(data_derived, Fs, filterBounds(1), filterBounds(2), 0, 0);
else
g = fitLFPpowerSpectrum(data_deligned,filterBounds(1),filterBounds(2),sFile.prop.sfreq);
S = zeros(length(data_deligned),1);
iSpk = round(spkSamples - nSegment/2);
iSpk = iSpk(iSpk > 0); % Only keep positive indices
S(iSpk) = 1; % This assumes the spike starts at 1/2 before the trough of the spike
data_derived = despikeLFP(data_deligned,S,Bs,g,opts);
data_derived = data_derived.z';
data_derived = bst_bandpass_hfilter(data_derived, Fs, filterBounds(1), filterBounds(2), 0, 0);
end
else
data_derived = data_deligned';
data_derived = bst_bandpass_hfilter(data_derived, Fs, filterBounds(1), filterBounds(2), 0, 0);
end
data_derived = downsample(data_derived, sMat.sr/1000); % The file now has a different sampling rate (fs/30) = 1000Hz
end
%% ===== DOWNLOAD AND INSTALL DeriveLFP =====
function downloadAndInstallDeriveLFP()
DeriveLFPDir = bst_fullfile(bst_get('BrainstormUserDir'), 'DeriveLFP');
DeriveLFPTmpDir = bst_fullfile(bst_get('BrainstormUserDir'), 'DeriveLFP_tmp');
url = 'http://packlab.mcgill.ca/despikingtoolbox.zip';
% If folders exists: delete
if isdir(DeriveLFPDir)
file_delete(DeriveLFPDir, 1, 3);
end
if isdir(DeriveLFPTmpDir)
file_delete(DeriveLFPTmpDir, 1, 3);
end
% Create folder
mkdir(DeriveLFPTmpDir);
% Download file
zipFile = bst_fullfile(DeriveLFPTmpDir, 'despikingtoolbox.zip');
errMsg = gui_brainstorm('DownloadFile', url, zipFile, 'DeriveLFP download'); % This line downloads the file
if ~isempty(errMsg)
error(['Impossible to download DeriveLFP:' errMsg]);
end
% Unzip file
bst_progress('start', 'DeriveLFP', 'Installing DeriveLFP...');
unzip(zipFile, DeriveLFPTmpDir);
newDeriveLFPDir = bst_fullfile(DeriveLFPTmpDir);
% Move directory to proper location
file_move(newDeriveLFPDir, DeriveLFPDir);
% Delete unnecessary files
file_delete(DeriveLFPTmpDir, 1, 3);
% Add to Matlab path
addpath(genpath(DeriveLFPDir));
end