Skip to content

Commit 1151a27

Browse files
committed
Updated existing online writer plugins to support prediction relative to markers (PredictAt option), improved fault tolerance in the online plugin system.
1 parent 804481b commit 1151a27

16 files changed

+222
-120
lines changed

RELEASE NOTES.TXT

+8-9
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ Major New Features
105105
- set_joinepos: new function to join epoched signals across epochs (orders of magnitude faster than set_merge, and handles corner cases)
106106
- set_makepos: can now extract epochs relative to markers online/incrementally (complete rewrite)
107107
- onl_***: generally markers can now be streamed through the online system and are visible to filters and the feature extraction
108-
-
109108

110109
Minor New Features
111110
- combine_structs/flatten_structs: new functions to handle structs in name-value pair lists
@@ -118,13 +117,13 @@ Minor New Features
118117
- onl_newpredictor: new argument PredictAt: allows to define markers relative to which predictions should be generated
119118
(and epochs should be extracted)
120119
- onl_peek: now supports markers
121-
- onl_read_background: now supports callback functions that also return markers
122-
- onl_write_background: new options PredictAt, Verbose, and EmptyResultValue
120+
- onl_read_background: now supports callback functions that also return markers, plus better error handling
121+
- onl_write_background: new options PredictAt, Verbose, and EmptyResultValue, also better error handling
123122
- onl_simulate: aside from specifying the latency of onl_predict calls, now allows to use the pipeline with marker-locked prediction
124123
(via TargetMarkers argument which takes the same role as PredictAt in some other functions)
125124
- run_readlsl: now supports reading synchronized markers from marker streams
126-
- run_writelsl, run_writesnap: added PredictAt option
127125
- run_readdataset: now supports reading markers
126+
- run_write***: added PredictAt and Verbose options
128127
- utl_buffer: new utility function for buffering of subsequent signal structs
129128
- utl_fileinfo: now supports empty file paths
130129
- utl_find_filter: now supports returning the subscript path to the matching location in the data structure (for use with subsasgn)
@@ -152,7 +151,7 @@ Minor Fixes
152151
- utl_resolve_streams: fixed rare crash bug when multiple candidate streams needed to be disambiguated based on supported channels and types
153152
- vis_filtered: minor documentation fix
154153

155-
154+
156155
=== Changes since 1.1-beta ===
157156

158157
Major New Features
@@ -173,7 +172,7 @@ Minor Changes
173172
Serious Fixes
174173

175174
Minor Fixes
176-
- flt_ica, robust_sphere option: was calling an obsolete function
175+
- flt_ica, robust_sphere option: was calling an obsolete function
177176

178177
=== Changes since 1.01 ===
179178

