-
Notifications
You must be signed in to change notification settings - Fork 768
Expand file tree
/
Copy pathft_spikedownsample.m
More file actions
289 lines (258 loc) · 12.2 KB
/
Copy pathft_spikedownsample.m
File metadata and controls
289 lines (258 loc) · 12.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
function [cfg] = ft_spikedownsample(cfg)
% FT_SPIKEDOWNSAMPLE takes electrophysiological data that was continuoudly
% sampled at 32KHz and preprocesses and downsamples it to obtain the LFP
% data, which can subsequently be processed in more detail.
%
% Use as
% [cfg] = ft_spikedownsample(cfg)
%
% The configuration should contain
% cfg.dataset = string with the input dataset
% cfg.output = string with the output dataset (default is determined automatic)
% cfg.dataformat = string with the output dataset format, see WRITE_DATA
% cfg.channel = Nx1 cell-array with selection of channels (default = 'all'),
% see FT_CHANNELSELECTION for details
% cfg.fsample = desired sampling frequency in Hz (default = 1000)
% cfg.method = resampling method, can be 'resample', 'decimate' or 'downsample'
% cfg.timestampdefinition = 'orig' or 'sample'
% cfg.channelprefix = string, will be added to channel name, e.g. 'lfp' -> 'lfp_ncs001' (default = [])
% cfg.calibration = optional scaling factor to apply to the data to convert it in uV, see below
%
% The Neuralynx acquisition system at the FCDC in Nijmegen makes use of
% Plexon headstages which have a amplification of 20x. The data that is
% written by the Neuralynx acquisition software therefore is 20x larger
% than the true microvolt values. When operating FT_SPIKEDOWNSAMPLE on the
% *.ncs files that are recorded with the Neuralynx Cheetah software, the
% calibration should be set to 1/20. The raw dma file (*.nrd) and the
% splitted DMA files contains AD values that are not scaled in uV and
% require an additional factor of 64x. If you operate FT_SPIKEDOWNSAMPLE on
% raw dma files or on splitted DMA files, the calibration should be set to
% 1/(64*20).
%
% The default is to process the full dataset. You can select a latency range with
% cfg.latency = [begin end], default is [0 inf]
% or you can specify multiple latency segments with
% cfg.latency = [b1 e1; b2 e2; ...]
%
% Furthermore, the configuration can contain the following preprocessing options
% cfg.preproc.lpfilter = 'no' or 'yes' lowpass filter
% cfg.preproc.hpfilter = 'no' or 'yes' highpass filter
% cfg.preproc.bpfilter = 'no' or 'yes' bandpass filter
% cfg.preproc.lnfilter = 'no' or 'yes' line noise removal using notch filter
% cfg.preproc.dftfilter = 'no' or 'yes' line noise removal using discrete fourier transform
% cfg.preproc.medianfilter = 'no' or 'yes' jump preserving median filter
% cfg.preproc.lpfreq = lowpass frequency in Hz
% cfg.preproc.hpfreq = highpass frequency in Hz
% cfg.preproc.bpfreq = bandpass frequency range, specified as [low high] in Hz
% cfg.preproc.lnfreq = line noise frequency in Hz, default 50Hz
% cfg.preproc.lpfiltord = lowpass filter order
% cfg.preproc.hpfiltord = highpass filter order
% cfg.preproc.bpfiltord = bandpass filter order
% cfg.preproc.lnfiltord = line noise notch filter order
% cfg.preproc.medianfiltord = length of median filter
% cfg.preproc.lpfilttype = digital filter type, 'but' (default) or 'fir'
% cfg.preproc.hpfilttype = digital filter type, 'but' (default) or 'fir'
% cfg.preproc.bpfilttype = digital filter type, 'but' (default) or 'fir'
% cfg.preproc.lpfiltdir = filter direction, 'twopass' (default) or 'onepass'
% cfg.preproc.hpfiltdir = filter direction, 'twopass' (default) or 'onepass'
% cfg.preproc.bpfiltdir = filter direction, 'twopass' (default) or 'onepass'
% cfg.preproc.detrend = 'no' or 'yes'
% cfg.preproc.demean = 'no' or 'yes'
% cfg.preproc.baselinewindow = [begin end] in seconds, the default is the complete trial
% cfg.preproc.hilbert = 'no' or 'yes'
% cfg.preproc.rectify = 'no' or 'yes'
% Copyright (C) 2005-2010, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
% FieldTrip 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.
%
% FieldTrip 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 FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$
% these are used by the ft_preamble/ft_postamble function and scripts
ft_revision = '$Id$';
ft_nargin = nargin;
ft_nargout = nargout;
% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble provenance
% set the general defaults
if ~isfield(cfg, 'dataset'), cfg.dataset = []; end
if ~isfield(cfg, 'output'), cfg.output = []; end
if ~isfield(cfg, 'channel'), cfg.channel = 'all'; end
if ~isfield(cfg, 'channelprefix'), cfg.channelprefix = []; end
if ~isfield(cfg, 'latency'), cfg.latency = [0 inf]; end
if ~isfield(cfg, 'headerformat'), cfg.headerformat = []; end
if ~isfield(cfg, 'dataformat'), cfg.dataformat = []; end
% set the specific defaults
if ~isfield(cfg, 'fsample'), cfg.fsample = 1000; end
%if ~isfield(cfg, 'method'), cfg.method = []; end
if ~isfield(cfg, 'precision'), cfg.precision = 'double'; end
if ~isfield(cfg, 'calibration')
if ft_filetype(cfg.dataset, 'neuralynx_dma') || ft_filetype(cfg.dataset, 'neuralynx_sdma')
error('You must specify cfg.calibration in case of neuralynx_dma or neuralynx_sdma');
else
cfg.calibration = 1;
end
end
% check that the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'required', {'method', 'calibration', 'dataformat'});
cfg = ft_checkconfig(cfg, 'forbidden', {'ADtoUV'});
% ensure that the preproc specific options are located in the preproc substructure
cfg = ft_checkconfig(cfg, 'createsubcfg', {'preproc'});
cfg.preproc = ft_checkconfig(cfg.preproc, 'renamed', {'blc', 'demean'});
cfg.preproc = ft_checkconfig(cfg.preproc, 'renamed', {'blcwindow', 'baselinewindow'});
status = mkdir(cfg.output);
if ~status
error('error creating LFP output dataset %s', cfg.output);
end
% read the header of the completete dataset
hdr = ft_read_header(cfg.dataset, 'headerformat', cfg.headerformat);
cfg.channel = ft_channelselection(cfg.channel, hdr.label);
chansel = match_str(hdr.label, cfg.channel);
if strcmp(cfg.timestampdefinition, 'sample')
% the default would be to keep the original definition of timestamps as determined from looking at the file
% here the definition of timestamps is changed to correspond with samples at the original sampling rate
hdr.TimeStampPerSample = 1;
hdr.FirstTimeStamp = 1;
hdr.LastTimeStamp = hdr.nSamples*hdr.nTrials;
end
if hdr.nSamples<1
error('the input dataset contains no samples');
elseif length(chansel)<1
error('the input selection contains no channels');
end
% give some feedback, based on the complete data
fprintf('data contains %10d channels\n', hdr.nChans);
fprintf('selected %10d channels\n', length(chansel));
numsample = [];
numsegment = size(cfg.latency,1);
for j=1:numsegment
begsample(j) = max(round(cfg.latency(j,1) * hdr.Fs + 1), 1);
endsample(j) = min(round(cfg.latency(j,2) * hdr.Fs ), hdr.nSamples);
numsample(j) = endsample(j) - begsample(j) + 1;
cfg.latency(j,1) = (begsample(j)-1)/hdr.Fs;
cfg.latency(j,2) = (endsample(j) )/hdr.Fs;
end
numsample = sum(numsample);
fprintf('data contains %10d samples\n', hdr.nSamples);
fprintf('selected %10d samples in %d segments\n', numsample, numsegment);
s = floor(hdr.nSamples ./ hdr.Fs);
m = floor(s/60);
h = floor(m/60);
m = m - 60*h;
s = s - 60*m - 60*60*h;
fprintf('duration of data %02dh:%02dm:%02ds\n', h, m, s);
s = floor(numsample ./ hdr.Fs);
m = floor(s/60);
h = floor(m/60);
m = m - 60*h;
s = s - 60*m - 60*60*h;
fprintf('duration of selection %02dh:%02dm:%02ds\n', h, m, s);
fprintf('estimated memory usage %d MB\n', round((numsample*(8+8+2))/(1024^2)));
% determine the exact sampling frequency for the LFP and MUA
fac = hdr.Fs/cfg.fsample;
if strcmp(cfg.method, 'resample');
% this works with arbitrary changes in the frequency
else
% this requires an integer downsampling fraction
fac = round(fac);
cfg.fsample = hdr.Fs/fac;
end
if numsegment==1
% determine the timestamps of the first and last sample after downsampling
tsoff = cast(0, class(hdr.FirstTimeStamp));
tsbeg = double(hdr.FirstTimeStamp - tsoff);
tsend = double(hdr.LastTimeStamp - tsoff);
tsdif = (tsend-tsbeg)/(hdr.nSamples-1); % should be the same as hdr.TimeStampPerSample
% this applies to all samples in the datafile, i.e. not on the selected latency window
resampled_tsbeg = tsbeg + ((fac-1)/2)*tsdif;
resampled_tsend = tsend - ((fac-1)/2)*tsdif;
resampled_tsdif = fac * tsdif;
% this applies to the selected latency window
selected_tsbeg = double(hdr.FirstTimeStamp - tsoff) + (begsample-1)*tsdif;
selected_tsend = double(hdr.FirstTimeStamp - tsoff) + (endsample-1)*tsdif;
resampled_selected_tsbeg = selected_tsbeg + ((fac-1)/2)*tsdif;
resampled_selected_tsend = selected_tsend - ((fac-1)/2)*tsdif;
% these should be integers, but will be casted to uint32 or uint64 later
resampled_selected_tsbeg = round(resampled_selected_tsbeg);
resampled_selected_tsend = round(resampled_selected_tsend);
resampled_tsdif ; % this remains a double
else
warning('multiple data segments were selected, timestamps are invalid');
end
% process each channel separetely
for i=chansel(:)'
fprintf('reading channel %d, ''%s''\n', i, hdr.label{i});
% read the data of a single channel and concatenate into one vector
org = zeros(1,sum(numsample));
for j=1:numsegment
buf = ft_read_data(cfg.dataset, 'header', hdr, 'begsample', begsample(j), 'endsample', endsample(j), 'chanindx', i);
% apply the optional calibration to the data to ensure that the numbers represent uV
if cfg.calibration~=1
buf = cfg.calibration * buf;
end
if j==1
begsegment = 1;
endsegment = numsample(j);
else
begsegment = numsample(j-1) + 1;
endsegment = sum(numsample(1:j));
end
% concatenate the data into one large vector
org(begsegment:endsegment) = buf;
clear buf;
end
label = hdr.label(i); % this should be cell-array for preproc
% apply preprocessing and downsample
fprintf('preprocessing\n');
dat = preproc(org, label, offset2time(0, hdr.Fs, size(org,2)), cfg.preproc);
dat = ft_preproc_resample(dat, hdr.Fs, cfg.fsample, cfg.method);
chanhdr = [];
chanhdr.nChans = 1;
chanhdr.nTrials = 1; % since continuous
chanhdr.nSamplesPre = 1; % since continuous
chanhdr.Fs = cfg.fsample;
chanhdr.nSamples = length(dat);
if isempty(cfg.channelprefix)
% the label should be a cell-array of length one
chanhdr.label = hdr.label(i);
else
% add a prefix to the channel name
chanhdr.label = {[cfg.channelprefix '_' hdr.label{i}]};
end
if numsegment==1
chanhdr.FirstTimeStamp = resampled_selected_tsbeg;
chanhdr.LastTimeStamp = resampled_selected_tsend;
chanhdr.TimeStampPerSample = resampled_tsdif;
else
% these are not defined in case of multiple segments
warning('multiple data segments were selected, timestamps are invalid');
chanhdr.FirstTimeStamp = nan;
chanhdr.LastTimeStamp = nan;
chanhdr.TimeStampPerSample = nan;
end
if strcmp(cfg.dataformat, 'fcdc_matbin')
chanhdr.precision = cfg.precision;
end
% the output file contains the new channel name
datafile = fullfile(cfg.output, chanhdr.label{1}); % this is without filename extension
fprintf('writing to file ''%s''\n', datafile);
ft_write_data(datafile, dat, 'header', chanhdr, 'dataformat', cfg.dataformat);
% keep memory tidy
clear dat
end % for each file
% do the general cleanup and bookkeeping at the end of the function
ft_postamble provenance