Skip to content

Neuroscan plugin #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions bcilab_config.m
Original file line number Diff line number Diff line change
@@ -8,9 +8,16 @@
% argument to the "bcilab" function (e.g. cd /your/path/to/bcilab; bcilab
% /your/path/to/your/bcilab_config.m)

% This file is loaded by bcilab.m -- note that you can have a version of the file in your ~/.bcilab/
% directory, which will take precedence over a file of same name placed in the bcilab folder (see
% bcilab.m), and that you can specify a custom config file to load.
%
% The options in this configuration file can also be edited via the GUI (under Settings); the GUI
% always edits the file that was used when the toolbox was loaded.

% this is your data path, i.e., where your studies and data sets are stored
data = {'/data/projects'};
data = [];
% (if empty, will default to your bcilab:/userdata folder)
data = []; % e.g., {'/data/myprojects','bcilab:/userdata','home:/mydata'};

% this is a path where results can be stored (you must have write permission)
% (if empty, it is set to equal the data path)
@@ -28,7 +35,8 @@
else
cache = [];
end
% comment out this line to enable caching of intermediate results
% comment out this line to enable caching of intermediate results in one of the above default linux
% temp folders
cache = [];

% This is the memory capacity that is being reserved for in-memory caching of intermediate results.
@@ -56,9 +64,14 @@
data_reuses = 3;

% this is a directory for temporary results (e.g. IC decompositions); if this is empty, it will be
% set to a directory next to your BCILAB path
% set to a directory that is your BCILAB installation path with -temp appended.
temp = [];

% if you have private plugins that you manage separately from the bcilab directory tree, you can set
% the path to these here (using the same sub-directory structure as ~/.bcilab/, see that folder for
% reference)
private = [];

% whether to show the main BCILAB menu by default (if this is set to 'separate', the menu is
% always detached from the EEGLAB main menu; otherwise it is under Tools>BCILAB)
menu = 'separate';
@@ -75,5 +88,3 @@
acquire_options = {'Hostnames',{'localhost'},'ShutdownTimeout',300};