@@ -282,10 +281,10 @@ Minor New Features
282281
- flt_iir: now supports free-form designs via the Yule-Walker method
283282
- flt_pipeline: now more tolerant when trying to load erroneous plugin functions
284283
- flt_ica: now supports rica, sphering and robust sphering as methods
285-
- plugins are now also searched in the respective sub-directory “in_development, and marked
284+
- plugins are now also searched in the respective sub-directory “in_development�?, and marked
286285
experimental by default
287286
- ParadigmFBCSP: now supports robust covariance estimation
288-
- ParadigmDataflowSimplified: now supports a “feature conditioning stage between feature
287+
- ParadigmDataflowSimplified: now supports a “feature conditioning�? stage between feature
289288
extraction and machine learning
290289
- ParadigmDALERP: now supports application to other data fields than .data, and allows for
291290
optional shrinkage-based covariance estimation
@@ -334,7 +333,7 @@ Major Changes
334333
- set_chanid is now case insensitive at channel matching (note that this should only break
335334
processing of pathological data sets which have multiple channels with the same name but
336335
different case, and otherwise improve things)
337-
- some methods are now marked as “experimental and not shown in the GUI by default (can be
336+
- some methods are now marked as “experimental�? and not shown in the GUI by default (can be
338337
toggled at startup or in the bcilab_config.m)
339338

340339
Minor Changes

code/online_analysis/onl_newpredictor.m

-2
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@
8080

8181
% optionally set up the filter graph to generate epochs relative to desired target markers
8282
if ~isempty(predict_at)
83-
if ~iscellstr(predict_at)
84-
error('PredictAt must be a cell array of strings.'); end
8583
% for each pipeline...
8684
for c=1:length(predictor.tracking.filter_graph)
8785
% substitute the node set_makepos('signal',x,...,'online_epoching',y,...)

code/online_analysis/onl_predict.m

+13-13
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,19 @@
99
% In:
1010
% Name : name of a predictor (under which is was previously created with onl_newpredictor)
1111
%
12-
% Format : the desired form of the prediction (see also ult_formatprediction), can be:
13-
% * 'raw': the raw prediction, as defined by ml_predict (default)
14-
% * 'expectation': the output is the expected value (i.e., posterior mean) of the
15-
% quantity to be predicted; can be multi-dimensional [1xD], but D
16-
% equals in most cases 1
17-
% * 'distribution': the output is the probability distribution (discrete or
18-
% continuous) of the quantity to be predicted usually, this is a
19-
% discrete distribution - one probability value for every possible
20-
% target outcome [1xV] it can also be the parameters of a
21-
% parametric distribution (e.g., mean, variance) - yielding one
22-
% value for each parameter [DxP]
23-
% * 'mode': the mode [1xD], or most likely output value (only supported for discrete
24-
% probability distributions)
12+
% Format : format of the output prediction (in the descriptions, N is the number of
13+
% predictions), can be one of:
14+
% * 'expectation': the output is the expected value (i.e., posterior mean) of the
15+
% quantity to be predicted; can be multi-dimensional [NxD]
16+
% * 'distribution': the output is the probability distribution (discrete or
17+
% continuous) of the quantity to be predicted usually, this is a
18+
% discrete distribution - one probability value for every possible
19+
% target outcome [NxV] it can also be the parameters of a
20+
% parametric distribution (e.g., mean, variance) - yielding one
21+
% value for each parameter [NxP]
22+
% * 'mode': the mode [Nx1], or most likely output value (only supported for discrete
23+
% probability distributions)
24+
% * 'raw': the raw prediction, as defined by ml_predict
2525
%
2626
% SuppressOutput : whether to suppress console output (default: true)
2727
%

code/online_analysis/onl_read_background.m

+17-12
Original file line numberDiff line numberDiff line change
@@ -53,21 +53,26 @@ function append_data(stream_name,stream_id,timer_handle,read_block)
5353
x = evalin('base',stream_name);
5454
if x.streamid ~= stream_id
5555
error('Stream changed.'); end
56-
% get a new block
57-
if nargin(read_block) == 1
58-
block = read_block(x);
59-
else
60-
block = read_block();
61-
end
62-
% append it to the stream
63-
if iscell(block)
64-
onl_append(stream_name,block{:});
65-
else
66-
onl_append(stream_name,block);
56+
try
57+
% get a new block
58+
if nargin(read_block) == 1
59+
block = read_block(x);
60+
else
61+
block = read_block();
62+
end
63+
% append it to the stream
64+
if iscell(block)
65+
onl_append(stream_name,block{:});
66+
else
67+
onl_append(stream_name,block);
68+
end
69+
catch e
70+
disp('Error in block-reading function:');
71+
hlp_handleerror(e);
6772
end
6873
catch e
6974
if ~strcmp(e.identifier,'MATLAB:UndefinedFunction')
70-
env_handleerror(e); end
75+
hlp_handleerror(e); end
7176
% stream has been changed (e.g., replaced/deleted) --> stop timer
7277
stop(timer_handle);
7378
delete(timer_handle);

code/online_analysis/onl_write_background.m

+16-8
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ function onl_write_background(varargin)
88
% computation of updated estimates, and their transfer to the data sink.
99
%
1010
% In:
11-
% ResultWriter : function that receives a BCI estimate and writes it to some external device.
11+
% ResultWriter : Function that receives one or more BCI estimates and writes them to some external
12+
% device. The format is according to OutputFormat.
1213
%
13-
% MatlabStream : real-time stream name to read from (in MATLAB workspace) (default: 'laststream')
14+
% MatlabStream : Real-time stream name to read from (in MATLAB workspace) (default: 'laststream')
1415
%
15-
% Model : predictive model to use, or variable name (in MATLAB workspace) (default: 'lastmodel')
16+
% Model : Predictive model to use, or variable name (in MATLAB workspace) (default: 'lastmodel')
1617
%
17-
% OutputFormat : output data format, see onl_predict
18-
% (default: 'distribution')
18+
% OutputFormat : Output data format, see onl_predict (default: 'distribution')
1919
%
20-
% UpdateFrequency : frequency at which the device should be queried, in Hz (default: 25)
20+
% UpdateFrequency : Frequency at which the device should be queried, in Hz (default: 25)
2121
%
2222
% StartDelay : Delay before real-time processing begins; grace period until user resources are
2323
% created (default: 1)
@@ -86,8 +86,16 @@ function write_data(predictor,stream,fmt,result_writer,pred_id,stream_id,timer_h
8686
% make a prediction
8787
y = onl_predict(predictor,fmt,~verbose,empty_result_value);
8888
% and write it out
89-
result_writer(y);
90-
catch %#ok<CTCH>
89+
try
90+
result_writer(y);
91+
catch e
92+
disp('Error in result-writing function:');
93+
hlp_handleerror(e);
94+
end
95+
catch e
96+
if ~strcmp(e.identifier,'MATLAB:UndefinedFunction')
97+
hlp_handleerror(e); end
98+
% stream or predictor have changed (e.g., replaced/deleted) --> stop timer
9199
stop(timer_handle);
92100
delete(timer_handle);
93101
end

code/online_plugins/BioSemi/run_readbiosemi.m

+3
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ function run_readbiosemi(varargin)
4747
% start background acquisition
4848
onl_read_background(opts.new_stream,@()read_block(conn,opts),opts.update_freq);
4949

50+
disp('Now reading...');
51+
52+
5053

5154
% background block reader function
5255
function block = read_block(conn,opts)

code/online_plugins/DataRiver/run_readdatariver.m

+2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ function run_readdatariver(varargin)
5656
% create background reading job
5757
onl_read_background(new_stream,@()read_block(datariver_stream),update_freq)
5858

59+
disp('Now reading...');
60+
5961

6062
% background data reading function
6163
function block = read_block(datariver_stream)

code/online_plugins/LSL/run_readlsl.m

+3-1
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,11 @@ function run_readlsl(varargin)
128128
timestamps = [];
129129

130130
% start background acquisition
131-
disp('Now reading...');
132131
onl_read_background(opts.new_stream,@()read_data(inlet,marker_inlet,opts.always_double),opts.update_freq);
133132

133+
disp('Now reading...');
134+
135+
134136
function result = read_data(data_inlet,marker_inlet,always_double)
135137
% get a new chunk of data
136138
[chunk,stamps] = data_inlet.pull_chunk();

code/online_plugins/LSL/run_writelsl.m

+6-3
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ function run_writelsl(varargin)
102102
info = lsl_streaminfo(lib,opts.out_stream,'MentalState',length(opts.channel_names),opts.update_freq,'cf_float32',uid);
103103
% ... including some meta-data
104104
desc = info.desc();
105+
channels = desc.append_child('channels');
105106
for c=1:length(opts.channel_names)
106-
newchn = desc.append_child('channel');
107+
newchn = channels.append_child('channel');
107108
newchn.append_child_value('name',opts.channel_names{c});
108109
newchn.append_child_value('type',opts.out_form);
109110
end
@@ -113,7 +114,7 @@ function run_writelsl(varargin)
113114

114115
% start background writer job
115116
onl_write_background( ...
116-
'ResultWriter',@(y)send_sample(outlet,y),...
117+
'ResultWriter',@(y)send_samples(outlet,y),...
117118
'MatlabStream',opts.in_stream, ...
118119
'Model',opts.pred_model, ...
119120
'OutputFormat',opts.out_form, ...
@@ -124,8 +125,10 @@ function run_writelsl(varargin)
124125
'StartDelay',0,...
125126
'EmptyResultValue',[]);
126127

128+
disp('Now writing...');
127129

128-
function send_sample(outlet,y)
130+
131+
function send_samples(outlet,y)
129132
if ~isempty(y)
130133
outlet.push_chunk(y'); end
131134

code/online_plugins/OSC/run_writeosc.m

+29-11
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function run_writeosc(varargin)
4747
declare_properties('name','OSC');
4848

4949
% define arguments
50-
arg_define(varargin, ...
50+
opts = arg_define(varargin, ...
5151
arg({'pred_model','Model'}, 'lastmodel', [], 'Predictive model. As obtained via bci_train or the Model Calibration dialog.','type','expression'), ...
5252
arg({'in_stream','SourceStream'}, 'laststream',[],'Input Matlab stream. This is the stream that shall be analyzed and processed.'), ...
5353
arg({'out_hostname','OutputHost'}, '127.0.0.1',[],'Destination TCP hostname. Can be a computer name, URL, or IP address.'), ...
@@ -56,28 +56,46 @@ function run_writeosc(varargin)
5656
arg({'out_form','OutputForm'},'distribution',{'expectation','distribution','mode'},'Output form. Can be the expected value (posterior mean) of the target variable, or the distribution over possible target values (probabilities for each outcome, or parametric distribution), or the mode (most likely value) of the target variable.'), ...
5757
arg({'msg_format','MessageFormatter'},'@(D) num2cell(single(D(:))))',[],'Message Formatting function. Converts a BCI output (usually scalar or row vector, in some complex cases a matrix) into a cell array of OSC-compatible data types (float32,int32, ...).','type','expression'), ...
5858
arg({'update_freq','UpdateFrequency'},10,[],'Update frequency. This is the rate at which the graphics are updated.'), ...
59-
arg({'pred_name','PredictorName'}, 'lastpredictor',[],'Name of new predictor. This is the workspace variable name under which a predictor will be created.'));
59+
arg({'predict_at','PredictAt'}, {},[],'Predict at markers. If nonempty, this is a cell array of online target markers relative to which predictions shall be made. If empty, predictions are always made on the most recently added sample.','type','expression'), ...
60+
arg({'pred_name','PredictorName'}, 'lastpredictor',[],'Name of new predictor. This is the workspace variable name under which a predictor will be created.'), ...
61+
arg({'verbose_output','Verbose'}, false,[],'Verbose output. Whether to display verbose outputs (e.g., connection failure).'));
6062

6163
% convert format strings to formatting functions
62-
if ischar(msg_format)
63-
msg_format = @(D) sprintf(msg_format,D); end
64+
if ischar(opts.msg_format)
65+
opts.msg_format = @(D) sprintf(opts.msg_format,D); end
6466

6567
% connect to remote address via OSC
6668
if ~exist('osc_new_address','file')
6769
try
6870
build_osc;
69-
catch
71+
catch e
7072
error('The OSC library has not been built for your platform yet; see dependencies/OSC* for more info.');
7173
end
7274
end
73-
conn = osc_new_address(out_hostname,out_port);
75+
conn = osc_new_address(opts.out_hostname,opts.out_port);
7476
deleter = onCleanup(@()osc_free_address(conn)); % if the last reference to this is dropped, the connection is closed (on MATLAB 2008a+)
7577

7678
% start background writer job
77-
onl_write_background(@(y)send_message(y,conn,out_path,msg_format,deleter),in_stream,pred_model,out_form,update_freq,0,pred_name);
79+
onl_write_background( ...
80+
'ResultWriter',@(y)send_message(y,conn,opts.out_path,opts.msg_format,deleter),...
81+
'MatlabStream',opts.in_stream, ...
82+
'Model',opts.pred_model, ...
83+
'OutputFormat',opts.out_form, ...
84+
'UpdateFrequency',opts.update_freq, ...
85+
'PredictorName',opts.pred_name, ...
86+
'PredictAt',opts.predict_at, ...
87+
'Verbose',opts.verbose_output, ...
88+
'StartDelay',0,...
89+
'EmptyResultValue',[]);
90+
91+
disp('Now writing...');
92+
7893

7994
% background message sending function
80-
function send_message(y,conn,opath,formatter,deleter)
81-
msg = struct('path',opath,'data',{formatter(y)});
82-
if osc_send(conn,msg) == 0
83-
error('OSC transmission failed.'); end
95+
function send_message(yy,conn,opath,formatter,deleter)
96+
for k=1:size(yy,1)
97+
y = yy(k,:);
98+
msg = struct('path',opath,'data',{formatter(y)});
99+
if osc_send(conn,msg) == 0
100+
error('OSC transmission failed.'); end
101+
end

code/online_plugins/SNAP/run_writesnap.m

+25-18
Original file line numberDiff line numberDiff line change
@@ -81,40 +81,47 @@ function run_writesnap(varargin)
8181
'StartDelay',0,...
8282
'EmptyResultValue',[]);
8383

84+
disp('Now writing...');
85+
86+
8487
% background message sending function
85-
function send_message(y,verbose_output,id,host,port,varname)
88+
function send_message(yy,verbose_output,id,host,port,varname)
8689
persistent conns;
8790
try
88-
if isempty(y)
89-
return; end
9091
% try to connect if necessary
9192
if ~isfield(conns,id)
9293
conns.(id) = connect(host,port); end
93-
try
94-
strm = conns.(id).strm;
95-
if isscalar(y)
96-
strm.writeBytes(char([sprintf('setup %s=%.5f',varname,y) 10]));
97-
elseif ~isempty(y)
98-
strm.writeBytes(char(['setup ' varname '=(' sprintf('%.5f,',y) ')' 10]));
99-
end
100-
strm.flush();
101-
catch e1
102-
if strcmp(e1.identifier, 'MATLAB:Java:GenericException')
103-
% failed to send: try to re-connect...
104-
conns.(id) = connect(host,port);
105-
else
106-
rethrow(e1);
94+
% for each prediction...
95+
for k=1:size(yy,1)
96+
y = yy(k,:);
97+
try
98+
% send it off
99+
strm = conns.(id).strm;
100+
if isscalar(y)
101+
strm.writeBytes(char([sprintf('setup %s=%.5f',varname,y) 10]));
102+
elseif ~isempty(y)
103+
strm.writeBytes(char(['setup ' varname '=(' sprintf('%.5f,',y) ')' 10]));
104+
end
105+
strm.flush();
106+
catch e1
107+
if strcmp(e1.identifier, 'MATLAB:Java:GenericException')
108+
% failed to send: try to re-connect...
109+
conns.(id) = connect(host,port);
110+
else
111+
rethrow(e1);
112+
end
107113
end
108114
end
109115
catch e2
110116
if strcmp(e2.identifier, 'MATLAB:Java:GenericException')
111117
if verbose_output
112118
fprintf('Could not connect to %s:%.0f\n',host,port); end
113119
else
114-
env_handleerror(e2);
120+
hlp_handleerror(e2);
115121
end
116122
end
117123

124+
118125
function newconn = connect(host,port)
119126
import java.io.*
120127
import java.net.*

code/online_plugins/Simulated/run_readdataset.m

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ function run_readdataset(varargin)
4747
% start a background reading job
4848
onl_read_background(new_stream,@(stream)read_block(in_dataset,stream,always_double), update_freq);
4949

50+
disp('Now reading...');
51+
5052

5153
% background block reader function
5254
function result = read_block(in_dataset,stream,always_double)

0 commit comments

Comments
 (0)