Skip to content

Commit

Permalink
Fix LSL stream plugin, 1st commit, NEEDS TESTING
Browse files Browse the repository at this point in the history
Updates BCILAB/dependencies/liblsl-Matlab to ?latest? version taken from labstreaminglayer's github, commit b9ff52584e0a6c9ef13de050b995a4816f82b0b9, Apps/MATLABViewer/liblsl-Matlab folder.
Also comments out parameters passed to the data_inlet.time_correction function to blindly force compliance with new function definition. This likely induces errors.
  • Loading branch information
haniawni committed Jul 15, 2016
1 parent e287299 commit 44b0fc0
Show file tree
Hide file tree
Showing 1,087 changed files with 110 additions and 201 deletions.
2 changes: 1 addition & 1 deletion code/online_plugins/LSL/run_readlsl.m
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ function run_readlsl(varargin)
function result = read_data(data_inlet,marker_inlet,always_double)
% get a new chunk of data
[chunk,stamps] = data_inlet.pull_chunk();
data_clock = data_inlet.time_correction([],opts.clock_alignment,opts.clock_est_window);
data_clock = data_inlet.time_correction(); % formerly called passing ([], opts.clock_alignment, opts.clock_est_window);
stamps = stamps + data_clock;
if opts.jitter_correction
stamps = update_regression(stamps); end
Expand Down
Binary file modified dependencies/liblsl-Matlab/bin/liblsl32.dll
Binary file not shown.
Binary file modified dependencies/liblsl-Matlab/bin/liblsl32.dylib
Binary file not shown.
Binary file modified dependencies/liblsl-Matlab/bin/liblsl64.dll
Binary file not shown.
Binary file modified dependencies/liblsl-Matlab/bin/liblsl64.dylib
Binary file not shown.
Binary file modified dependencies/liblsl-Matlab/bin/liblsl64.so
Binary file not shown.
42 changes: 42 additions & 0 deletions dependencies/liblsl-Matlab/examples/HandleMetaData.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
%% instantiate the library
lib = lsl_loadlib();

% create a new StreamInfo and declare some meta-data (in accordance with XDF format)
info = lsl_streaminfo(lib,'MetaTester','EEG',8,100,'cf_float32','myuid56872');
chns = info.desc().append_child('channels');
for label = {'C3','C4','Cz','FPz','POz','CPz','O1','O2'}
ch = chns.append_child('channel');
ch.append_child_value('label',label{1});
ch.append_child_value('unit','microvolts');
ch.append_child_value('type','EEG');
end
info.desc().append_child_value('manufacturer','SCCN');
cap = info.desc().append_child('cap');
cap.append_child_value('name','EasyCap');
cap.append_child_value('size','54');
cap.append_child_value('labelscheme','10-20');

% create outlet for the stream
outlet = lsl_outlet(info);


% === the following could run on another computer ===

% resolve the stream and open an inlet
lib = lsl_loadlib();
result = {};
while isempty(result)
result = lsl_resolve_byprop(lib,'type','EEG'); end
inlet = lsl_inlet(result{1});
% get the full stream info (including custom meta-data) and dissect it
inf = inlet.info();
fprintf('The stream''s XML meta-data is: \n');
fprintf([inf.as_xml() '\n']);
fprintf(['The manufacturer is: ' inf.desc().child_value('manufacturer') '\n']);
fprintf(['The cap circumference is: ' inf.desc().child('cap').child_value('size') '\n']);
fprintf('The channel labels are as follows:\n');
ch = inf.desc().child('channels').child('channel');
for k = 1:inf.channel_count()
fprintf([' ' ch.child_value('label') '\n']);
ch = ch.next_sibling();
end
182 changes: 25 additions & 157 deletions dependencies/liblsl-Matlab/lsl_inlet.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
classdef lsl_inlet < handle
% A stream inlet.
% Inlets are used to receive streaming data (and meta-data) from the lab network.
% A stream inlet.
% Inlets are used to receive streaming data (and meta-data) from the lab network.
%
% Note:
% Most functions of the inlet can throw an error of type 'lsl:lost_error' if the stream
Expand All @@ -12,13 +12,6 @@
InletHandle = 0; % this is a handle to an lsl_inlet object within the library.
ChannelCount = 0; % copy of the inlet's channel count
IsString = 0; % whether this is a string-formatted inlet

