@@ -65,7 +65,8 @@ function vis_stream(varargin)
65
65
66
66
67
67
% make sure that dependencies are on the path and that LSL is loaded
68
- if ~exist(' arg_define' ,' file' )
68
+ evalin(' base' , ' global EEG;' );
69
+ if ~exist(' lsl_loadlib' ,' file' )
69
70
addpath(genpath(fileparts(mfilename(' fullpath' )))); end
70
71
try
71
72
lib = lsl_loadlib(env_translatepath(' dependencies:/liblsl-Matlab/bin' ));
@@ -75,6 +76,17 @@ function vis_stream(varargin)
75
76
76
77
% handle input arguments
77
78
streamnames = find_streams(lib );
79
+
80
+ if isempty(streamnames )
81
+ if isempty(varargin )
82
+ errordlg(' There is no stream visible on the network.' );
83
+ return
84
+ else
85
+ error(' There is no stream visible on the network.' );
86
+ end
87
+ end
88
+
89
+ % arg({'notchfilter','NotchFilter'},0,[0 Inf],'Notch filter. Enter 50 or 60 based on line noise frequency in your country.'), ...
78
90
opts = arg_define(varargin , ...
79
91
arg({' streamname' ,' StreamName' },streamnames{1 },streamnames ,' LSL stream that should be displayed. The name of the stream that you would like to display.' ), ...
80
92
arg({' bufferrange' ,' BufferRange' },10 ,[0 Inf ],' Maximum time range to buffer. Imposes an upper limit on what can be displayed.' ), ...
@@ -88,6 +100,7 @@ function vis_stream(varargin)
88
100
arg({' standardize' ,' Standardize' },false ,[],' Standardize data.' ), ...
89
101
arg({' rms' ,' RMS' },true ,[],' Show RMS for each channel.' ), ...
90
102
arg({' zeromean' ,' ZeroMean' },true ,[],' Zero-mean data.' ), ...
103
+ arg({' recordbut' ,' RecordButton' },true ,[],' Show Record button.' ), ...
91
104
arg_nogui({' parent_fig' ,' ParentFigure' },[],[],' Parent figure handle.' ), ...
92
105
arg_nogui({' parent_ax' ,' ParentAxes' },[],[],' Parent axis handle.' ), ...
93
106
arg_nogui({' pageoffset' ,' PageOffset' },0 ,uint32([0 100 ]),' Channel page offset. Allows to flip forward or backward pagewise through the displayed channels.' ), ...
@@ -96,16 +109,82 @@ function vis_stream(varargin)
96
109
if ~isempty(varargin )
97
110
% create stream inlet, figure and stream buffer
98
111
inlet = create_inlet(lib ,opts );
99
- stream = create_streambuffer(opts ,inlet .info());
112
+ stream = create_streambuffer(opts ,inlet .info());
113
+
100
114
[fig ,axrms ,ax ,lines ] = create_figure(opts ,@on_key ,@on_close );
115
+ opts.scalevals = [10 20 50 100 200 500 1000 ];
116
+ opts.scalepos = 4 ;
117
+
118
+ % scale
119
+ hh = 0.94 ;
120
+ uicontrol(' unit' , ' normalized' , ' position' , [0.04 hh - 0.01 0.08 0.05 ], ' style' , ' text' , ' string' , ' scale:' );
121
+ uicontrol(' unit' , ' normalized' , ' position' , [0.17 hh - 0.01 0.08 0.05 ], ' style' , ' text' , ' string' , ' uV' );
122
+ opts.scaleui = uicontrol(' unit' , ' normalized' , ' position' , [0.11 hh 0.08 0.05 ], ' style' , ' edit' , ' string' , num2str(opts .datascale ));
123
+
124
+ % record button
125
+ if opts .recordbut
126
+ cb_record = [ ' if isequal(get(gcbo, '' string'' ), '' Record'' ),' ...
127
+ ' set(gcbo, '' string'' , '' Stop'' );' ...
128
+ ' warndlg([ '' Your RAM should be able to hold the entire data.'' 10 '' When you press stop, you will be prompted to save the data.'' ]);' ...
129
+ ' EEG = [];' ...
130
+ ' else,' ...
131
+ ' set(gcbo, '' string'' , '' Record'' );' ...
132
+ ' EEG.data = [ EEG.data{:} ];' ...
133
+ ' EEG.trials = 1;' ...
134
+ ' EEG.pnts = size(EEG.data,2);' ...
135
+ ' [filenametmp, filepathtmp] = uiputfile('' *.set'' , '' Save dataset with .set extension'' );' ...
136
+ ' if isequal(filenametmp, 0), return; end;' ...
137
+ ' try,' ...
138
+ ' if ~isequal(lower(filenametmp(end-3:end)), '' .set'' ),' ...
139
+ ' filenametmp = [ filenametmp '' .set'' ];' ...
140
+ ' end;' ...
141
+ ' save('' -mat'' , fullfile(filepathtmp, filenametmp), '' EEG'' );' ...
142
+ ' disp('' Dataset saved'' );' ...
143
+ ' catch,' ...
144
+ ' errordlg(['' Cannot save data file.'' 0 '' EEG data is still in the EEG variable'' 0 '' in the global workspace.'' ]);' ...
145
+ ' end;' ...
146
+ ' clear filenametmp, filepathtmp;' ...
147
+ ' end;' ];
148
+ opts.recordui = uicontrol(' unit' , ' normalized' , ' position' , [0.87 0.05 0.10 0.10 ], ' style' , ' pushbutton' , ' string' , ' Record' , ' callback' , cb_record , ' userdata' , 0 );
149
+ end
150
+
101
151
% optionally design a frequency filter
152
+ valFilter = 1 ;
153
+ strFilter = { ' No filter' ' BP 2-45Hz' ' BP 5-45Hz' ' BP 15-45Hz' ' BP 7-13Hz' };
154
+ allBs = { [] };
155
+ allBs{end + 1 } = design_bandpass([1 2 45 47 ],stream .srate ,20 ,true );
156
+ allBs{end + 1 } = design_bandpass([4 5 45 47 ],stream .srate ,20 ,true );
157
+ allBs{end + 1 } = design_bandpass([14 15 45 47 ],stream .srate ,20 ,true );
158
+ allBs{end + 1 } = design_bandpass([ 6 7 13 14 ],stream .srate ,20 ,true );
102
159
if length(opts .freqfilter ) == 4
103
- B = design_bandpass(opts .freqfilter ,stream .srate ,20 ,true );
104
- elseif isscalar(opts .freqfilter )
105
- B = ones(opts .freqfilter ,1 )/max(1 ,opts .freqfilter );
160
+ allBs{end + 1 } = design_bandpass(opts .freqfilter ,stream .srate ,20 ,true );
161
+ strFilter{end + 1 } = sprintf(' BP %1.0f -%1.0f Hz' , opts .freqfilter(2 ), opts .freqfilter(3 ));
162
+ valFilter = 6 ;
163
+ elseif isscalar(opts .freqfilter )
164
+ if opts .freqfilter ~= 0
165
+ allBs{end + 1 } = ones(opts .freqfilter ,1 )/max(1 ,opts .freqfilter );
166
+ strFilter{end + 1 } = ' Moving av.' ;
167
+ valFilter = 6 ;
168
+ end
106
169
else
107
170
error(' The FIR filter must be given as 4 frequencies in Hz [raise-start,raise-stop,fall-start,fall-stop] or moving-average length in samples.' );
108
171
end
172
+ opts.filterui = uicontrol(' unit' , ' normalized' , ' position' , [0.25 hh 0.20 0.05 ], ' style' , ' popupmenu' , ' string' , strFilter , ' value' , valFilter );
173
+
174
+ % other options
175
+ opts.rerefui = uicontrol(' unit' , ' normalized' , ' position' , [0.47 hh 0.15 0.05 ], ' style' , ' checkbox' , ' string' , ' Ave Ref' , ' value' , opts .reref );
176
+ opts.normui = uicontrol(' unit' , ' normalized' , ' position' , [0.60 hh 0.12 0.05 ], ' style' , ' checkbox' , ' string' , ' Norm.' , ' value' , opts .standardize );
177
+ opts.zeroui = uicontrol(' unit' , ' normalized' , ' position' , [0.73 hh 0.22 0.05 ], ' style' , ' checkbox' , ' string' , ' Zero mean' , ' value' , opts .zeromean );
178
+
179
+ % filtering UI
180
+ % B50 = design_bandpass(opts.freqfilter,stream.srate,20,true);
181
+ % valGui
182
+ % if ~isempty(opts.notch)
183
+ % if opts.notch == 50, valGui = 2; end
184
+ % if opts.notch == 60, valGui = 3; end
185
+ % end
186
+ % opts.notchfilterui = uicontrol('unit', 'normalized', 'position', [0.25 0.945 0.18 0.05], 'style', 'popupmenu', 'string', { 'No notch' 'Notch 50Hz' 'Notch 60Hz' }, 'value', valGui, 'userdata', );
187
+
109
188
% start a timer that reads from LSL and updates the display
110
189
th = timer(' TimerFcn' ,@on_timer ,' Period' ,1.0 / opts .refreshrate ,' ExecutionMode' ,' fixedRate' );
111
190
start(th );
@@ -116,17 +195,25 @@ function vis_stream(varargin)
116
195
117
196
% update display with new data
118
197
function on_timer(varargin )
198
+ global EEG ;
119
199
try
200
+
120
201
% pull a new chunk from LSL
121
202
[chunk ,timestamps ] = inlet .pull_chunk();
122
203
if isempty(chunk )
123
204
return ; end
124
205
125
206
% optionally filter the chunk
126
207
chunk(~isfinite(chunk(: ))) = 0 ;
208
+ oriChunk = chunk ;
209
+ B = allBs{get(opts .filterui , ' value' )};
127
210
if ~isempty(B )
128
211
[chunk ,stream .state ] = filter(B ,1 ,chunk ,stream .state ,2 ); end
129
212
213
+ % get scale
214
+ tmpscale = str2double(get(opts .scaleui , ' string' ));
215
+ if length(tmpscale ) == 1 , opts.datascale = tmpscale ; end ;
216
+
130
217
% append it to the stream buffer
131
218
[stream .nsamples ,stream .buffer(: ,1 + mod(stream .nsamples : stream .nsamples + size(chunk ,2 )-1 ,size(stream .buffer ,2 )))] = deal(stream .nsamples + size(chunk ,2 ),chunk );
132
219
@@ -137,13 +224,70 @@ function on_timer(varargin)
137
224
[stream .nbchan ,stream .pnts ,stream .trials ] = size(stream .data );
138
225
stream.xmax = max(timestamps ) - lsl_local_clock(lib );
139
226
stream.xmin = stream .xmax - (samples_to_get - 1 )/stream .srate ;
227
+
228
+ % save as EEG dataset
229
+ if opts .recordbut
230
+ % get recording status
231
+ strRecord = get(opts .recordui , ' string' );
232
+ currentlyRecording = false ;
233
+ if isequal(strRecord , ' Stop' )
234
+ currentlyRecording = true ;
235
+ end
140
236
237
+ if currentlyRecording
238
+ if isempty(EEG )
239
+ EEG.nbchan = stream .nbchan ;
240
+ EEG.xmin = 0 ;
241
+ EEG.xmax = 0 ;
242
+ EEG.srate = stream .srate ;
243
+ EEG.data = {};
244
+ EEG.setname = ' ' ;
245
+ EEG.filename = ' ' ;
246
+ EEG.filepath = ' ' ;
247
+ EEG.subject = ' ' ;
248
+ EEG.group = ' ' ;
249
+ EEG.condition = ' ' ;
250
+ EEG.session = [];
251
+ EEG.comments = ' ' ;
252
+ EEG.times = [];
253
+ EEG.icaact = [];
254
+ EEG.icawinv = [];
255
+ EEG.icasphere = [];
256
+ EEG.icaweights = [];
257
+ EEG.icachansind = [];
258
+ EEG.chanlocs = [];
259
+ EEG.urchanlocs = [];
260
+ EEG.chaninfo = [];
261
+ EEG.ref = [];
262
+ EEG.event = [];
263
+ EEG.urevent = [];
264
+ EEG.eventdescription = {};
265
+ EEG.epoch = [];
266
+ EEG.epochdescription = {};
267
+ EEG.reject = [];
268
+ EEG.stats = [];
269
+ EEG.specdata = [];
270
+ EEG.specicaact = [];
271
+ EEG.splinefile = ' ' ;
272
+ EEG.icasplinefile = ' ' ;
273
+ EEG.dipfit = [];
274
+ EEG.history = ' ' ;
275
+ EEG.saved = ' no' ;
276
+ EEG.etc = [];
277
+ end
278
+ if ~iscell(EEG .data )
279
+ EEG.data = {};
280
+ end
281
+ EEG.data{end + 1 } = oriChunk ;
282
+ end
283
+ end
284
+
141
285
% optionally post-process the data
142
- if opts .reref
286
+ if get( opts .rerefui , ' value ' )
143
287
stream.data = bsxfun(@minus ,stream .data ,mean(stream .data )); end
144
- if opts .standardize
288
+ if get( opts .normui , ' value ' )
145
289
stream.data = bsxfun(@times ,stream .data ,1 ./ std(stream .data ,[],2 )); end
146
- if opts .zeromean
290
+ if get( opts .zeroui , ' value ' )
147
291
stream.data = bsxfun(@minus , stream .data , mean(stream .data ,2 )); end
148
292
149
293
% arrange for plotting
@@ -154,7 +298,6 @@ function on_timer(varargin)
154
298
% update graphics
155
299
if isempty(lines )
156
300
lines = plot(ax ,plottime ,plotdata );
157
- title(ax ,opts .streamname );
158
301
xlabel(ax ,' Time (sec)' ,' FontSize' ,12 );
159
302
ylabel(ax ,' Activation' ,' FontSize' ,12 );
160
303
else
@@ -201,9 +344,9 @@ function on_timer(varargin)
201
344
function on_key(key )
202
345
switch lower(key )
203
346
case ' uparrow' % decrease datascale
204
- opts.datascale = opts .datascale * 0.9 ;
347
+ opts.datascale = round( opts .datascale * 0.9 ); set( opts . scaleui , ' string ' , num2str( opts . datascale )) ;
205
348
case ' downarrow' % increase datascale
206
- opts.datascale = opts .datascale * 1.1 ;
349
+ opts.datascale = round( opts .datascale * 1.1 ); set( opts . scaleui , ' string ' , num2str( opts . datascale )) ;
207
350
case ' rightarrow' % increase timerange
208
351
opts.timerange = opts .timerange * 1.1 ;
209
352
case ' leftarrow' % decrease timerange
@@ -231,8 +374,6 @@ function on_close(varargin)
231
374
function names = find_streams(lib )
232
375
streams = lsl_resolve_all(lib ,0.3 );
233
376
names = unique(cellfun(@(s )s .name(),streams ,' UniformOutput' ,false)) ;
234
- if isempty(names )
235
- error(' There is no stream visible on the network.' ); end
236
377
end
237
378
238
379
% create a new figure and axes
@@ -241,17 +382,15 @@ function on_close(varargin)
241
382
if isempty(opts .parent_ax )
242
383
if isempty(opts .parent_fig )
243
384
fig = figure(' Name' ,[' LSL:Stream'' ' opts .streamname ' '' ' ], ' CloseRequestFcn' ,on_close , ...
244
- ' KeyPressFcn' ,@(varargin )on_key(varargin{2 }.Key));
385
+ ' KeyPressFcn' ,@(varargin )on_key(varargin{2 }.Key), ' menubar ' , ' none ' , ' numbertitle ' , ' off ' );
245
386
% fig = figure('Name',['LSL:Stream''' opts.streamname '''']);
246
387
else
247
388
fig = opts .parent_fig ;
248
389
end
249
390
if opts .rms
250
391
axrms = axes(' Parent' ,fig , ' YAxisLocation' , ' right' , ' YDir' ,' normal' , ' position' , [0.1300 0.1100 0.7050 0.8150 ]);
251
- ax = axes(' Parent' ,fig , ' YDir' ,' normal' , ' position' , [0.1300 0.1100 0.7050 0.8150 ]);
252
- else
253
- ax = axes(' Parent' ,fig , ' YDir' ,' normal' );
254
392
end
393
+ ax = axes(' Parent' ,fig , ' YDir' ,' normal' , ' position' , [0.1300 0.1100 0.7050 0.8150 ]);
255
394
else
256
395
ax = opts .parent_ax ;
257
396
end
0 commit comments