2 changes: 1 addition & 1 deletion code/dataset_editing/set_concat.m
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@
end
% count events, epochs and samples in each set
event_count = cellfun(@(x)length(x.event),varargin);
sample_count = cellfun(@(x)x.pnts,varargin).*epoch_count;
sample_count = cellfun(@(x)x.pnts,varargin);
% concatenate .event and .epoch fields
event = cellfun(@(x)x.event,varargin,'UniformOutput',false); result.event = [event{:}];
% shift event latencies based on cumulative sample counts
29 changes: 26 additions & 3 deletions code/environment/env_startup.m
Original file line number Diff line number Diff line change
@@ -24,6 +24,9 @@ function env_startup(varargin)
% (default: path/to/bcilab-temp, or path/to/cache/bcilab_temp if a cache
% directory was specified)
%
% 'private': optional private plugin directory, separate from bcilab/*; can have
% the same directory structure as ~/.bcilab/ (default: [])
%
% --- caching settings ---
%
% 'cache': Path where intermediate data sets are cached. Should be located on a fast
@@ -209,7 +212,7 @@ function env_startup(varargin)
disp('Note: Your version of MATLAB is not supported by BCILAB any more. You may try BCILAB version 0.9, which supports old MATLAB''s back to version 2006a.'); end

% get options
opts = hlp_varargin2struct(varargin,'data',[],'store',[],'cache',[],'temp',[],'mem_capacity',2,'data_reuses',3, ...
opts = hlp_varargin2struct(varargin,'data',[],'store',[],'cache',[],'temp',[],'private',[],'mem_capacity',2,'data_reuses',3, ...
'parallel',{'use','local'}, 'menu',true, 'configscript','', 'worker',false, 'autocompile',true, 'acquire_options',{}, ...
'show_experimental',false,'show_guru',false);

@@ -218,6 +221,8 @@ function env_startup(varargin)
env_load_dependencies(dependency_dir,opts.autocompile);
if exist(env_translatepath('home:/.bcilab/code/dependencies'),'dir')
env_load_dependencies(env_translatepath('home:/.bcilab/code/dependencies'),opts.autocompile); end
if ~isempty(opts.private) && exist(env_translatepath([opts.private '/code/dependencies']),'dir')
env_load_dependencies(env_translatepath([opts.private '/code/dependencies']),opts.autocompile); end

if ischar(opts.worker)
try
@@ -248,7 +253,7 @@ function env_startup(varargin)
opts.data = {opts.data}; end
for d = 1:length(opts.data)
opts.data{d} = path_normalize(opts.data{d}); end
if isempty(opts.data) || ~any(cellfun(@exist,opts.data))
if isempty(opts.data)
opts.data = {[base_dir 'userdata']}; end

% process store directory
@@ -297,7 +302,7 @@ function env_startup(varargin)

% set global variables
global tracking
tracking.paths = struct('bcilab_path',{base_dir(1:end-1)}, 'function_path',{function_dir}, 'data_paths',{opts.data}, 'store_path',{opts.store}, 'dependency_path',{dependency_dir},'resource_path',{resource_dir},'temp_path',{opts.temp});
tracking.paths = struct('bcilab_path',{base_dir(1:end-1)}, 'function_path',{function_dir}, 'data_paths',{opts.data}, 'store_path',{opts.store}, 'dependency_path',{dependency_dir},'resource_path',{resource_dir},'temp_path',{opts.temp}, 'private_path',{opts.private});
for d=1:length(opts.cache)
location = rmfield(opts.cache{d},'tag');
% convert GiB to bytes
@@ -406,6 +411,8 @@ function env_startup(varargin)
disp('cache is disabled');
end
disp(['temp is in ' opts.temp]);
if ~isempty(opts.private)
disp(['private plugins are in ' opts.private]); end
fprintf('\n');


@@ -463,6 +470,22 @@ function env_startup(varargin)
end
catch,end

% add private plugin directories to the path
if ~isempty(opts.private)
try
private_codedirs = {['code' filesep 'filters'],['code' filesep 'dataset_editing'], ...
['code' filesep 'machine_learning'], ['code' filesep 'paradigms'], ['code' filesep 'scripts']};
% and add the code directories to the path
for d = private_codedirs
tmpdir = env_translatepath([opts.private filesep d{1}]);
if exist(tmpdir,'dir')
addpath(genpath(tmpdir)); end
end
catch e
disp(['Could not add private code directories to path: ' e.message]);
end
end

% create a menu
if ~(isequal(opts.menu,false) || isequal(opts.menu,0))
try
89 changes: 51 additions & 38 deletions code/environment/env_translatepath.m
Original file line number Diff line number Diff line change
@@ -10,6 +10,11 @@
% 30])", and this in turn allows to share the same data set caches (which are indexed by expression)
% across machines and operating systems, minimizing redundant computations.
%
% Some of the path references, such as data:/, temp:/, store:/, private:/ can be set in the startup
% arguments of bcilab.m or bcilab_config.m (see documentation in those files). It is permitted to
% use other relative paths, such as home:/ when specifying those arguments (but circular references
% must be avoided).
%
% In:
% IndependentPath : platform-independent path; may contain forward and/or backward slashes
% (forward slashes generally preferred), and may refer to locations such as
@@ -42,7 +47,6 @@
% % resolve a reference to the resources directory
% env_translatepath('resources:/workspaces/testing.mat')
%
%
% See also:
% env_startup, io_loadset
%
@@ -51,47 +55,56 @@

global tracking;

% turn the path into a system-dependent one
filename = strrep(strrep(filename,'\',filesep),'/',filesep);
% function to sanitize file names (replace file separators by system-dependent version)
sanitize = @(filename) strrep(strrep(strrep(filename,'\',filesep),'/',filesep),[filesep filesep],filesep);

% resolve location references
if strncmp('store:',filename,6)
filename = [tracking.paths.store_path filename(1+length('store:'):end)];
elseif strncmp('resources:',filename,10)
filename = [tracking.paths.resource_path filename(1+length('resources:'):end)];
elseif strncmp('temp:',filename,5)
filename = [tracking.paths.temp_path filename(1+length('temp:'):end)];
elseif strncmp('functions:',filename,10)
filename = [tracking.paths.function_path filename(1+length('functions:'):end)];
elseif strncmp('bcilab:',filename,7)
filename = [tracking.paths.bcilab_path filename(1+length('bcilab:'):end)];
elseif strncmp('dependencies:',filename,13)
filename = [tracking.paths.dependency_path filename(1+length('dependencies:'):end)];
elseif strncmp('home:',filename,5)
filename = [hlp_homedir filename(1+length('home:'):end)];
elseif strncmp('data:',filename,5)
rest = filename(1+length('data:'):end);
bestpath = 1; bestlen = -1;
if length(tracking.paths.data_paths) > 1
fpieces = hlp_split(rest,filesep);
% find the data path that contains the longest prefix of the filename
for pidx=1:length(tracking.paths.data_paths)
p = tracking.paths.data_paths{pidx};
% for each prefix of the filename (starting with the longest one)
for k=length(fpieces):-1:0
% check if the data path plus the first k pieces of the filename exists
if exist([p sprintf([filesep '%s'],fpieces{1:k})],'file')
% found a match - check if it is a new length record among all our data paths...
if k>bestlen
bestlen = k;
bestpath = pidx;
% loop until all path references have been resolved
while true
% resolve location references
if strncmp('store:',filename,6)
filename = [tracking.paths.store_path filename(1+length('store:'):end)];
elseif strncmp('resources:',filename,10)
filename = [tracking.paths.resource_path filename(1+length('resources:'):end)];
elseif strncmp('temp:',filename,5)
filename = [tracking.paths.temp_path filename(1+length('temp:'):end)];
elseif strncmp('functions:',filename,10)
filename = [tracking.paths.function_path filename(1+length('functions:'):end)];
elseif strncmp('bcilab:',filename,7)
filename = [tracking.paths.bcilab_path filename(1+length('bcilab:'):end)];
elseif strncmp('dependencies:',filename,13)
filename = [tracking.paths.dependency_path filename(1+length('dependencies:'):end)];
elseif strncmp('private:',filename,8)
filename = [tracking.paths.private_path filename(1+length('private:'):end)];
elseif strncmp('home:',filename,5)
filename = [hlp_homedir filename(1+length('home:'):end)];
elseif strncmp('data:',filename,5)
rest = filename(1+length('data:'):end);
bestpath = 1; bestlen = -1;
if length(tracking.paths.data_paths) > 1
% find the data path that contains the longest prefix of the filename
fpieces = hlp_split(sanitize(rest),filesep);
for pidx=1:length(tracking.paths.data_paths)
p = env_translatepath(tracking.paths.data_paths{pidx});
% for each prefix of the filename (starting with the longest one)
for k=length(fpieces):-1:0
% check if the data path plus the first k pieces of the filename exists
if exist([p filesep fpieces{1:k}],'file')
% found a match - check if it is a new length record among all our data paths...
if k>bestlen
bestlen = k;
bestpath = pidx;
end
break;
end
break;
end
end
end
% resolve the reference using that data path which matches most of the filename,
% where, if multiple data paths are equally well suited, the first one of them is taken
filename = [tracking.paths.data_paths{bestpath} rest];
else
break;
end
% resolve the reference using that data path which matches most of the filename,
% where, if multiple data paths are equally well suited, the first one of them is taken
filename = [tracking.paths.data_paths{bestpath} rest];
end

filename = sanitize(filename);
4 changes: 4 additions & 0 deletions code/filters/flt_pipeline.m
Original file line number Diff line number Diff line change
@@ -391,6 +391,10 @@
dir(env_translatepath('functions:/dataset_editing/set_*.m'));
dir(env_translatepath('functions:/filters/in_development/flt_*.m'));
dir(env_translatepath('functions:/dataset_editing/in_development/set_*.m'));
dir(env_translatepath('private:/code/filters/flt_*.m'));
dir(env_translatepath('private:/code/dataset_editing/set_*.m'));
dir(env_translatepath('private:/code/filters/in_development/flt_*.m'));
dir(env_translatepath('private:/code/dataset_editing/in_development/set_*.m'));
dir(env_translatepath('home:/.bcilab/code/filters/flt_*.m'));
dir(env_translatepath('home:/.bcilab/code/dataset_editing/set_*.m'))];

6 changes: 5 additions & 1 deletion code/gui/gui_batchanalysis.m
Original file line number Diff line number Diff line change
@@ -81,6 +81,7 @@ function gui_batchanalysis_OpeningFcn(hObject, eventdata, handles, varargin)
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
global tracking;

% turn all large edit panes to multi-line editors
for obj = {handles.edit1,handles.edit20,handles.edit19}
@@ -93,7 +94,10 @@ function gui_batchanalysis_OpeningFcn(hObject, eventdata, handles, varargin)

% get all paradigm names
para_files = [];
for p = {'functions:/paradigms', 'home:/.bcilab/code/paradigms'}
para_paths = {'functions:/paradigms', 'functions:/paradigms/in_development', 'home:/.bcilab/code/paradigms'};
if ~isempty(tracking.paths.private_path)
para_paths = [para_paths {'private:/code/paradigms','private:/code/paradigms/in_development'}]; end
for p = para_paths
para_files = [para_files dir([env_translatepath(p{1}) filesep 'Paradigm*.m'])]; end
para_acronyms = cellfun(@(s)s(9:end-2),{para_files.name},'UniformOutput',false);

9 changes: 8 additions & 1 deletion code/gui/utils/gui_listapproaches.m
Original file line number Diff line number Diff line change
@@ -27,6 +27,11 @@
{'Built-in Paradigms', {'functions:/paradigms'}}, ...
{'User Paradigms', {'home:/.bcilab/code/paradigms'}}, ...
{'Experimental Paradigms',quickif(tracking.gui.show_experimental,{'functions:/paradigms/in_development'},{})}};
if ~isempty(tracking.paths.private_path)
paradigm_locations = [paradigm_locations { ...
{'Private Paradigms', {'private:/code/paradigms'}}, ...
{'Private Experimental Paradigms',quickif(tracking.gui.show_experimental,{'private:/code/paradigms/in_development'},{})}}];
end

for loc=1:length(paradigm_locations)
if ~isempty(paradigm_locations{loc}{2})
@@ -68,7 +73,7 @@
for v=find(strcmp({vars.class},'cell'))
var = evalin('base',vars(v).name);
if length(var)>1 && ischar(var{1}) && ((strncmp(var{1},'Paradigm',8) && length(var{1})>8 && any(strcmp(var{1}(9:end),paradigm_names))) || any(strcmp(var{1},paradigm_names)))
approaches{end+1} = struct('paradigm',quickif(strncmp(var{1},'Paradigm',8),var{1},['Paradigm',var{1}]),'name',vars(v).name,'parameters',{var(2:end)}); end
approaches{end+1} = struct('paradigm',quickif(strncmp(var{1},'Paradigm',8),var{1},['Paradigm',var{1}]),'name',vars(v).name,'parameters',{var(2:end)},'description',''); end
end

list = [list {'From Workspace', approaches}];
@@ -77,6 +82,8 @@
% --- aggregate all approaches in the approaches directory (and the user's home directory, too) ---
approaches = {};
approach_dirs = {env_translatepath('home:/.bcilab/approaches'),env_translatepath('resources:/approaches')};
if ~isempty(tracking.paths.private_path)
approach_dirs = [approach_dirs {env_translatepath('private:/approaches')}]; end
for d = approach_dirs
approach_dir = d{1};
files = dir([approach_dir filesep '*.apr']);
6 changes: 5 additions & 1 deletion code/machine_learning/ml_train.m
Original file line number Diff line number Diff line change
@@ -180,10 +180,14 @@

function learners = list_learners(update_list)
% list all the learning functions in code/machine_learning/
global tracking;
persistent memo;
if isempty(memo) || exist('update_list','var') && update_list
memo = {};
for p = {'functions:/machine_learning/ml_train*.m','home:/.bcilab/code/machine_learning/ml_train*.m'}
ml_paths = {'functions:/machine_learning/ml_train*.m','home:/.bcilab/code/machine_learning/ml_train*.m'};
if ~isempty(tracking.paths.private_path)
ml_paths = [ml_paths {'private:/code/machine_learning/ml_train*.m'}]; end
for p = ml_paths
modules = dir(env_translatepath(p{1}));
names = setdiff({modules.name},{'ml_train.m','ml_trainvote.m'});
tags = cellfun(@(n) n(9:end-2),names,'UniformOutput',false);
18 changes: 18 additions & 0 deletions code/online_plugins/Neuroscan/BCILAB.readme
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
README for Neuroscan Scan Plugin for BCILAB
Author: Visual Attention and Cognition Lab, Dan Roberts, and Nick Pe�aranda, George Mason University, Spring 2014
Released under the GPLv3, see COPYING.txt
Based on the BrainVision BCILAB plug-in by Hal Greenwald


Plugin to stream EEG data and event markers from Neuroscan Scan software to BCILAB. Has been tested with Scan 4.3 and a NuAmps 40-channel amplifier

Associated Files:

BCILAB/code/online_plugins/Neuroscan:
ns_close.m
ns_open.m
ns_parseheader.m
ns_parseinfo.m
ns_read.m
ns_sendpacket.m
run_readneuroscan.m
674 changes: 674 additions & 0 deletions code/online_plugins/Neuroscan/COPYING.txt

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions code/online_plugins/Neuroscan/ns_close.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
function ns_close(h)
% Close a TCP connection to Neuroscan Scan
% ns_close(h)
%
%
% In:
% h : handle to an existing Neuroscan connection
%
% Author: Visual Attention and Cognition Lab, Dan Roberts, and Nick Peñaranda, George Mason University, Spring 2014
% Released under the GPLv3, see COPYING.txt
% Based on the BrainVision BCILAB plug-in by Hal Greenwald


% send message to Scan to stop sending data
ns_sendpacket(TCP_Connection,'CTRL',3,4,0);
% and indicate that connection is closing
ns_sendpacket(TCP_Connection,'CTRL',1,2,0);

if ~h.initialized
return;
end
disp('Cleaning up connection to Neuroscan Scan');
pnet(h.handle, 'close');
if evalin('base', ['exist(' 'sprintf(''%s'', h.name)' ')'])
evalin('base',['clear ' sprintf('%s',h.name) ';']);
end
89 changes: 89 additions & 0 deletions code/online_plugins/Neuroscan/ns_open.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
function h = ns_open(hostname, port)
% Open a TCP connection to Neuroscan Recorder
% h = ns_open(Hostname, Port)
%
%
% In:
% Hostname: Source TCP hostname. Can be a computer name, URL, or IP
% address
%
% Port : the port on which to connect to the TCP host
%
% Out:
% h : handle to a newly opened Neuroscan connection
%
% Author: Visual Attention and Cognition Lab, Dan Roberts, and Nick Peñaranda, George Mason University, Spring 2014
% Released under the GPLv3, see COPYING.txt
% Based on the BrainVision BCILAB plug-in by Hal Greenwald


% open the connection
h.handle = pnet('tcpconnect', hostname, port);
ConnectionStatus = pnet(h.handle,'status');

if ConnectionStatus > 0
disp('Neuroscan Scan connection established');
else
error('Neuroscan Scan connection failed - check the IP and port');
end

% flush the buffer
bufferData = pnet(h.handle,'read', 'noblock');
while ~isempty(bufferData);
bufferData = pnet(h.handle,'read', 'noblock');
end

% request basic header
ns_sendpacket(h.handle,'CTRL',3,5,0);

% try
% read reader and basic info
packetBytes = pnet(h.handle,'read', 40, 'uint8');

% parse header data
header = ns_parseheader(packetBytes(1:12));
basicinfo = ns_parseinfo(packetBytes(13:end));

% attach Neuroscan parameters to connection handle
infoFields = fields(basicinfo);
for f = 1:length(infoFields)
h.(infoFields{f}) = basicinfo.(infoFields{f});
end

h.totalChan = h.numChan + h.numEventChan;

if h.numEventChan ~= 0
h.markerChanIdx = h.numChan + 1;
end

% number of bytes for neuroscan header
h.headerSize = 12;

% save the block size, equal to the number of channels (including
% marker channel) x the number of samples per block x the number of
% bytes per sample
h.dataBlockSize = (basicinfo.numChan + basicinfo.numEventChan) ...
* basicinfo.samplesPerBlock * basicinfo.bytesPerSample;

% save the data type (16 or 32 bit integer) for later casting
if h.bytesPerSample == 2
h.datatype = 'int16';
elseif h.bytesPerSample == 4
h.datatype = 'int32';
else
error('expecting either 2 or 4 bytes per sample');
end

h.cleanup = onCleanup(@()pnet(h.handle, 'close'));

% instruct Scan to begin sending data
ns_sendpacket(h.handle,'CTRL',3,3,0);
h.initialized = true;

% catch er
% disp(er.message);
% return;
% end

end

36 changes: 36 additions & 0 deletions code/online_plugins/Neuroscan/ns_parseheader.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
function hdr = ns_parseheader(header)
% Parse the header info packet returned by Neuroscan Scan server
% hdr = ns_parseinfo(dataBytes)
%
%
% In:
% dataBytes : 12 byte array returned by Neuroscan server as header
%
% Out:
% hdr: Structure containing each element of packet header as a field.
% Header information includes:
%
% id: ID string, 'CTRL', 'FILE', or 'DATA'
%
% code: Control code, 1 (General), 2 (Server), or 3 (Client)
%
% req: Request value, see the Neuroscan Acquire manual
%
% bodysize: Size (in bytes) of message attached to header,
% or 0 if packet does not contain a data body (for example start
% or stop acquisition messages)
%
%
% Author: Visual Attention and Cognition Lab, Dan Roberts, and Nick Peñaranda, George Mason University, Spring 2014
% Released under the GPLv3, see COPYING.txt
% Based on the BrainVision BCILAB plug-in by Hal Greenwald


hdr = struct('id',[],'code',[],'req',[],'bodysize',[]);

hdr.id = char(header(1:4));
hdr.code = double(typecast(fliplr(uint8(header(5:6))), 'uint16'));
hdr.req = double(typecast(fliplr(uint8(header(7:8))), 'uint16'));
hdr.bodysize = double(typecast(fliplr(uint8(header(9:12))), 'uint32'));


47 changes: 47 additions & 0 deletions code/online_plugins/Neuroscan/ns_parseinfo.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
function basicinfo = ns_parseinfo(dataBytes)
% Parse the basic info packet returned by Neuroscan Scan
% basicinfo = ns_parseinfo(dataBytes)
%
%
% In:
% dataBytes : 28 byte array returned by Neuroscan server after requesting
% basic info
%
% Out:
% basicinfo: Structure containing each element of basic EEG info as a
% field. EEG info includes:
%
% size: size of info array (in bytes)
%
% numChan: number of EEG data channels
%
% numEventChan: number of event marker channels
%
% samplesPerBlock: the number of data samples transmitted each block
%
% srate: data sampling rate in Hz
%
% bytesPerSamples: number of bytes per sample, either 2 (16 bits
% per sample) or 4 (32 bits per sample)
%
% resolution: the value in microvolts represented by the least
% significant bit
%
%
% Author: Visual Attention and Cognition Lab, Dan Roberts, and Nick Peñaranda, George Mason University, Spring 2014
% Released under the GPLv3, see COPYING.txt
% Based on the BrainVision BCILAB plug-in by Hal Greenwald

% basic EEG info
basicinfo = struct('size',[],'numChan',[],'numEventChan',[],'samplesPerBlock',[],...
'srate', [], 'bytesPerSample', [], 'resolution', []);

basicinfo.size = double(typecast((uint8(dataBytes(1:4))), 'int32'));
basicinfo.numChan = double(typecast((uint8(dataBytes(5:8))), 'int32'));
basicinfo.numEventChan = double(typecast((uint8(dataBytes(9:12))), 'int32'));
basicinfo.samplesPerBlock = double(typecast((uint8(dataBytes(13:16))), 'int32'));
basicinfo.srate = double(typecast((uint8(dataBytes(17:20))), 'int32'));
basicinfo.bytesPerSample = double(typecast((uint8(dataBytes(21:24))), 'int32'));
basicinfo.resolution = double(typecast((uint8(dataBytes(25:28))), 'single'));

end
84 changes: 84 additions & 0 deletions code/online_plugins/Neuroscan/ns_read.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
function block = ns_read(h)
% Block reader for Neuroscan Scan Recorder
% ns_read(h)
%
%
% In:
% h : handle to an existing Neuroscan connection
%
% Out:
% block: cell array data block containing EEG and event values
%
% Author: Visual Attention and Cognition Lab, Dan Roberts, and Nick Peñaranda, George Mason University, Spring 2014
% Released under the GPLv3, see COPYING.txt
% Based on the BrainVision BCILAB plug-in by Hal Greenwald

data = [];
if (~h.initialized)
return;
end

try
% check for existing data in socket buffer
headerBytes = pnet(h.handle, 'read', h.headerSize, 'uint8', 'noblock');

events = [];

if ~isempty(headerBytes)

headerData = ns_parseheader(headerBytes);

if strcmp(headerData.id, 'DATA')

% wait until the data packet body is available
dataPreview = [];
while length(dataPreview) < headerData.bodysize
% wait until the rest of the packet is available
dataPreview = pnet(h.handle, 'read', headerData.bodysize,...
'uint8', 'view', 'noblock');
end

% read the data packet body
dataBytes = pnet(h.handle, 'read', headerData.bodysize, 'uint8', 'noblock');

dataBytes = reshape(uint8(dataBytes), [h.bytesPerSample, h.totalChan, h.samplesPerBlock]);

markerValues = squeeze((dataBytes(1,h.markerChanIdx, :)));
markerPoints = find(markerValues);

if ~isempty(markerPoints)
markers = markerValues(markerPoints);
latencies = markerPoints;
for m = 1:length(markers)
events(m).type = num2str(markers(m)); %#ok<AGROW>
events(m).latency = latencies(m); %#ok<AGROW>
end
else
events = [];
end

dataBytes(:,h.markerChanIdx, :) = [];

dataCell = squeeze(num2cell(dataBytes, 1));
dataBlock = cellfun(@(x) typecast(x, h.datatype), dataCell);
data = horzcat(data, dataBlock);

else
disp('unknown message');
end

end


catch er
disp(er.message);
rethrow(er);
end

if ~isempty(data)
% scale data to uV units
data = bsxfun(@times,data,h.resolution);
end

block = {data,events};

35 changes: 35 additions & 0 deletions code/online_plugins/Neuroscan/ns_sendpacket.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
function ns_sendpacket(h,id,code,request,bodysize)
% Prepare and send a data packet to Neuroscan Scan server
% ns_sendpacket(h,id,code,request,bodysize)
%
%
% In:
% h: PNET connection handle
%
% id: Neuroscan message ID string
%
% code: Neuroscan message code value
%
% request: Neuroscan message request value
%
% bodysize: Size of message body
%
% Note: See the Neuroscan Scan documentation for description of message ID,
% code, and request values
%
% Author: Visual Attention and Cognition Lab, Dan Roberts, and Nick Peñaranda, George Mason University, Spring 2014
% Released under the GPLv3, see COPYING.txt
% Based on the BrainVision BCILAB plug-in by Hal Greenwald


% assemble the packet
packet = zeros(12,1,'uint8');
packet(1:4) = id;
packet(5:6) = fliplr(typecast(int16(code),'int8'));
packet(7:8) = fliplr(typecast(int16(request),'int8'));
packet(9:12) = fliplr(typecast(int32(bodysize),'int8'));
% write the packet
pnet(h,'write', packet)

end

73 changes: 73 additions & 0 deletions code/online_plugins/Neuroscan/run_readneuroscan.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
function run_readneuroscan(varargin)
% Receive real-time data from Neuroscan Scan Recorder
% run_readneuroscan(MatlabStream,InputHost,Port)
%
%
% In:
% MatlabStream : name of the stream to create in the MATLAB environment (default: 'laststream')
%
% InputHost: Source TCP hostname. Can be a computer name, URL, or IP
% address (default: '127.0.0.1')
%
% Port : the port on which to connect to the TCP host (default: 4000)
%
% UpdateFrequency : update frequency, in Hz (default: 25)
%
% Examples:
% % open an input stream that named 'newstream' on port 5000, using all
% % other defaults
% run_readneuroscan('MatlabStream', 'newstream', 'Port', 5000);
%
% Author: Visual Attention and Cognition Lab, Dan Roberts, and Nick Peñaranda, George Mason University, Spring 2014
% Released under the GPLv3, see COPYING.txt
% Based on the BrainVision BCILAB plug-in by Hal Greenwald

% declare the name of this component (shown in the menu)
declare_properties('name','Neuroscan Recorder');

% read options
opts = arg_define(varargin, ...
arg({'new_stream','MatlabStream'}, 'laststream',[],'New Stream to create. This is the name of the stream within the MATLAB environment.'), ...
arg({'src_hostname','InputHost'}, '127.0.0.1',[],'Source TCP hostname. Can be a computer name, URL, or IP address.'), ...
arg({'src_port','Port'}, 4000,[],'TCP Host Port.'), ...
arg({'update_freq','UpdateFrequency'}, 25,[],'Update frequency (Hz). New data is polled at this rate, in Hz.'),...
arg({'chan_labels', 'ChannelLabels'}, 'standard', [], 'Enter custom channel labels if applicable')...
);

% open a connection
h = ns_open(opts.src_hostname, opts.src_port);

if strcmpi(opts.chan_labels, 'standard')
if h.numChan == 40 % assume standard NuAmps 40-channel montage
h.channelNames = {'HEOL', 'HEOR', 'FP1', 'FP2', 'VEOU', 'VEOL', 'F7', 'F3', 'FZ',...
'F4', 'F8', 'FT7', 'FC3', 'FCZ', 'FC4', 'FT8', 'T3', 'C3', 'CZ', 'C4', 'T4', ...
'TP7', 'CP3', 'CPZ', 'CP4', 'TP8', 'A1', 'T5', 'P3', 'PZ', 'P4', 'T6', ...
'A2', 'O1', 'OZ', 'O2', 'FT9', 'FT10', 'PO1', 'PO2'};
elseif h.numChan == 68 % assume standard SynAmps2 montage,
% which includes 64 EEG channels + VEOG, HEOG, EKG, EMG
h.channelNames = {'FP1','FPZ','FP2','AF3','AF4','F7','F5','F3',...
'F1','FZ','F2','F4','F6','F8','FT7','FC5','FC3','FC1','FCZ',...
'FC2','FC4','FC6','FT8','T7','C5','C3','C1','CZ','C2','C4',...
'C6','T8','M1','TP7','CP5','CP3','CP1','CPZ','CP2','CP4',...
'CP6','TP8','M2','P7','P5','P3','P1','PZ','P2','P4','P6',...
'P8','PO7','PO5','PO3','POZ','PO4','PO6','PO8','CB1','O1',...
'OZ','O2','CB2','VEO','HEO','EKG','EMG'};
else
error('unknown default channel labels for this montage');
end
else % custom channel labels
opts.chan_labels = evalin('base', opts.chan_labels);
if length(opts.chan_labels) ~= h.numChan
errorMsg = sprintf('the number of custom channel labels provided (%i) does not match the number of channels in the data stream (%i)', length(opts.chan_labels), h.numChan);
ns_close(h);
error(errorMsg); %#ok<SPERR>
end
end

h.name = opts.new_stream;

%Create and initialize online stream
onl_newstream(opts.new_stream, 'srate', h.srate, 'chanlocs', h.channelNames, 'data', zeros(length(h.channelNames),0,0),'xmin',toc(uint64(0)));

% start background acquisition
onl_read_background(opts.new_stream,@()ns_read(h), opts.update_freq);
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
option_savetwofiles = 1 ; % If set, save not one but two files for each dataset (header and data). This allows faster data loading in studies.
option_saveica = 0 ; % If set, write ICA activations to disk. This speeds up loading ICA components when dealing with studies.
% Memory options
option_single = 1 ; % If set, use single precision under Matlab 7.x. This saves RAM but can lead to rare numerical imprecisions.
option_single = 0 ; % If set, use single precision under Matlab 7.x. This saves RAM but can lead to rare numerical imprecisions.
option_memmapdata = 0 ; % If set, use memory mapped array under Matlab 7.x. This may slow down some computation.
option_eegobject = 0 ; % If set, use the EEGLAB EEG object instead of the standard EEG structure (experimental).
% ICA options
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
option_savetwofiles = 1 ; % If set, save not one but two files for each dataset (header and data). This allows faster data loading in studies.
option_saveica = 0 ; % If set, write ICA activations to disk. This speeds up loading ICA components when dealing with studies.
% Memory options
option_single = 1 ; % If set, use single precision under Matlab 7.x. This saves RAM but can lead to rare numerical imprecisions.
option_single = 0 ; % If set, use single precision under Matlab 7.x. This saves RAM but can lead to rare numerical imprecisions.
option_memmapdata = 0 ; % If set, use memory mapped array under Matlab 7.x. This may slow down some computation.
option_eegobject = 0 ; % If set, use the EEGLAB EEG object instead of the standard EEG structure (experimental).
% ICA options
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@
if args.effective_rate && isfinite(stream.info.effective_srate) && stream.info.effective_srate>0
raw.srate = stream.info.effective_srate;
else
raw.srate = str2num(stream.info.nominal_srate);
raw.srate = str2num(stream.info.nominal_srate); %#ok<ST2NM>
end
raw.xmin = 0;
raw.xmax = (raw.pnts-1)/raw.srate;
@@ -105,19 +105,24 @@

% events...
event = [];
if isfinite(stream.info.effective_srate) && stream.info.effective_srate>0
srate = stream.info.effective_srate;
else
srate = raw.srate;
end
for s=1:length(streams)
if (strcmp(streams{s}.info.type,'Markers') || strcmp(streams{s}.info.type,'Events')) && ~ismember(streams{s}.info.name,args.exclude_markerstreams)
try
if iscell(streams{s}.time_series)
for e=1:length(streams{s}.time_stamps)
event(end+1).type = streams{s}.time_series{e};
event(end).latency = 1+raw.srate*(streams{s}.time_stamps(e)-stream.time_stamps(1));
event(end).latency = 1+srate*(streams{s}.time_stamps(e)-stream.time_stamps(1));
event(end).duration = 1;
end
else
for e=1:length(streams{s}.time_stamps)
event(end+1).type = num2str(streams{s}.time_series(e));
event(end).latency = 1+raw.srate*(streams{s}.time_stamps(e)-stream.time_stamps(1));
event(end).latency = 1+srate*(streams{s}.time_stamps(e)-stream.time_stamps(1));
event(end).duration = 1;
end
end
@@ -128,6 +133,7 @@
end
raw.event = event;


% etc...
raw.etc.desc = stream.info.desc;
raw.etc.info = rmfield(stream.info,'desc');
Binary file modified resources/sa_montreal_small.mat
Binary file not shown.