CorrectionBuffer = []; % buffer of time-correction values
TimestampBuffer = []; % buffer of time-stamps associated with the correction values

LinearRegressionCoeff = []; % cached coefficients for standard linear regression
TrimmedRegressionCoeff = []; % cached coefficients for trimmed linear regression
RobustRegressionCoeff = []; % cached coefficients for robust linear regression
end

methods
Expand All @@ -31,22 +24,22 @@
% Streaminfo : A resolved stream info object (as coming from one of the lsl_resolve_* functions).
%
% MaxBuffered : Optionally the maximum amount of data to buffer (in seconds if there is a nominal
% sampling rate, otherwise x100 in samples). Recording applications want to use a fairly
% large buffer size here, while real-time applications would only buffer as much as
% they need to perform their next calculation. (default: 360)
% sampling rate, otherwise x100 in samples). Recording applications want to use a fairly
% large buffer size here, while real-time applications would only buffer as much as
% they need to perform their next calculation. (default: 360)
%
% ChunkSize : Optionally the maximum size, in samples, at which chunks are transmitted
% (0 corresponds to the chunk sizes used by the sender).
% Recording applications can use a generous size here (leaving it to the network how
% to pack things), while real-time applications may want a finer (perhaps 1-sample) granularity.
% (0 corresponds to the chunk sizes used by the sender).
% Recording applications can use a generous size here (leaving it to the network how
% to pack things), while real-time applications may want a finer (perhaps 1-sample) granularity.
% (default 0)
%
% Recover : Try to silently recover lost streams that are recoverable (=those that that have a source_id set).
% In all other cases (recover is false or the stream is not recoverable) functions may throw a
% lost_error if the stream's source is lost (e.g., due to an app or computer crash).
% In all other cases (recover is false or the stream is not recoverable) functions may throw a
% lost_error if the stream's source is lost (e.g., due to an app or computer crash).
% (default: 1)

if ~exist('maxbuffered','var') || isempty(maxbuffered) maxbuffered = 360; end %#ok<*SEPEX>
if ~exist('maxbuffered','var') || isempty(maxbuffered) maxbuffered = 360; end
if ~exist('chunksize','var') || isempty(chunksize) chunksize = 0; end
if ~exist('recover','var') || isempty(recover) recover = 1; end
self.LibHandle = info.LibHandle;
Expand Down Expand Up @@ -79,15 +72,15 @@ function delete(self)
if ~exist('timeout','var') || isempty(timeout) timeout = 60; end
result = lsl_streaminfo(self.LibHandle,lsl_get_fullinfo(self.LibHandle, self.InletHandle, timeout));
end

