Skip to content

Commit c66ce71

Browse files
committed
adding new GUI elements and record button
1 parent 3663001 commit c66ce71

File tree

4 files changed

+218
-18
lines changed

4 files changed

+218
-18
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,8 @@ Press package. Test using the executable in "for_testing" folder. For example te
6060

6161
# Caveats
6262
Currently, marker streams are not being displayed, and streams with irregular sampling rate will not have the correct time axis.
63+
64+
# Versions
65+
66+
v1.0 - Initial EEGLAB plugin release
67+

eegplugin_lsl_app_matlabviewer.m

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
% eegplugin_lsl_app_matlabviewer() - EEGLAB plugin for Matlab LSL viewer.
2+
%
3+
% Usage:
4+
% >> eegplugin_lsl_app_matlabviewer(fig, trystrs, catchstrs);
5+
%
6+
% Inputs:
7+
% fig - [integer] EEGLAB figure
8+
% trystrs - [struct] "try" strings for menu callbacks.
9+
% catchstrs - [struct] "catch" strings for menu callbacks.
10+
%
11+
% Authors: Arnaud Delorme for the plugin and Christian Kothe for the viewer
12+
%
13+
% See also: pop_loadbv()
14+
15+
% Copyright (C) 2004 Arnaud Delorme
16+
%
17+
% This program is free software; you can redistribute it and/or modify
18+
% it under the terms of the GNU General Public License as published by
19+
% the Free Software Foundation; either version 2 of the License, or
20+
% (at your option) any later version.
21+
%
22+
% This program is distributed in the hope that it will be useful,
23+
% but WITHOUT ANY WARRANTY; without even the implied warranty of
24+
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25+
% GNU General Public License for more details.
26+
%
27+
% You should have received a copy of the GNU General Public License
28+
% along with this program; if not, write to the Free Software
29+
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30+
31+
function vers = eegplugin_lsl_app_matlabviewer(fig, trystrs, catchstrs)
32+
33+
vers = 'lsl_app_matlabviewer1.0';
34+
if nargin < 3
35+
error('eegplugin_bva_io requires 3 arguments');
36+
end
37+
38+
% add folder to path
39+
% ------------------
40+
if ~exist('eegplugin_lsl_app_matlabviewer')
41+
p = which('eegplugin_lsl_app_matlabviewer.m');
42+
p = p(1:findstr(p,'eegplugin_lsl_app_matlabviewer.m')-1);
43+
addpath( p );
44+
end
45+
46+
% find import data menu
47+
% ---------------------
48+
menui = findobj(fig, 'label', 'File');
49+
50+
% menu callbacks
51+
% --------------
52+
comcnt1 = 'vis_stream;';
53+
54+
% create menus
55+
% ------------
56+
uimenu( menui, 'label', 'Matlab LSL viewer', 'callback', comcnt1, 'separator', 'on', 'position', 5 );

vis_stream.m

Lines changed: 156 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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
7071
try
7172
lib = lsl_loadlib(env_translatepath('dependencies:/liblsl-Matlab/bin'));
@@ -75,6 +76,17 @@ function vis_stream(varargin)
7576

7677
% handle input arguments
7778
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.'), ...
7890
opts = 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)
96109
if ~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.0fHz', 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)
231374
function 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
236377
end
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

vis_stream_comp.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function vis_stream_comp(varargin)
3232
opt = struct([]);
3333
end
3434

35-
numFields = { 'bufferrange' 'timerange' 'channelrange' 'samplingrate' 'refreshrate' 'freqfilter' 'position' 'reref' 'standardize' 'zeromean' };
35+
numFields = { 'bufferrange' 'timerange' 'channelrange' 'samplingrate' 'refreshrate' 'freqfilter' 'position' 'reref' 'standardize' 'zeromean' 'recordbut' };
3636

3737
for iField = 1:length(numFields)
3838
if isfield(opt, numFields{iField}) && ischar(opt.(numFields{iField}))

0 commit comments

Comments
 (0)