/
ft_realtime_downsample.m
156 lines (132 loc) · 6.83 KB
/
ft_realtime_downsample.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
function ft_realtime_downsample(cfg)
% FT_REALTIME_DOWNSAMPLE reads realtime data from one buffer and writes it after downsampling
% to another buffer.
%
% Use as
% ft_realtime_downsample(cfg)
% with the following configuration options
% cfg.channel = cell-array, see FT_CHANNELSELECTION (default = 'all')
% cfg.decimation = integer, downsampling factor (default = 1, no downsampling)
% cfg.order = interger, order of butterworth lowpass filter (default = 4)
% cfg.cutoff = double, cutoff frequency of lowpass filter (default = 0.8*Nyquist-freq.)
%
% The source of the data is configured as
% cfg.source.dataset = string
% or alternatively to obtain more low-level control as
% cfg.source.datafile = string
% cfg.source.headerfile = string
% cfg.source.eventfile = string
% cfg.source.dataformat = string, default is determined automatic
% cfg.source.headerformat = string, default is determined automatic
% cfg.source.eventformat = string, default is determined automatic
%
% The target to write the data to is configured as
% cfg.target.datafile = string, target destination for the data (default = 'buffer://localhost:1972')
% cfg.target.dataformat = string, default is determined automatic
%
% To stop this realtime function, you have to press Ctrl-C
% Copyright (C) 2008, Robert Oostenveld
% Copyright (C) 2010, Stefan Klanke
%
% 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$
% set the defaults
if ~isfield(cfg, 'source'), cfg.source = []; end
if ~isfield(cfg, 'target'), cfg.target = []; end
if ~isfield(cfg.source, 'headerformat'), cfg.source.headerformat = []; end % default is detected automatically
if ~isfield(cfg.source, 'dataformat'), cfg.source.dataformat = []; end % default is detected automatically
if ~isfield(cfg.target, 'headerformat'), cfg.target.headerformat = []; end % default is detected automatically
if ~isfield(cfg.target, 'dataformat'), cfg.target.dataformat = []; end % default is detected automatically
if ~isfield(cfg.target, 'datafile'), cfg.target.datafile = 'buffer://localhost:1972'; end
if ~isfield(cfg, 'minblocksize'), cfg.minblocksize = 0; end % in seconds
if ~isfield(cfg, 'maxblocksize'), cfg.maxblocksize = 1; end % in seconds
if ~isfield(cfg, 'channel'), cfg.channel = 'all'; end
if ~isfield(cfg, 'decimation'), cfg.decimation = 1; end
if ~isfield(cfg, 'order'), cfg.order = 4; end
if cfg.decimation < 1 || cfg.decimation~=round(cfg.decimation)
error 'Decimation factor must be integer quantity >= 1';
end
% translate dataset into datafile+headerfile
cfg.source = ft_checkconfig(cfg.source, 'dataset2files', 'yes');
cfg.target = ft_checkconfig(cfg.target, 'dataset2files', 'yes');
ft_checkconfig(cfg.source, 'required', {'datafile' 'headerfile'});
ft_checkconfig(cfg.target, 'required', {'datafile' 'headerfile'});
% ensure that the persistent variables related to caching are cleared
clear ft_read_header
% read the header for the first time
hdr = ft_read_header(cfg.source.headerfile);
fprintf('updating the header information, %d samples available\n', hdr.nSamples*hdr.nTrials);
targethdr = hdr;
targethdr.Fs = targethdr.Fs/cfg.decimation;
% Calculate cutoff frequency expressed in Nyquist freq of original signal
if ~isfield(cfg, 'cutoff')
cutoff = 0.8/cfg.decimation;
else
cutoff = cfg.cutoff / (0.5*hdr.Fs);
if cutoff >= 1
ft_error('Cutoff frequency too high');
end
end
[B,A] = butter(cfg.order, cutoff, 'low');
% define a subset of channels for reading
cfg.channel = ft_channelselection(cfg.channel, hdr.label);
chanindx = match_str(hdr.label, cfg.channel);
nchan = length(chanindx);
if nchan==0
ft_error('no channels were selected');
end
minblocksmp = round(cfg.minblocksize*hdr.Fs);
minblocksmp = max(minblocksmp, 1);
maxblocksmp = round(cfg.maxblocksize*hdr.Fs);
prevSample = 0;
count = 0;
% create filter model and downsampling model
FM = online_filter_init(B, A, zeros(nchan, 1));
DM = online_downsample_init(cfg.decimation);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% this is the general BCI loop where realtime incoming data is handled
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
while true
% determine number of samples available in buffer
hdr = ft_read_header(cfg.source.headerfile, 'cache', true);
% see whether new samples are available
newsamples = (hdr.nSamples*hdr.nTrials-prevSample);
if newsamples>=minblocksmp
% determine the samples to copy from source to target
begsample = prevSample+1;
endsample = prevSample + min(newsamples, maxblocksmp);
% remember up to where the data was read
prevSample = endsample;
count = count + 1;
fprintf('processing segment %d from sample %d to %d\n', count, begsample, endsample);
% read data segment
dat = ft_read_data(cfg.source.datafile, 'dataformat', cfg.source.dataformat, 'begsample', begsample, 'endsample', endsample, 'chanindx', chanindx, 'checkboundary', false);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% from here onward it is specific to the downsampling
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[FM, dat] = online_filter_apply(FM, dat);
[DM, xd] = online_downsample_apply(DM, dat);
if count==1
% flush the file, write the header and subsequently write the data segment
ft_write_data(cfg.target.datafile, dat, 'header', targethdr, 'dataformat', cfg.target.dataformat, 'chanindx', chanindx, 'append', false);
else
% write the data segment
ft_write_data(cfg.target.datafile, dat, 'header', targethdr, 'dataformat', cfg.target.dataformat, 'chanindx', chanindx, 'append', true);
end
end % if enough new samples
end % while true