function open_stream(self, timeout)
% Subscribe to the data stream from this moment onwards.
% open_stream(Timeout)
%
% All samples pushed in at the other end from this moment onwards will be queued and
% eventually be delivered in response to pull_sample() or pull_chunk() calls.
% Pulling a sample without some preceding open_stream is permitted (the stream will then
% eventually be delivered in response to pull_sample() or pull_chunk() calls.
% Pulling a sample without some preceding open_stream is permitted (the stream will then
% be opened implicitly).
%
% In:
Expand All @@ -113,120 +106,38 @@ function close_stream(self)
end


function result = time_correction(self,timeout,postproc,winlen)
function result = time_correction(self,timeout)
% Retrieve an estimated time correction offset for the given stream.
%
% The first call to this function takes several miliseconds until a reliable first estimate is obtained.
% Subsequent calls are very fast (and rely on periodic background updates).
% The raw estimates should have a standard deviation under 1 ms (empirically within +/-0.5 ms).
% Subsequent calls are instantaneous (and rely on periodic background updates).
% The precision of these estimates should be below 1 ms (empirically within +/-0.2 ms).
%
% In:
% Timeout : Timeout to acquire the first time-correction estimate (default: 60).
%
% PostProcessing : type of post-processing to apply; can be one of the following:
% * 'raw' : return the raw time-correction estimates; these tend to
% have an average jitter of up to +/- 0.5ms (default)
% * 'median' : return the median of measurements in a given time
% window (most robust; note that this becomes inaccurate
% for time windows > 1 minute if there is drift)
% * 'linear' : return a linear fit in the given time window (fast and
% accurate but sensitive to rare outliers, e.g., under
% network load spikes)
% * 'trimmed' : a trimmed linear estimator; the two observations with
% the largest positive (resp. negative) deviation will
% be removed
% * 'robust' : return a robust linear fit in the given time window
% (slowest but best tradeoff between accuracy and
% robustness)
% * 'zero' : return zero (i.e., dismiss time-correction)
% note: if you switch the post-processing you need to wait for up to
% 5 seconds until the estimator has updated
%
% PostProcessingWindow : time window for post-processing, in seconds (default: 60)
%
% Out:
% Offset : The time correction estimate. If the first estimate cannot be obtained
% within the alloted time, the result is NaN.
% Offset : The time correction estimate. If the first estimate cannot within the alloted time,
% the result is NaN.

if ~exist('timeout','var') || isempty(timeout) timeout = 60; end
if ~exist('postproc','var') || isempty(postproc) postproc = 'raw'; end
if ~exist('winlen','var') || isempty(winlen) winlen = 60; end
result = lsl_time_correction(self.LibHandle, self.InletHandle,timeout);
% do post-processing if requested (and don't attempt to post-process NaN values)
if ~isnan(result) && ~strcmp(postproc,'raw')
t0 = lsl_local_clock(self.LibHandle);
% insert new measurement into buffer (but skip identical measurements to save
% space/time since the time-correction values in LSL will only refresh every few
% seconds)
if isempty(self.CorrectionBuffer) || result ~= self.CorrectionBuffer(end)
self.CorrectionBuffer(end+1) = result;
self.TimestampBuffer(end+1) = t0;
% for each insert, check if we can remove any now-outdated values
remove_mask = self.TimestampBuffer < t0-winlen;
if any(remove_mask)
self.CorrectionBuffer(remove_mask) = [];
self.TimestampBuffer(remove_mask) = [];
end
% update regression model
switch postproc
case 'linear'
if length(self.CorrectionBuffer) > 1
% perform linear regression
self.LinearRegressionCoeff = self.CorrectionBuffer / [ones(1,length(self.TimestampBuffer)); self.TimestampBuffer];
else
self.LinearRegressionCoeff = [self.CorrectionBuffer(end) 0];
end
case 'trimmed'
if length(self.CorrectionBuffer) > 3
% perform trimmed linear regression (remove max/min value before linear fitting)
[dummy,maxidx] = max(self.CorrectionBuffer); %#ok<ASGLU>
[dummy,minidx] = min(self.CorrectionBuffer); %#ok<ASGLU>
indices = 1:length(self.CorrectionBuffer);
indices(indices==maxidx | indices==minidx) = [];
self.TrimmedRegressionCoeff = self.CorrectionBuffer(indices) / [ones(1,length(indices)); self.TimestampBuffer(indices)];
else
self.TrimmedRegressionCoeff = [self.CorrectionBuffer(end) 0];
end
case {'robust','robust_linear'}
if length(self.CorrectionBuffer) > 2
winsor_threshold = 0.0002; % typical standard deviation of 0.2 ms
% perform linear fitting under Laplacian measurement noise (the below
% calculation tolerates 10-20% corruption by major outliers)
self.RobustRegressionCoeff = robust_fit([ones(length(self.TimestampBuffer),1) self.TimestampBuffer']/winsor_threshold, self.CorrectionBuffer'/winsor_threshold,0.001,100);
else
self.RobustRegressionCoeff = [self.CorrectionBuffer(end) 0];
end
end
end
% predict the time-correction
switch postproc
case 'zero'
result = 0;
case 'median'
result = median(self.CorrectionBuffer);
case 'linear'
result = self.LinearRegressionCoeff(1) + self.LinearRegressionCoeff(2) * t0;
case 'trimmed'
result = self.TrimmedRegressionCoeff(1) + self.TrimmedRegressionCoeff(2) * t0;
case {'robust','robust_linear'}
result = self.RobustRegressionCoeff(1) + self.RobustRegressionCoeff(2) * t0;
otherwise
error('The given post-processing option is not valid: %s',postproc);
end
end
result = lsl_time_correction(self.LibHandle, self.InletHandle,timeout);
end


function [data,timestamp] = pull_sample(self,timeout,binary_blobs)
% Pull a sample from the inlet and read it into an array of values.
% [SampleData,Timestamp] = pull_sample(Timeout)
%
% In:
% Timeout : The timeout for this operation, if any. (default: 60)
% Use 0 to make the function non-blocking.
%
% Out:
% SampleData : The sample's contents. This is either a numeric vector (type: double) if
% the stream holds numeric contents, or a cell array of strings if the stream
% is string-formatted.
% is string-formatted, or a cell array of uint8 vectors if BinaryBlobs is set
% to true.
%
% Note: this is empty if the operation times out.
%
Expand Down Expand Up @@ -273,52 +184,9 @@ function close_stream(self)
[chunk,timestamps] = lsl_pull_chunk_d(self.LibHandle,self.InletHandle,self.ChannelCount);
end



% --- internal functions ---

function h = get_libhandle(self)
% get the library handle (e.g., to query the clock)
h = self.LibHandle;
end

function x = robust_fit(A,y,rho,iters)
% Perform a robust linear regression using the Huber loss function.
% x = robust_fit(A,y,rho,iters)
%
% Input:
% A : design matrix
% y : target variable
% rho : augmented Lagrangian variable (default: 1)
% iters : number of iterations to perform (default: 1000)
%
% Output:
% x : solution for x
%
% Notes:
% solves the following problem via ADMM for x:
% minimize 1/2*sum(huber(A*x - y))
%
% Based on the ADMM Matlab codes also found at:
% http://www.stanford.edu/~boyd/papers/distr_opt_stat_learning_admm.html
%
% Christian Kothe, Swartz Center for Computational Neuroscience, UCSD
% 2013-03-04

if ~exist('rho','var')
rho = 1; end
if ~exist('iters','var')
iters = 1000; end
Aty = A'*y;
L = sparse(chol(A'*A,'lower')); U = L';
z = zeros(size(y)); u = z;
for k = 1:iters
x = U \ (L \ (Aty + A'*(z - u)));
d = A*x - y + u;
z = rho/(1+rho)*d + 1/(1+rho)*max(0,(1-(1+1/rho)./abs(d))).*d;
u = d - z;
end
end
end
end

end
13 changes: 7 additions & 6 deletions dependencies/liblsl-Matlab/lsl_outlet.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
classdef lsl_outlet < handle
% A stream outlet.
% Outlets are used to make streaming data (and the meta-data) available on the lab network.
% A stream outlet.
% Outlets are used to make streaming data (and the meta-data) available on the lab network.

properties (Hidden)
LibHandle = 0; % this is a handle to the liblsl library (to call its functions)
Expand Down Expand Up @@ -50,14 +50,15 @@ function push_sample(self,sampledata,timestamp,pushthrough)
%
% In:
% SampleData : either a numeric vector (as double, converted implicitly) with as many
% elements as the stream has channels, or a cell array of strings of the
% same size (if the stream is string-formatted).
% elements as the stream has channels, or a cell array of strings (if the
% stream is string-formatted), or a cell array of uint8 vectors to submit
% variable-length binary data.
%
% Timestamp : Optionally the capture time of the most recent sample, in agreement with lsl_local_clock();
% if 0, the current time is used.
%
% Pushthrough : Whether to push the chunk through to the receivers instead of buffering it with subsequent samples.
% Note that the chunk_size, if specified at outlet construction, takes precedence over the pushthrough flag.
% Note that the chunk_size, if specified at outlet construction, takes precedence over the pushthrough flag.
% (default: 1)

if ~exist('timestamp','var') || isempty(timestamp) timestamp = 0; end
Expand All @@ -80,7 +81,7 @@ function push_chunk(self,chunkdata,timestamps,pushthrough)
% If this is a vector, it is assumed that it contains one time stamp for each sample. (default: 0)
%
% Pushthrough : Whether to push the chunk through to the receivers instead of buffering it with subsequent samples.
% Note that the chunk_size, if specified at outlet construction, takes precedence over the pushthrough flag.
% Note that the chunk_size, if specified at outlet construction, takes precedence over the pushthrough flag.
% (default: 1)

if ~exist('timestamps','var') || isempty(timestamps) timestamps = 0; end
Expand Down
Loading

0 comments on commit 44b0fc0

Please sign in to comment.