@@ -65,7 +65,8 @@ function vis_stream(varargin)
6565
6666
6767% 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' )
6970 addpath(genpath(fileparts(mfilename(' fullpath' )))); end
7071try
7172 lib = lsl_loadlib(env_translatepath(' dependencies:/liblsl-Matlab/bin' ));
@@ -75,6 +76,17 @@ function vis_stream(varargin)
7576
7677% handle input arguments
7778streamnames = 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.'), ...
7890opts = arg_define(varargin , ...
7991 arg({' streamname' ,' StreamName' },streamnames{1 },streamnames ,' LSL stream that should be displayed. The name of the stream that you would like to display.' ), ...
8092 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)
88100 arg({' standardize' ,' Standardize' },false ,[],' Standardize data.' ), ...
89101 arg({' rms' ,' RMS' },true ,[],' Show RMS for each channel.' ), ...
90102 arg({' zeromean' ,' ZeroMean' },true ,[],' Zero-mean data.' ), ...
103+ arg({' recordbut' ,' RecordButton' },true ,[],' Show Record button.' ), ...
91104 arg_nogui({' parent_fig' ,' ParentFigure' },[],[],' Parent figure handle.' ), ...
92105 arg_nogui({' parent_ax' ,' ParentAxes' },[],[],' Parent axis handle.' ), ...
93106 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)
96109if ~isempty(varargin )
97110 % create stream inlet, figure and stream buffer
98111 inlet = create_inlet(lib ,opts );
99- stream = create_streambuffer(opts ,inlet .info());
112+ stream = create_streambuffer(opts ,inlet .info());
113+
100114 [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+
101151 % 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 );
102159 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
106169 else
107170 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.' );
108171 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+
109188 % start a timer that reads from LSL and updates the display
110189 th = timer(' TimerFcn' ,@on_timer ,' Period' ,1.0 / opts .refreshrate ,' ExecutionMode' ,' fixedRate' );
111190 start(th );
@@ -116,17 +195,25 @@ function vis_stream(varargin)
116195
117196 % update display with new data
118197 function on_timer(varargin )
198+ global EEG ;
119199 try
200+
120201 % pull a new chunk from LSL
121202 [chunk ,timestamps ] = inlet .pull_chunk();
122203 if isempty(chunk )
123204 return ; end
124205
125206 % optionally filter the chunk
126207 chunk(~isfinite(chunk(: ))) = 0 ;
208+ oriChunk = chunk ;
209+ B = allBs{get(opts .filterui , ' value' )};
127210 if ~isempty(B )
128211 [chunk ,stream .state ] = filter(B ,1 ,chunk ,stream .state ,2 ); end
129212
213+ % get scale
214+ tmpscale = str2double(get(opts .scaleui , ' string' ));
215+ if length(tmpscale ) == 1 , opts.datascale = tmpscale ; end ;
216+
130217 % append it to the stream buffer
131218 [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 );
132219
@@ -137,13 +224,70 @@ function on_timer(varargin)
137224 [stream .nbchan ,stream .pnts ,stream .trials ] = size(stream .data );
138225 stream.xmax = max(timestamps ) - lsl_local_clock(lib );
139226 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
140236
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+
141285 % optionally post-process the data
142- if opts .reref
286+ if get( opts .rerefui , ' value ' )
143287 stream.data = bsxfun(@minus ,stream .data ,mean(stream .data )); end
144- if opts .standardize
288+ if get( opts .normui , ' value ' )
145289 stream.data = bsxfun(@times ,stream .data ,1 ./ std(stream .data ,[],2 )); end
146- if opts .zeromean
290+ if get( opts .zeroui , ' value ' )
147291 stream.data = bsxfun(@minus , stream .data , mean(stream .data ,2 )); end
148292
149293 % arrange for plotting
@@ -154,7 +298,6 @@ function on_timer(varargin)
154298 % update graphics
155299 if isempty(lines )
156300 lines = plot(ax ,plottime ,plotdata );
157- title(ax ,opts .streamname );
158301 xlabel(ax ,' Time (sec)' ,' FontSize' ,12 );
159302 ylabel(ax ,' Activation' ,' FontSize' ,12 );
160303 else
@@ -201,9 +344,9 @@ function on_timer(varargin)
201344 function on_key(key )
202345 switch lower(key )
203346 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 )) ;
205348 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 )) ;
207350 case ' rightarrow' % increase timerange
208351 opts.timerange = opts .timerange * 1.1 ;
209352 case ' leftarrow' % decrease timerange
@@ -231,8 +374,6 @@ function on_close(varargin)
231374function names = find_streams(lib )
232375 streams = lsl_resolve_all(lib ,0.3 );
233376 names = unique(cellfun(@(s )s .name(),streams ,' UniformOutput' ,false)) ;
234- if isempty(names )
235- error(' There is no stream visible on the network.' ); end
236377end
237378
238379% create a new figure and axes
@@ -241,17 +382,15 @@ function on_close(varargin)
241382 if isempty(opts .parent_ax )
242383 if isempty(opts .parent_fig )
243384 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 ' );
245386 % fig = figure('Name',['LSL:Stream''' opts.streamname '''']);
246387 else
247388 fig = opts .parent_fig ;
248389 end
249390 if opts .rms
250391 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' );
254392 end
393+ ax = axes(' Parent' ,fig , ' YDir' ,' normal' , ' position' , [0.1300 0.1100 0.7050 0.8150 ]);
255394 else
256395 ax = opts .parent_ax ;
257396 end
0 commit comments