diff --git a/+dat/addLogEntry.m b/+dat/addLogEntry.m index bb6785b6..fcbeb676 100644 --- a/+dat/addLogEntry.m +++ b/+dat/addLogEntry.m @@ -1,4 +1,4 @@ -function e = addLogEntry(subject, timestamp, type, value, comments) +function e = addLogEntry(subject, timestamp, type, value, comments, AlyxInstance) %DAT.ADDLOGENTRY Adds a new entry to the experiment log % e = DAT.ADDLOGENTRY(subject, timestamp, type, value, comments) files a % new log entry for 'subject' with the corresponding info. @@ -26,6 +26,7 @@ %% create and store entry e = entry(nextidx); log(nextidx) = e; +if nargin > 5; e.AlyxInstance = AlyxInstance; end %% store updated log to *all* repos locations superSave(dat.logPath(subject, 'all'), struct('log', log)); diff --git a/+dat/listSubjects.m b/+dat/listSubjects.m index f3aff6ec..dbac4793 100644 --- a/+dat/listSubjects.m +++ b/+dat/listSubjects.m @@ -1,17 +1,43 @@ -function subjects = listSubjects() +function subjects = listSubjects(varargin) %DAT.LISTSUBJECTS Lists recorded subjects -% subjects = DAT.LISTSUBJECTS() Lists the experimental subjects present +% subjects = DAT.LISTSUBJECTS([alyxInstance]) Lists the experimental subjects present % in experiment info repository ('expInfo'). % +% Optional input argument of an alyx instance will enable generating this +% list from alyx rather than from the directory structure on zserver +% % Part of Rigbox % 2013-03 CB created +% 2018-01 NS added alyx compatibility -% The master 'expInfo' repository is the reference for the existence of -% experiments, as given by the folder structure -expInfoPath = dat.reposPath('expInfo', 'master'); - -dirs = file.list(expInfoPath, 'dirs'); -subjects = setdiff(dirs, {'misc'}); %exclude the misc directory - +if nargin>0 && ~isempty(varargin{1}) % user provided an alyx instance + ai = varargin{1}; % an alyx instance + + % get list of all living, non-stock mice from alyx + s = alyx.getData(ai, 'subjects?stock=False&alive=True'); + + % determine the user for each mouse + respUser = cellfun(@(x)x.responsible_user, s, 'uni', false); + + % get cell array of subject names + subjNames = cellfun(@(x)x.nickname, s, 'uni', false); + + % determine which subjects belong to this user + thisUserSubs = sort(subjNames(strcmp(respUser, ai.username))); + + % all the subjects + otherUserSubs = sort(subjNames(~strcmp(respUser, ai.username))); + + % the full, ordered list + subjects = [{'default'}, thisUserSubs, otherUserSubs]'; +else + + % The master 'expInfo' repository is the reference for the existence of + % experiments, as given by the folder structure + expInfoPath = dat.reposPath('expInfo', 'master'); + + dirs = file.list(expInfoPath, 'dirs'); + subjects = setdiff(dirs, {'misc'}); %exclude the misc directory +end end \ No newline at end of file diff --git a/+dat/loadBlock.m b/+dat/loadBlock.m index e2e2f511..827a74ac 100644 --- a/+dat/loadBlock.m +++ b/+dat/loadBlock.m @@ -1,5 +1,5 @@ function block = loadBlock(varargin) -%loadBlock Load the designated experiment block(s) +%DAT.LOADBLOCK Load the designated experiment block(s) % BLOCK = loadBlock(EXPREF, [EXPTYPE]) % % BLOCK = loadBlock(SUBJECTREF, EXPDATE, EXPSEQ, [EXPTYPE]) diff --git a/+dat/newExp.m b/+dat/newExp.m index 035d351e..2358ac04 100644 --- a/+dat/newExp.m +++ b/+dat/newExp.m @@ -1,6 +1,20 @@ -function [expRef, expSeq] = newExp(subject, expDate, expParams) +function [expRef, expSeq, url] = newExp(subject, expDate, expParams, AlyxInstance) %DAT.NEWEXP Create a new unique experiment in the database -% [ref, seq] = DAT.NEWEXP(subject, expDate, expParams) TODO +% [ref, seq, url] = DAT.NEWEXP(subject, expDate, expParams[, AlyxInstance]) +% Create a new experiment by creating the relevant folder tree in the +% local and main data repositories in the following format: +% +% subject/ +% |_ YYYY-MM-DD/ +% |_ expSeq/ +% +% If experiment parameters are passed into the function, they are saved +% here, as a mat and in JSON (if possible). If an instance of Alyx is +% passed and a base session for the experiment date is not found, one is +% created in the Alyx database. A corresponding subsession is also +% created and the parameters file is registered with the sub-session. +% +% See also DAT.PATHS % % Part of Rigbox @@ -16,6 +30,11 @@ expParams = []; end +if (nargin < 4 || isempty(AlyxInstance)) && ~strcmp(subject, 'default') + % no instance of Alyx, don't create session on Alyx + AlyxInstance = alyx.loginWindow; +end + if ischar(expDate) % if the passed expDate is a string, parse it into a datenum expDate = datenum(expDate, 'yyyy-mm-dd'); @@ -29,8 +48,7 @@ [~, dateList, seqList] = dat.listExps(subject); % filter the list by expdate -expDate = floor(expDate); -filterIdx = dateList == expDate; +filterIdx = dateList == floor(expDate); % find the next sequence number expSeq = max(seqList(filterIdx)) + 1; @@ -40,7 +58,7 @@ end % expInfo repository is the reference location for which experiments exist -[expPath, expRef] = dat.expPath(subject, expDate, expSeq, 'expInfo'); +[expPath, expRef] = dat.expPath(subject, floor(expDate), expSeq, 'expInfo'); % ensure nothing went wrong in making a "unique" ref and path to hold assert(~any(file.exists(expPath)), ... sprintf('Something went wrong as experiment folders already exist for "%s".', expRef)); @@ -48,6 +66,53 @@ % now make the folder(s) to hold the new experiment assert(all(cellfun(@(p) mkdir(p), expPath)), 'Creating experiment directories failed'); +if ~strcmp(subject, 'default') % Ignore fake subject + % if the Alyx Instance is set, find or create BASE session + expDate = alyx.datestr(expDate); % date in Alyx format + % Get list of base sessions + sessions = alyx.getData(AlyxInstance,... + ['sessions?type=Base&subject=' subject]); + + %If the date of this latest base session is not the same date as + %today, then create a new base session for today + if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), expDate(1:10)) + d = struct; + d.subject = subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = expDate; + d.type = 'Base'; + % d.users = {AlyxInstance.username}; + + base_submit = alyx.postData(AlyxInstance, 'sessions', d); + assert(isfield(base_submit,'subject'),... + 'Submitted base session did not return appropriate values'); + + %Now retrieve the sessions again + sessions = alyx.getData(AlyxInstance,... + ['sessions?type=Base&subject=' subject]); + end + latest_base = sessions{end}; + + %Now create a new SUBSESSION, using the same experiment number + d = struct; + d.subject = subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = expDate; + d.type = 'Experiment'; + d.parent_session = latest_base.url; + d.number = expSeq; + % d.users = {AlyxInstance.username}; + + subsession = alyx.postData(AlyxInstance, 'sessions', d); + assert(isfield(subsession,'subject'),... + 'Failed to create new sub-session in Alyx for %s', subject); + url = subsession.url; +else + url = []; +end + % if the parameters had an experiment definition function, save a copy in % the experiment's folder if isfield(expParams, 'defFunction') @@ -61,5 +126,29 @@ % now save the experiment parameters variable superSave(dat.expFilePath(expRef, 'parameters'), struct('parameters', expParams)); - +if ~isempty(expParams) + try % save a copy of parameters in json + % First, change all functions to strings + f_idx = structfun(@(s)isa(s, 'function_handle'), expParams); + fields = fieldnames(expParams); + paramCell = struct2cell(expParams); + paramCell(f_idx) = cellfun(@func2str, paramCell(f_idx),'UniformOutput', false); + expParams = cell2struct(paramCell, fields); + % Generate JSON path and save + jsonPath = fullfile(fileparts(dat.expFilePath(expRef, 'parameters', 'master')),... + [expRef, '_parameters.json']); + savejson('parameters', expParams, jsonPath); + % Register our JSON parameter set to Alyx + if ~strcmp(subject, 'default') + alyx.registerFile(jsonPath, 'json', url, 'Parameters', [], AlyxInstance); + end + catch ex + warning(ex.identifier, 'Failed to save paramters as JSON: %s.\n Registering mat file instead', ex.message) + % Register our parameter set to Alyx + if ~strcmp(subject, 'default') + alyx.registerFile(dat.expFilePath(expRef, 'parameters', 'master'), 'mat',... + url, 'Parameters', [], AlyxInstance); + end + end +end end \ No newline at end of file diff --git a/+dat/parseAlyxInstance.m b/+dat/parseAlyxInstance.m index eeddb157..2eb09711 100644 --- a/+dat/parseAlyxInstance.m +++ b/+dat/parseAlyxInstance.m @@ -1,28 +1,42 @@ -function varargout = parseAlyxInstance(varargin) -%DATA.PARSEALYXINSTANCE Converts input to string for UDP message and back -% [UDP_string] = DATA.PARSEALYXINSTANCE(AlyxInstance, ref) +function [ref, AlyxInstance] = parseAlyxInstance(varargin) +%DAT.PARSEALYXINSTANCE Converts input to string for UDP message and back +% [UDP_string] = DATA.PARSEALYXINSTANCE(ref, AlyxInstance) +% [ref, AlyxInstance] = DATA.PARSEALYXINSTANCE(UDP_string) % % The pattern for 'ref' should be '{date}_{seq#}_{subject}', with two % date formats accepted, either 'yyyy-mm-dd' or 'yyyymmdd'. % % AlyxInstance should be a struct with the following fields, all -% containing strings: 'baseURL', 'token', 'username'. +% containing strings: 'baseURL', 'token', 'username'[, 'subsessionURL']. % % Part of Rigbox % 2017-10 MW created -if nargin > 1 % in [AlyxInstance, ref] - ai = varargin{1}; % extract AlyxInstance struct - ref = varargin(2); % extract expRef +if nargin > 1 % in [ref, AlyxInstance] + ref = varargin{1}; % extract expRef + ai = varargin{2}; % extract AlyxInstance struct if isstruct(ai) % if there is an AlyxInstance + ai = orderfields(ai); % alphabetize fields + % remove water requirement remaining field + if isfield(ai, 'water_requirement_remaining') + ai = rmfield(ai, 'water_requirement_remaining'); + end + fname = fieldnames(ai); % get fieldnames + emp = structfun(@isempty, ai); % find empty fields + if any(emp); ai = rmfield(ai, fname(emp)); end % remove the empty fields c = cellfun(@(fn) ai.(fn), fieldnames(ai), 'UniformOutput', false); % get fieldnames - varargout = strjoin([c; ref],'\'); % join into single string for UDP - else % otherwise just output the expRef - varargout = ref; + ref = strjoin([ref; c],'\'); % join into single string for UDP, otherwise just output the expRef end else % in [UDP_string] C = strsplit(varargin{1},'\'); % split string - varargout{1} = struct('baseURL', C{1}, 'token', C{2}, 'username', C{3}); % reconstruct AlyxInstance - varargout{2} = C{4}; % output expRef + ref = C{1}; % output expRef + if numel(C)>4 % if UDP string included AlyxInstance + AlyxInstance = struct('baseURL', C{2}, 'subsessionURL', C{3},... + 'token', C{4}, 'username', C{5}); % reconstruct AlyxInstance + elseif numel(C)>1 % if AlyxInstance has no subsession set + AlyxInstance = struct('baseURL', C{2}, 'token', C{3}, 'username', C{4}); % reconstruct AlyxInstance + else + AlyxInstance = []; % if input was just an expRef, output empty AlyxInstance + end end \ No newline at end of file diff --git a/+dat/parseExpRef.m b/+dat/parseExpRef.m index 80b16a34..32e68cb3 100644 --- a/+dat/parseExpRef.m +++ b/+dat/parseExpRef.m @@ -1,5 +1,5 @@ function [subjectRef, expDate, expSequence] = parseExpRef(ref) -%DATA.PARSEEXPREF Extracts subject, date and seq from an experiment ref +%DAT.PARSEEXPREF Extracts subject, date and seq from an experiment ref % [subject, date, seq] = DATA.PARSEEXPREF(ref) % % The pattern for 'ref' should be '{date}_{seq#}_{subject}', with two diff --git a/+dat/paths.m b/+dat/paths.m index a9bb73fe..e95284dc 100644 --- a/+dat/paths.m +++ b/+dat/paths.m @@ -18,6 +18,8 @@ % server3Name = '\\zserver3.cortexlab.net'; % 2017-02-18 MW - Currently % unused by Rigbox server4Name = '\\zserver4.cortexlab.net'; +basketName = '\\basket.cortexlab.net'; % for working analyses +lugaroName = '\\lugaro.cortexlab.net'; % for tape backup %% defaults % path containing rigbox config folders @@ -51,6 +53,18 @@ % repository for all experiment definitions p.expDefinitions = fullfile(server1Name, 'Code', 'Rigging', 'ExpDefinitions'); +% repository for working analyses that are not meant to be stored +% permanently +p.workingAnalysisRepository = fullfile(basketName, 'data'); + +% for tape backups, first files go here: +p.tapeStagingRepository = fullfile(lugaroName, 'bigdrive', 'staging'); + +% then they go here: +p.tapeArchiveRepository = fullfile(lugaroName, 'bigdrive', 'toarchive'); + + + %% load rig-specific overrides from config file, if any customPathsFile = fullfile(p.rigConfig, 'paths.mat'); if file.exists(customPathsFile) diff --git a/+dat/updateLogEntry.m b/+dat/updateLogEntry.m index d6aa3d06..fefad909 100644 --- a/+dat/updateLogEntry.m +++ b/+dat/updateLogEntry.m @@ -1,12 +1,23 @@ function updateLogEntry(subject, id, newEntry) %DAT.UPDATELOGENTRY Updates an existing experiment log entry -% DAT.UPDATELOGENTRY(subject, id, newEntry) -% TODO +% DAT.UPDATELOGENTRY(subject, id, newEntry) The server copy of the log is +% loaded and the relevant record overwritten. If an AlyxInstance is set, +% any session comments are saved in the session narrative in Alyx. +% +% See also DAT.ADDLOGENTRY % % Part of Rigbox % 2013-03 CB created +if isfield(newEntry, 'AlyxInstance')&&~isempty(newEntry.comments) + data = struct('subject', dat.parseExpRef(newEntry.value.ref),... + 'narrative', strrep(mat2DStrTo1D(newEntry.comments),newline,'\n')); + alyx.putData(newEntry.AlyxInstance,... + newEntry.AlyxInstance.subsessionURL, data); + newEntry = rmfield(newEntry, 'AlyxInstance'); +end + %load existing log from central repos log = pick(load(dat.logPath(subject, 'master')), 'log'); %find the entry with specified id @@ -17,5 +28,4 @@ function updateLogEntry(subject, id, newEntry) log(idx) = newEntry; %store new log to all repos locations superSave(dat.logPath(subject), struct('log', log)); - end \ No newline at end of file diff --git a/+eui/AlyxPanel.m b/+eui/AlyxPanel.m index 702ace45..6b9704b5 100644 --- a/+eui/AlyxPanel.m +++ b/+eui/AlyxPanel.m @@ -1,486 +1,626 @@ - -function AlyxPanel(obj, parent) -% Makes a panel with some controls to interact with Alyx in basic ways. -% Obj must be an eui.MControl object. -% -% TODO: -% - when making a session, put Headfix and Location in by default -% - test "stored weighings" functionality - - -%% -% parent = figure; % for testing - -alyxPanel = uix.Panel('Parent', parent, 'Title', 'Alyx'); -alyxbox = uiextras.VBox('Parent', alyxPanel); - -loginbox = uix.HBox('Parent', alyxbox); -loginText = bui.label('Not logged in', loginbox); -loginBtn = uicontrol('Parent', loginbox,... - 'Style', 'pushbutton', ... - 'String', 'Login', ... - 'Enable', 'on',... - 'Callback', @(src, evt)alyxLogin(obj)); -loginbox.Widths = [-1 75]; - -waterReqbox = uix.HBox('Parent', alyxbox); -waterReqText = bui.label('Log in to see water requirements', waterReqbox); -refreshBtn = uicontrol('Parent', waterReqbox,... - 'Style', 'pushbutton', ... - 'String', 'Refresh', ... - 'Enable', 'off',... - 'Callback', @(src, evt)dispWaterReq(obj)); -waterReqbox.Widths = [-1 75]; - -waterbox = uix.HBox('Parent', alyxbox); - -viewSubjectBtn = uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Subject history', ... - 'Enable', 'off',... - 'Callback', @(src, evt)viewSubjectHistory(obj)); -viewAllSubjectBtn = uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'All WR subjects', ... - 'Enable', 'off',... - 'Callback', @(src, evt)viewAllSubjects(obj)); -manualWeightBtn = uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Manual weighing', ... - 'Enable', 'off',... - 'Callback', @(src, evt)manualWeightLog(obj)); -futureGelBtn = uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Give gel in future', ... - 'Enable', 'off',... - 'Callback', @(src, evt)giveFutureGelFcn(obj)); - -% bui.label('', waterbox); % to take up empty space -isHydrogelChk = uicontrol('Parent', waterbox,... - 'Style', 'checkbox', ... - 'String', 'Hydrogel?', ... - 'HorizontalAlignment', 'right',... - 'Value', true, ... - 'Enable', 'on'); -waterAmt = uicontrol('Parent', waterbox,... - 'Style', 'edit',... - 'BackgroundColor', [1 1 1],... - 'HorizontalAlignment', 'right',... - 'Enable', 'on',... - 'String', '0.00', ... - 'Callback', @(src, evt)changeWaterText(src, evt, obj)); -giveWater = uicontrol('Parent', waterbox,... - 'Style', 'pushbutton', ... - 'String', 'Give water', ... - 'Enable', 'off',... - 'Callback', @(src, evt)giveWaterFcn(obj)); -waterLeftText = bui.label('[]', waterbox); -waterbox.Widths = [100 100 100 100 75 75 75 75]; - -launchbox = uix.HBox('Parent', alyxbox); -subjectURLbtn = uicontrol('Parent', launchbox,... - 'Style', 'pushbutton', ... - 'String', 'Launch webpage for Subject', ... - 'Enable', 'off',... - 'Callback', @(src, evt)launchSubjectURL(obj)); - -sessionURLbtn = uicontrol('Parent', launchbox,... - 'Style', 'pushbutton', ... - 'String', 'Launch webpage for Session', ... - 'Enable', 'off',... - 'Callback', @(src, evt)launchSessionURL(obj)); - - -% set a callback on MControl's subject selection so that we can show water -% requirements for new mice as they are selected - -obj.NewExpSubject.addlistener('SelectionChanged', @(~,~)dispWaterReq(obj)); - - -%% - function alyxLogin(obj) - % Are we logging in or out? - if isempty(obj.AlyxInstance) % logging in - % attempt login - [ai, username] = alyx.loginWindow(); % returns an instance if success, empty if you cancel - if ~isempty(ai) % successful - obj.AlyxInstance = ai; - obj.AlyxUsername = username; - set(loginText, 'String', sprintf('You are logged in as %s', obj.AlyxUsername)); - set(subjectURLbtn, 'Enable', 'on'); - set(sessionURLbtn, 'Enable', 'on'); - set(giveWater, 'Enable', 'on'); - set(refreshBtn, 'Enable', 'on'); - set(viewSubjectBtn, 'Enable', 'on'); - set(viewAllSubjectBtn, 'Enable', 'on'); - set(manualWeightBtn, 'Enable', 'on'); - set(futureGelBtn, 'Enable', 'on'); - - set(loginBtn, 'String', 'Logout'); - - obj.log('Logged into Alyx successfully as %s', username); - - dispWaterReq(obj); - - % try updating the subject selectors in other panels - s = alyx.getData(ai, 'subjects?stock=False&alive=True'); - - respUser = cellfun(@(x)x.responsible_user, s, 'uni', false); - subjNames = cellfun(@(x)x.nickname, s, 'uni', false); - - thisUserSubs = sort(subjNames(strcmp(respUser, obj.AlyxUsername))); - otherUserSubs = sort(subjNames); - % note that we leave this User's mice also in - % otherUserSubs, in case they get confused and look - % there. - - newSubs = {'default', thisUserSubs{:}, otherUserSubs{:}}; - oldSubs = obj.NewExpSubject.Option; - obj.NewExpSubject.Option = newSubs; - if isprop(obj, 'LogSubject') - obj.LogSubject.Option = newSubs; % these are the ones in the weighing tab - end - - % any database subjects that weren't in the old list of - % subjects will need a folder in expInfo. - firstTimeSubs = newSubs(~ismember(newSubs, oldSubs)); - for fts = 1:length(firstTimeSubs) - thisDir = fullfile(dat.reposPath('expInfo', 'master'), firstTimeSubs{fts}); - if ~exist(thisDir, 'dir') - fprintf(1, 'making expInfo directory for %s\n', firstTimeSubs{fts}); - mkdir(thisDir); - end - end - - % post any un-posted weighings - if ~isempty(obj.weighingsUnpostedToAlyx) - try - for w = 1:length(obj.weighingsUnpostedToAlyx) - d = obj.weighingsUnpostedToAlyx{w}; - wobj = alyx.postData(obj.AlyxInstance, 'weighings/', d); - obj.log('Alyx weight posting succeeded: %.2f for %s', wobj.weight, wobj.subject); - end - obj.weighingsUnpostedToAlyx = {}; - catch me - obj.log('Failed to post stored weighings') - end - end - - - else - obj.log('Did not log into Alyx'); +classdef AlyxPanel < handle + % EUI.ALYXPANEL A GUI for interating with the Alyx database + % This class is emplyed by mc (but may also be used stand-alone) to + % post weights and water administations to the Alyx database. + % + % eui.AlyxPanel() opens a stand-alone GUI. eui.AlyxPanel(parent) + % constructs the panel inside a parent object. + % + % Use the login button to retrieve a token from the database. + % Use the subject drop-down to select the subject. + % Subject weights can be entered using the 'Manual weighing' button. + % Previous weighings and water infomation can be viewed by pressing + % the 'Subject history' button. + % Water administrations can be recorded by entering a value in ml + % into the text box. Pressing return does not post the water, but + % updates the text to the right of the box, showing the amount of + % water remaining (i.e. the amount below the subject's calculated + % minimum requirement for that day. The check box to the right of + % the text box is to indicate whether the water was liquid + % (unchecked) or gel (checked). To post the water to Alyx, press the + % 'Give water' button. + % To post gel for future date (for example weekend hydrogel), Click + % the 'Give gel in future' button and enter in all the values + % starting at tomorrow then the day after, etc. + % The 'All WR subjects' button shows the amount of water remaining + % today for all mice that are currently on water restriction. + % + % The 'default' subject is for testing and is usually ignored. + % + % See also ALYX, EUI.MCONTROL + % + % 2017-03 NS created + % 2017-10 MW made into class + properties (SetAccess = private) + AlyxInstance = []; % A struct containing the database URL, a token and the username of the who is logged in + QueuedWeights = {}; % Holds weighings until someone logs in, to be posted + SubjectList % List of active subjects from database + Subject = 'default' % The name of the currently selected subject + end + + properties (Access = private) + LoggingDisplay % Control for showing log output + RootContainer % Handle of the uix.Panel object named 'Alyx' + NewExpSubject % Drop-down menu subject list + LoginText % Text displaying whether/which user is logged in + LoginButton % Button to log in to Alyx + WaterEntry % Text box for entering the amout of water to give + IsHydrogel % UI checkbox indicating whether to water to be given is in gel form + WaterRequiredText % Handle to text UI element displaying the water required + WaterRemainingText % Handle to text UI element displaying the water remaining + LoginTimer % Timer to keep track of how long the user has been logged in, when this expires the user is automatically logged out + WaterRemaining % Holds the current water required for the selected subject + end + + events (NotifyAccess = 'protected') + Connected % Notified when logged in to database + Disconnected % Notified when logged out of database + end + + methods + function obj = AlyxPanel(parent) + % Constructor to build all the UI elements and set callbacks to + % the relevant functions. If a handle to parant UI object is + % not specified, a seperate figure is created. An optional + % handle to a logging display panal may be provided, otherwise + % one is created. + + if ~nargin % No parant object: create new figure + f = figure('Name', 'alyx GUI',... + 'MenuBar', 'none',... + 'Toolbar', 'none',... + 'NumberTitle', 'off',... + 'Units', 'normalized',... + 'OuterPosition', [0.1 0.1 0.4 .4]); + parent = uiextras.VBox('Parent', f,... + 'Visible', 'on'); + % subject selector + sbox = uix.HBox('Parent', parent); + bui.label('Select subject: ', sbox); + obj.NewExpSubject = bui.Selector(sbox, {'default'}); % Subject dropdown box + % set a callback on subject selection so that we can show water + % requirements for new mice as they are selected. This should + % be set by any other GUI that instantiates this object (e.g. + % MControl using this as a panel. + obj.NewExpSubject.addlistener('SelectionChanged', @(src, evt)obj.dispWaterReq(src, evt)); end - else % logging out - obj.AlyxInstance = []; - obj.AlyxUsername = []; - set(loginText, 'String', 'Not logged in'); - set(subjectURLbtn, 'Enable', 'off'); - set(sessionURLbtn, 'Enable', 'off'); - set(giveWater, 'Enable', 'off'); - set(refreshBtn, 'Enable', 'off'); - set(loginBtn, 'String', 'Login'); - set(viewSubjectBtn, 'Enable', 'off'); - set(viewAllSubjectBtn, 'Enable', 'off'); - set(manualWeightBtn, 'Enable', 'off'); - obj.log('Logged out of Alyx'); - % return the subject selectors to their previous values - obj.NewExpSubject.Option = dat.listSubjects; + obj.RootContainer = uix.Panel('Parent', parent, 'Title', 'Alyx'); + alyxbox = uiextras.VBox('Parent', obj.RootContainer); + + loginbox = uix.HBox('Parent', alyxbox); + % Login infomation + obj.LoginText = bui.label('Not logged in', loginbox); + % Button to log in and out of Alyx + obj.LoginButton = uicontrol('Parent', loginbox,... + 'Style', 'pushbutton', ... + 'String', 'Login', ... + 'Enable', 'on',... + 'Callback', @(~,~)obj.login); + loginbox.Widths = [-1 75]; + + waterReqbox = uix.HBox('Parent', alyxbox); + obj.WaterRequiredText = bui.label('Log in to see water requirements', waterReqbox); % water required text + % Button to refresh all data retrieved from Alyx + uicontrol('Parent', waterReqbox,... + 'Style', 'pushbutton', ... + 'String', 'Refresh', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.dispWaterReq); + waterReqbox.Widths = [-1 75]; + + waterbox = uix.HBox('Parent', alyxbox); + % Button to launch a dialog displaying water and weight info for a given mouse + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Subject history', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.viewSubjectHistory); + % Button to launch a dialog displaying water and weight info for all mice + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'All WR subjects', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.viewAllSubjects); + % Button to open a dialog for manually submitting a mouse weight + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Manual weighing', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.recordWeight); + % Button to launch dialog for submitting gel administrations + % for future dates + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Give gel in future', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.giveFutureGel); + % Check box to indicate whether water was gel or liquid + obj.IsHydrogel = uicontrol('Parent', waterbox,... + 'Style', 'checkbox', ... + 'String', 'Hydrogel?', ... + 'HorizontalAlignment', 'right',... + 'Value', true, ... + 'Enable', 'off'); + % Input for submitting amount of water + obj.WaterEntry = uicontrol('Parent', waterbox,... + 'Style', 'edit',... + 'BackgroundColor', [1 1 1],... + 'HorizontalAlignment', 'right',... + 'Enable', 'off',... + 'String', '0.00', ... + 'Callback', @(src, evt)obj.changeWaterText(src, evt)); + % Button for submitting water administration + uicontrol('Parent', waterbox,... + 'Style', 'pushbutton', ... + 'String', 'Give water', ... + 'Enable', 'off',... + 'Callback', @(~,~)giveWater(obj)); + % Label Indicating the amount of water remaining + obj.WaterRemainingText = bui.label('[]', waterbox); + waterbox.Widths = [100 100 100 100 75 75 75 75]; - if isprop(obj, 'LogSubject') - obj.LogSubject.Option = obj.NewExpSubject.Option; + launchbox = uix.HBox('Parent', alyxbox); + % Button for launching subject page in browser + uicontrol('Parent', launchbox,... + 'Style', 'pushbutton', ... + 'String', 'Launch webpage for Subject', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.launchSubjectURL); + % Button for launching (and creating) a session for a given subject in the browser + uicontrol('Parent', launchbox,... + 'Style', 'pushbutton', ... + 'String', 'Launch webpage for Session', ... + 'Enable', 'off',... + 'Callback', @(~,~)obj.launchSessionURL); + + if ~nargin + % logging message area + obj.LoggingDisplay = uicontrol('Parent', parent, 'Style', 'listbox',... + 'Enable', 'inactive', 'String', {}); + parent.Sizes = [50 150 150]; + else + % Use parent's logging display + obj.LoggingDisplay = findobj('Tag', 'Logging Display'); end end - end - - function giveWaterFcn(obj) - ai = obj.AlyxInstance; - thisSubj = obj.NewExpSubject.Selected; - thisDate = now; - amount = str2double(get(waterAmt, 'String')); - isHydrogel = logical(get(isHydrogelChk, 'Value')); - if ~isempty(ai)&&amount~=0 - wa = alyx.postWater(ai, thisSubj, amount, thisDate, isHydrogel); - if ~isempty(wa) % returned us a created water administration object successfully - if isHydrogel - wstr = 'Hydrogel'; + function delete(obj) + % To be called before destroying AlyxPanel object. Deletes the + % loggin timer + disp('AlyxPanel destructor called'); + if obj.RootContainer.isvalid; delete(obj.RootContainer); end + if ~isempty(obj.LoginTimer) % If there is a timer object + stop(obj.LoginTimer) % Stop the timer... + delete(obj.LoginTimer) % ... delete it... + obj.LoginTimer = []; % ... and remove it + end + end + + function login(obj) + % Used both to log in and out of Alyx. Logging means to + % generate an Alyx token with which to send/request data. + % Logging out does not cause the token to expire, instead the + % token is simply deleted from this object. + + % Are we logging in or out? + if isempty(obj.AlyxInstance) % logging in + % attempt login + [ai, username] = alyx.loginWindow(); % returns an instance if success, empty if you cancel + if ~isempty(ai) % successful + obj.AlyxInstance = ai; + obj.AlyxInstance.username = username; + % Start log in timer, to automatically log out after 30 + % minutes of 'inactivity' (defined as not calling + % dispWaterReq) + obj.LoginTimer = timer('StartDelay', 30*60, 'TimerFcn', @(~,~)obj.login); + start(obj.LoginTimer) + % Enable all buttons + set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'on'); + set(obj.LoginText, 'String', ['You are logged in as ', username]); % display which user is logged in + set(obj.LoginButton, 'String', 'Logout'); + + % try updating the subject selectors in other panels + s = alyx.getData(ai, 'subjects?stock=False&alive=True'); + + respUser = cellfun(@(x)x.responsible_user, s, 'uni', false); + subjNames = cellfun(@(x)x.nickname, s, 'uni', false); + + thisUserSubs = sort(subjNames(strcmp(respUser, username))); + otherUserSubs = sort(subjNames); + % note that we leave this User's mice also in + % otherUserSubs, in case they get confused and look + % there. + + newSubs = [{'default'}, thisUserSubs, otherUserSubs]; + obj.NewExpSubject.Option = newSubs; + obj.SubjectList = newSubs; + + notify(obj, 'Connected'); % Notify listeners of login + obj.log('Logged into Alyx successfully as %s', username); + + % any database subjects that weren't in the old list of + % subjects will need a folder in expInfo. + firstTimeSubs = newSubs(~ismember(newSubs, dat.listSubjects)); + for fts = 1:length(firstTimeSubs) + thisDir = fullfile(dat.reposPath('expInfo', 'master'), firstTimeSubs{fts}); + if ~exist(thisDir, 'dir') + fprintf(1, 'making expInfo directory for %s\n', firstTimeSubs{fts}); + mkdir(thisDir); + end + end + + % post any un-posted weighings + if ~isempty(obj.QueuedWeights) + try + for w = 1:length(obj.QueuedWeights) + d = obj.QueuedWeights{w}; + wobj = alyx.postData(obj.AlyxInstance, 'weighings/', d); + obj.log('Alyx weight posting succeeded: %.2f for %s', wobj.weight, wobj.subject); + end + obj.QueuedWeights = {}; + catch + obj.log('Failed to post stored weighings') + end + end else - wstr = 'Water'; + obj.log('Did not log into Alyx'); + end + else % logging out + obj.AlyxInstance = []; + obj.SubjectList = []; + if ~isempty(obj.LoginTimer) % If there is a timer object + stop(obj.LoginTimer) % Stop the timer... + delete(obj.LoginTimer) % ... delete it... + obj.LoginTimer = []; % ... and remove it end - obj.log('%s administration of %.2f for %s posted successfully to alyx', wstr, amount, thisSubj); + set(obj.LoginText, 'String', 'Not logged in') + % Disable all buttons + set(findall(obj.RootContainer, '-property', 'Enable'), 'Enable', 'off') + set(obj.LoginButton, 'Enable', 'on', 'String', 'Login') % ... except the login button + notify(obj, 'Disconnected'); % Notify listeners of logout + obj.log('Logged out of Alyx'); end end - dispWaterReq(obj); - end - - - function giveFutureGelFcn(obj) - ai = obj.AlyxInstance; - thisSubj = obj.NewExpSubject.Selected; - thisDate = now; - - prompt=sprintf('Enter space-separated numbers \n[tomorrow, day after that, day after that.. etc] \nEnter 0 to skip a day'); - - answer = inputdlg(prompt,'Future Gel Amounts', [1 50]); - amount = str2num(answer{:}); - - weekendDates = thisDate + (1:length(amount)); - - if ~isempty(ai) - for d = 1:length(weekendDates) - if amount(d)>0 - wa = alyx.postWater(ai, thisSubj, amount(d), weekendDates(d), 1); - obj.log(['Hydrogel administration of %.2f for %s posted successfully to alyx for ' datestr(weekendDates(d))], amount(d), thisSubj); + function giveWater(obj) + % Callback to the give water button. Posts the value entered + % in the text box as either liquid or gel depending on the + % state of the 'is hydrogel' check box + ai = obj.AlyxInstance; + thisDate = now; + amount = str2double(get(obj.WaterEntry, 'String')); + isHydrogel = logical(get(obj.IsHydrogel, 'Value')); + if ~isempty(ai)&&amount~=0&&~isnan(amount) + wa = alyx.postWater(ai, obj.Subject, amount, thisDate, isHydrogel); + if ~isempty(wa) % returned us a created water administration object successfully + wstr = iff(isHydrogel, 'Hydrogel', 'Water'); + obj.log('%s administration of %.2f for %s posted successfully to alyx', wstr, amount, obj.Subject); end end + % update the water required text + dispWaterReq(obj); end - end - - function dispWaterReq(obj) - ai = obj.AlyxInstance; - if isempty(ai) - set(waterReqText, 'String', 'Log in to see water requirements'); - else - thisSubj = obj.NewExpSubject.Selected; - try - s = alyx.getData(ai, alyx.makeEndpoint(ai, ['subjects/' thisSubj])); % struct with data about the subject - - if s.water_requirement_total==0 - set(waterReqText, 'String', sprintf('Subject %s not on water restriction', thisSubj)); - else - set(waterReqText, 'String', ... - sprintf('Subject %s requires %.2f of %.2f today', ... - thisSubj, s.water_requirement_remaining, s.water_requirement_total)); - obj.AlyxInstance.water_requirement_remaining = s.water_requirement_remaining; - end - - catch me - d = loadjson(me.message); - if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') - set(waterReqText, 'String', sprintf('Subject %s not found in alyx', thisSubj)); + function giveFutureGel(obj) + % Open a dialog allowing one to input water submissions for + % future dates + ai = obj.AlyxInstance; + thisDate = now; + prompt=sprintf('Enter space-separated numbers \n[tomorrow, day after that, day after that.. etc] \nEnter 0 to skip a day'); + answer = inputdlg(prompt,'Future Gel Amounts', [1 50]); + if isempty(answer)||isempty(ai); return; end % user pressed 'Close' or 'x' + amount = str2num(answer{:}); %#ok + weekendDates = thisDate + (1:length(amount)); + for d = 1:length(weekendDates) + if amount(d) > 0 + alyx.postWater(ai, obj.Subject, amount(d), weekendDates(d), 1); + obj.log(['Hydrogel administration of %.2f for %s posted successfully to alyx for ' datestr(weekendDates(d))], amount(d), obj.Subject); end - - end - end - end - - function changeWaterText(src, evt, obj) - ai = obj.AlyxInstance; - if ~isempty(ai) && isfield(ai, 'water_requirement_remaining') && ~isempty(ai.water_requirement_remaining) - rem = ai.water_requirement_remaining; - curr = str2double(src.String); - set(waterLeftText, 'String', sprintf('(%.2f)', rem-curr)); - end - end - - function manualWeightLog(obj) - ai = obj.AlyxInstance; - if ~isempty(ai) - subj = obj.NewExpSubject.Selected; - prompt = {sprintf('weight of %s:', subj)}; - dlg_title = 'Manual weight logging'; - num_lines = 1; - defaultans = {'',''}; - answer = inputdlg(prompt,dlg_title,num_lines,defaultans); - - if isempty(answer) - % this happens if you click cancel - return; end - - clear d - d.subject = subj; - d.weight = str2double(answer); - d.user = ai.username; - - try - w = alyx.postData(ai, 'weighings/', d); - obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); - catch - obj.log('Warning: Alyx weight posting failed!'); - end - - end - end - - function launchSessionURL(obj) - ai = obj.AlyxInstance; - thisSubj = obj.NewExpSubject.Selected; - thisDate = alyx.datestr(now); - % determine whether there is a session for this subj and date - ss = alyx.getData(ai, ['sessions?subject=' thisSubj '&start_date=' datestr(now, 'yyyy-mm-dd')]); - - % if not, create one - if isempty(ss) - clear d - d.subject = thisSubj; - d.start_time = alyx.datestr(now); - d.users = {obj.AlyxUsername}; + function dispWaterReq(obj, src, ~) + % Display the amount of water required by the selected subject + % for it to reach its minimum requirement. This function is + % also used to update the selected subject, for example it is + % this funtion to use as a callback to subject dropdown + % listeners + ai = obj.AlyxInstance; + % Set the selected subject if it is an input + if nargin>1; obj.Subject = src.Selected; end + if isempty(ai) + set(obj.WaterRequiredText, 'String', 'Log in to see water requirements'); + return + end + % Refresh the timer as the user isn't inactive + stop(obj.LoginTimer); start(obj.LoginTimer) try - thisSess = alyx.postData(ai, 'sessions', d); - obj.log('New session created for %s', thisSubj); + s = alyx.getData(ai, alyx.makeEndpoint(ai, ['subjects/' obj.Subject])); % struct with data about the subject + if s.water_requirement_total==0 + set(obj.WaterRequiredText, 'String', sprintf('Subject %s not on water restriction', obj.Subject)); + else + set(obj.WaterRequiredText, 'String', ... + sprintf('Subject %s requires %.2f of %.2f today', ... + obj.Subject, s.water_requirement_remaining, s.water_requirement_total)); + obj.WaterRemaining = s.water_requirement_remaining; + end catch me - obj.log('Could not create new session - cannot launch page'); - return + d = loadjson(me.message); + if isfield(d, 'detail') && strcmp(d.detail, 'Not found.') + set(obj.WaterRequiredText, 'String', sprintf('Subject %s not found in alyx', obj.Subject)); + end end - - else - thisSess = ss{1}; end - % parse the uuid from the url in the session object - u = thisSess.url; - uuid = u(find(u=='/', 1, 'last')+1:end); - - % make the admin url - adminURL = fullfile(ai.baseURL, 'admin', 'actions', 'session', uuid, 'change'); + function changeWaterText(obj, src, ~) + % Update the panel text to show the amount of water still + % required for the subject to reach its minimum requirement. + % This text is updated before the value in the water text box + % has been posted to Alyx. For example if the user is unsure + % how much gel over the minimum they have weighed out, pressing + % return will display this without posting to Alyx + % + % See also DISPWATERREQ, GIVEWATER + ai = obj.AlyxInstance; + if ~isempty(ai) && ~isempty(obj.WaterRemaining) + rem = obj.WaterRemaining; + curr = str2double(src.String); + set(obj.WaterRemainingText, 'String', sprintf('(%.2f)', rem-curr)); + end + end - % launch the website - web(adminURL, '-browser'); - end - - function launchSubjectURL(obj) - ai = obj.AlyxInstance; - if ~isempty(ai) - thisSubj = obj.NewExpSubject.Selected; - s = alyx.getData(ai, alyx.makeEndpoint(ai, ['subjects/' thisSubj])); - subjURL = fullfile(ai.baseURL, 'admin', 'subjects', 'subject', s.id, 'change'); % this is wrong - need uuid - web(subjURL, '-browser'); + function recordWeight(obj, weight, subject) + % Post a subject's weight to Alyx. If no inputs are provided, + % create an input dialog for the user to input a weight. If no + % subject is provided, use this object's currently selected + % subject. + % + % See also VIEWSUBJECTHISTORY, VIEWALLSUBJECTS + ai = obj.AlyxInstance; + if nargin < 3; subject = obj.Subject; end + if nargin < 2 + prompt = {sprintf('weight of %s:', subject)}; + dlgTitle = 'Manual weight logging'; + numLines = 1; + defaultAns = {'',''}; + weight = inputdlg(prompt, dlgTitle, numLines, defaultAns); + if isempty(weight); return; end + end + % inputdlg returns weight as a cell, otherwise it may now be + weight = ensureCell(weight); % ensure it's a cell + % convert to double if weight is a string + weight = iff(ischar(weight{1}), str2double(weight{1}), weight{1}); + d.subject = subject; + d.weight = weight; + if isempty(ai) % if not logged in, save the weight for later + obj.QueuedWeights{end+1} = d; + obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); + else % otherwise immediately post to Alyx + d.user = ai.username; + try + w = alyx.postData(ai, 'weighings/', d); + obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); + catch + obj.log('Warning: Alyx weight posting failed!'); + end + end end - end - - function viewSubjectHistory(obj) - ai = obj.AlyxInstance; - if ~isempty(ai) - % collect the data for the table - thisSubj = obj.NewExpSubject.Selected; - endpnt = sprintf('water-requirement/%s?start_date=2016-01-01&end_date=%s', thisSubj, datestr(now, 'yyyy-mm-dd')); - wr = alyx.getData(ai, endpnt); + function launchSessionURL(obj) + % Launch the Webpage for the current base session in the + % default Web browser. If no session exists for today's date, + % a new base session is created accordingly. + % + % See also LAUNCHSUBJECTURL + ai = obj.AlyxInstance; + % determine whether there is a session for this subj and date + thisDate = alyx.datestr(now); + sessions = alyx.getData(ai, ['sessions?type=Base&subject=' obj.Subject]); - hydrogelAmts = cellfun(@(x)x.hydrogel_given, wr.records); - waterAmts = cellfun(@(x)x.water_given, wr.records); - waterExp = cellfun(@(x)x.water_expected, wr.records); - hasWeighing = cellfun(@(x)isfield(x, 'weight_measured'), wr.records); - weight = cellfun(@(x)x.weight_measured, wr.records(hasWeighing)); - weightExp = cellfun(@(x)x.weight_expected, wr.records); - dates = cellfun(@(x)datenum(x.date), wr.records); - - % build the figure to show it - f = figure('Name', thisSubj, 'NumberTitle', 'off'); % popup a new figure for this - p = get(f, 'Position'); - set(f, 'Position', [p(1) p(2) 1100 p(4)]); - histbox = uix.HBox('Parent', f, 'BackgroundColor', 'w'); - - plotBox = uix.VBox('Parent', histbox, 'BackgroundColor', 'w'); - - ax = axes('Parent', plotBox); - plot(dates(hasWeighing), weight, '.-'); - hold on; - plot(dates, weightExp*0.7, 'r', 'LineWidth', 2.0); - plot(dates, weightExp*0.8, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); - box off; - xlim([min(dates) max(dates)]); - set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) - ylabel('weight (g)'); - - - ax = axes('Parent', plotBox); - plot(dates(hasWeighing), weight./weightExp(hasWeighing), '.-'); - hold on; - plot(dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); - plot(dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); - box off; - xlim([min(dates) max(dates)]); - set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) - ylabel('weight as pct (%)'); + % If the date of this latest base session is not the same date + % as today, then create a new one for today + if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), thisDate(1:10)) + % Ask user whether he/she wants to create new session + % Construct a questdlg with three options + choice = questdlg('Would you like to create a new base session?', ... + ['No base session exists for ' datestr(now, 'yyyy-mm-dd')], ... + 'Yes','No','No'); + % Handle response + switch choice + case 'Yes' + % Create our base session + d = struct; + d.subject = obj.Subject; + d.procedures = {'Behavior training/tasks'}; + d.narrative = 'auto-generated session'; + d.start_time = thisDate; + d.type = 'Base'; + + thisSess = alyx.postData(ai, 'sessions', d); + if ~isfield(thisSess,'subject') % fail + warning('Submitted base session did not return appropriate values'); + warning('Submitted data below:'); + disp(d) + warning('Return values below:'); + disp(thisSess) + return + else % success + obj.log(['Created new base session in Alyx for ' obj.Subject]); + end + case 'No' + return + end + else + thisSess = sessions{end}; + end - axWater = axes('Parent',plotBox); - plot(dates, waterAmts+hydrogelAmts, '.-'); - hold on; - plot(dates, hydrogelAmts, '.-'); - plot(dates, waterAmts, '.-'); - plot(dates, waterExp, 'r', 'LineWidth', 2.0); - box off; - xlim([min(dates) max(dates)]); - set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) - ylabel('water/hydrogel (mL)'); + % parse the uuid from the url in the session object + u = thisSess.url; + uuid = u(find(u=='/', 1, 'last')+1:end); + % make the admin url + adminURL = fullfile(ai.baseURL, 'admin', 'actions', 'session', uuid, 'change'); - histTable = uitable('Parent', histbox,... - 'FontName', 'Consolas',... - 'RowName', []); + % launch the website + web(adminURL, '-browser'); + end - weightsByDate = cell([length(dates),1]); - weightsByDate(hasWeighing) = num2cell(weight); - weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); - weightPctByDate = cell([length(dates),1]); - weightPctByDate(hasWeighing) = num2cell(weight./weightExp(hasWeighing)); - weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); - - dat = horzcat(... - arrayfun(@(x)datestr(x), dates', 'uni', false), ... - weightsByDate, ... - arrayfun(@(x)sprintf('%.1f', 0.8*x), weightExp', 'uni', false), ... - weightPctByDate); - waterDat = (... - num2cell(horzcat(waterAmts', hydrogelAmts', ... - waterAmts'+hydrogelAmts', waterExp', waterAmts'+hydrogelAmts'-waterExp'))); - waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); - dat = horzcat(dat, waterDat); - - set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... - 'Data', dat(end:-1:1,:),... - 'ColumnEditable', false(1,5)); - histbox.Widths = [ -1 725]; + function launchSubjectURL(obj) + ai = obj.AlyxInstance; + if ~isempty(ai) + s = alyx.getData(ai, alyx.makeEndpoint(ai, ['subjects/' obj.Subject])); + subjURL = fullfile(ai.baseURL, 'admin', 'subjects', 'subject', s.id, 'change'); % this is wrong - need uuid + web(subjURL, '-browser'); + end end - end - - function val = hydrogelVal(x) - if isempty(x) - val = false; - else - val = logical(x); + + function viewSubjectHistory(obj, ax) + % View historical information about a subject. + % Opens a new window and plots a set of weight graphs as well + % as displaying a table with the water and weight entries for + % the selected subject. If an axes handle is provided, this + % function plots a single weight graph + ai = obj.AlyxInstance; + % If not logged in or 'default' is selected, return + if isempty(ai)||strcmp(obj.Subject, 'default'); return; end + % collect the data for the table + endpnt = sprintf('water-requirement/%s?start_date=2016-01-01&end_date=%s', obj.Subject, datestr(now, 'yyyy-mm-dd')); + wr = alyx.getData(ai, endpnt); + records = catStructs(wr.records, nan); + % no weighings found + if isempty(wr.records) + obj.log('No weight data found for subject %s', obj.Subject); + return + end + dates = cellfun(@(x)datenum(x), {records.date}); + + % build the figure to show it + if nargin==1 + f = figure('Name', obj.Subject, 'NumberTitle', 'off'); % popup a new figure for this + p = get(f, 'Position'); + set(f, 'Position', [p(1) p(2) 1100 p(4)]); + histbox = uix.HBox('Parent', f, 'BackgroundColor', 'w'); + plotBox = uix.VBox('Parent', histbox, 'BackgroundColor', 'w'); + ax = axes('Parent', plotBox); + end + + plot(ax, dates, [records.weight_measured], '.-'); + hold(ax, 'on'); + plot(ax, dates, [records.weight_expected]*0.7, 'r', 'LineWidth', 2.0); + plot(ax, dates, [records.weight_expected]*0.8, 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); + box(ax, 'off'); + if numel(dates) > 1; xlim(ax, [min(dates) max(dates)]); end + if nargin == 1 + set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) + else + ax.XTickLabel = arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false); + end + ylabel(ax, 'weight (g)'); + + if nargin==1 + ax = axes('Parent', plotBox); + plot(ax, dates, [records.weight_measured]./[records.weight_expected], '.-'); + hold(ax, 'on'); + plot(ax, dates, 0.7*ones(size(dates)), 'r', 'LineWidth', 2.0); + plot(ax, dates, 0.8*ones(size(dates)), 'LineWidth', 2.0, 'Color', [244, 191, 66]/255); + box(ax, 'off'); + xlim(ax, [min(dates) max(dates)]); + set(ax, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(ax, 'XTick'), 'uni', false)) + ylabel(ax, 'weight as pct (%)'); + + axWater = axes('Parent',plotBox); + plot(axWater, dates, [records.water_given]+[records.hydrogel_given], '.-'); + hold(axWater, 'on'); + plot(axWater, dates, [records.hydrogel_given], '.-'); + plot(axWater, dates, [records.water_given], '.-'); + plot(axWater, dates, [records.water_expected], 'r', 'LineWidth', 2.0); + box(axWater, 'off'); + xlim(axWater, [min(dates) max(dates)]); + set(axWater, 'XTickLabel', arrayfun(@(x)datestr(x, 'dd-mmm'), get(axWater, 'XTick'), 'uni', false)) + ylabel(axWater, 'water/hydrogel (mL)'); + + % Create table of useful weight and water information, + % sorted by date + histTable = uitable('Parent', histbox,... + 'FontName', 'Consolas',... + 'RowName', []); + weightsByDate = num2cell([records.weight_measured]); + weightsByDate = cellfun(@(x)sprintf('%.1f', x), weightsByDate, 'uni', false); + weightsByDate(isnan([records.weight_measured])) = {[]}; + weightPctByDate = num2cell([records.weight_measured]./[records.weight_expected]); + weightPctByDate = cellfun(@(x)sprintf('%.1f', x*100), weightPctByDate, 'uni', false); + weightPctByDate(isnan([records.weight_measured])) = {[]}; + + dat = horzcat(... + arrayfun(@(x)datestr(x), dates', 'uni', false), ... + weightsByDate', ... + arrayfun(@(x)sprintf('%.1f', 0.8*x), [records.weight_expected]', 'uni', false), ... + weightPctByDate'); + waterDat = (... + num2cell(horzcat([records.water_given]', [records.hydrogel_given]', ... + [records.water_given]'+[records.hydrogel_given]', [records.water_expected]',... + [records.water_given]'+[records.hydrogel_given]'-[records.water_expected]'))); + waterDat = cellfun(@(x)sprintf('%.2f', x), waterDat, 'uni', false); + dat = horzcat(dat, waterDat); + + set(histTable, 'ColumnName', {'date', 'meas. weight', '80% weight', 'weight pct', 'water', 'hydrogel', 'total', 'min water', 'excess'}, ... + 'Data', dat(end:-1:1,:),... + 'ColumnEditable', false(1,5)); + histbox.Widths = [ -1 725]; + end end - end - - function viewAllSubjects(obj) - ai = obj.AlyxInstance; - if ~isempty(ai) - - wr = alyx.getData(ai, alyx.makeEndpoint(ai, 'water-restricted-subjects')); - - subjs = cellfun(@(x)x.nickname, wr, 'uni', false); - waterReqTotal = cellfun(@(x)x.water_requirement_total, wr, 'uni', false); - waterReqRemain = cellfun(@(x)x.water_requirement_remaining, wr, 'uni', false); - - % build a figure to show it - f = figure; % popup a new figure for this - wrBox = uix.VBox('Parent', f); - wrTable = uitable('Parent', wrBox,... - 'FontName', 'Consolas',... - 'RowName', []); - htmlColor = @(colorNum)reshape(dec2hex(round(colorNum'*255),2)',1,6); -% colorgen = @(colorNum,text) ['
',text,'
']; - colorgen = @(colorNum,text) ['',text,'']; + function viewAllSubjects(obj) + ai = obj.AlyxInstance; + if ~isempty(ai) + + wr = alyx.getData(ai, alyx.makeEndpoint(ai, 'water-restricted-subjects')); + + subjs = cellfun(@(x)x.nickname, wr, 'uni', false); + waterReqTotal = cellfun(@(x)x.water_requirement_total, wr, 'uni', false); + waterReqRemain = cellfun(@(x)x.water_requirement_remaining, wr, 'uni', false); + + % build a figure to show it + f = figure; % popup a new figure for this + wrBox = uix.VBox('Parent', f); + wrTable = uitable('Parent', wrBox,... + 'FontName', 'Consolas',... + 'RowName', []); + + htmlColor = @(colorNum)reshape(dec2hex(round(colorNum'*255),2)',1,6); + % colorgen = @(colorNum,text) ['
',text,'
']; + colorgen = @(colorNum,text) ['',text,'']; + + wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3], sprintf('%.2f',x)), waterReqRemain, 'uni', false); + + set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... + 'Data', horzcat(subjs', ... + cellfun(@(x)sprintf('%.2f',x),waterReqTotal', 'uni', false), ... + wrdat'), ... + 'ColumnEditable', false(1,3)); + end + end - wrdat = cellfun(@(x)colorgen(1-double(x>0)*[0 0.3 0.3], sprintf('%.2f',x)), waterReqRemain, 'uni', false); - - set(wrTable, 'ColumnName', {'Name', 'Water Required', 'Remaining Requirement'}, ... - 'Data', horzcat(subjs', ... - cellfun(@(x)sprintf('%.2f',x),waterReqTotal', 'uni', false), ... - wrdat'), ... - 'ColumnEditable', false(1,3)); - - + function log(obj, varargin) + % Function for displaying timestamped information about + % occurrences. If the LoggingDisplay property is unset, the + % message is printed to the command prompt. + % log(formatSpec, A1,... An) + % + % See also FPRINTF + message = sprintf(varargin{:}); + if ~isempty(obj.LoggingDisplay) + timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); + str = sprintf('[%s] %s', timestamp, message); + current = get(obj.LoggingDisplay, 'String'); + %NB: If more that one instance of MATLAB is open, we use + %the last opened LoggingDisplay + set(obj.LoggingDisplay(end), 'String', [current; str], 'Value', numel(current) + 1); + else + fprintf(message) + end end end - - + end \ No newline at end of file diff --git a/+eui/ExpPanel.m b/+eui/ExpPanel.m index 221bfd08..5ccb95b6 100644 --- a/+eui/ExpPanel.m +++ b/+eui/ExpPanel.m @@ -1,56 +1,71 @@ classdef ExpPanel < handle %EUI.EXPPANEL Basic UI control for monitoring an experiment - % TODO + % The EXPPANEL superclass is instantiated by MCONTROL when an + % experiment is started through MC. The object adds listeners for + % updates broadcast by the rig (defined in the _rig_ object) and + % updates plots and text as the experiment progresses. Additionally + % there are buttons to end or abort the experiment and to view the + % experimental parameters (the _paramStruct_). + % EXPPANEL is not stand-alone and thus requires a handle to a parent + % window. This class has a number of subclasses, one for each + % experiment type, for example CHOICEEXPPANEL for ChoiceWorld and + % SQUEAKEXPPANEL for Signals experiments. + % + % + % + % See also SQUEAKEXPPANEL, CHOICEEXPPANEL, MCONTROL, MC % % Part of Rigbox % 2013-06 CB created + % 2017-05 MW added Alyx compatibility properties - Block = struct('numCompletedTrials', 0, 'trial', struct([])) + Block = struct('numCompletedTrials', 0, 'trial', struct([])) % A structure to hold update information relevant for the plotting of psychometics and performance calculations %log entry pertaining to this experiment LogEntry Listeners - Alyx end properties (Access = protected) - ExpRunning = false + ExpRunning = false % A flag indicating whether the experiment is still running ActivePhases = {} Root - Ref - SubjectRef - InfoGrid + Ref % The experimental reference (expRef). + SubjectRef % A string representing the subject's name + InfoGrid % Handle to the UIX.GRID UI object that holds contains the InfoFields and InfoLabels InfoLabels %label text controls for each info field InfoFields %field controls for each info field - StatusLabel - TrialCountLabel + StatusLabel % A text field displaying the status of the experiment, i.e. the current phase of the experiment + TrialCountLabel % A counter displaying the current trial number ConditionLabel DurationLabel - StopButtons + StopButtons % Handles to the End and Abort buttons, used to terminate an experiment through the UI StartedDateTime % ElapsedTimer - CloseButton - CommentsBox - CustomPanel - MainVBox - Parameters + CloseButton % The little x at the top right of the panel. Deletes the object. + CommentsBox % A handle to text box. Text inputed to this box is saved in the subject's LogEntry + CustomPanel % Handle to a UI box where any number of platting axes my be placed by subclasses + MainVBox % Handle to the main box containing all labels, buttons and UI boxes for this panel + Parameters % A structure of experimental parameters used by this experiment end methods (Static) - function p = live(parent, ref, remoteRig, paramsStruct, Alyx) + function p = live(parent, ref, remoteRig, paramsStruct) subject = dat.parseExpRef(ref); % Extract subject, date and seq from experiment ref try logEntry = dat.addLogEntry(... % Add new entry to log - subject, now, 'experiment-info', struct('ref', ref), ''); + subject, now, 'experiment-info', struct('ref', ref), '', remoteRig.AlyxInstance); catch ex logEntry.comments = ''; warning(ex.getReport()); end params = exp.Parameters(paramsStruct); % Get parameters - if isfield(params.Struct, 'expPanelFun') % Can define your own experiment panel - p = params.Struct.expPanelFun(parent, ref, params, logEntry); - else + % Can define your own experiment panel + if isfield(params.Struct, 'expPanelFun')&&~isempty(params.Struct.expPanelFun) + if isempty(which(params.Struct.expPanelFun)); addpath(fileparts(params.Struct.defFunction)); end + p = feval(params.Struct.expPanelFun, parent, ref, params, logEntry); + else % otherwise use the default switch params.Struct.type case {'SingleTargetChoiceWorld' 'ChoiceWorld' 'DiscWorld' 'SurroundChoiceWorld'} p = eui.ChoiceExpPanel(parent, ref, params, logEntry); @@ -69,21 +84,19 @@ set(p.StopButtons(1), 'Callback',... @(src, ~) fun.run(true,... - @() remoteRig.quitExperiment(false, Alyx),... + @() remoteRig.quitExperiment(false),... @() set(src, 'Enable', 'off'))); set(p.StopButtons(2), 'Callback',... @(src, ~) fun.run(true,... - @() remoteRig.quitExperiment(true, Alyx),... + @() remoteRig.quitExperiment(true),... @() set(p.StopButtons, 'Enable', 'off'))); - p.Alyx = Alyx; p.Root.Title = sprintf('%s on ''%s''', p.Ref, remoteRig.Name); % Set experiment panel title p.Listeners = [... ...event.listener(remoteRig, 'Connected', @p.expStarted) ...event.listener(remoteRig, 'Disconnected', @p.expStopped) event.listener(remoteRig, 'ExpStarted', @p.expStarted) event.listener(remoteRig, 'ExpStopped', @p.expStopped) - event.listener(remoteRig, 'ExpUpdate', @p.expUpdate); - event.listener(remoteRig, 'AlyxRequest', @p.AlyxRequest)]; % request from expServer for Alyx Instance + event.listener(remoteRig, 'ExpUpdate', @p.expUpdate)]; % p.ElapsedTimer = timer('Period', 0.9, 'ExecutionMode', 'fixedSpacing',... % 'TimerFcn', @(~,~) set(p.DurationLabel, 'String',... % sprintf('%i:%02.0f', floor(p.elapsed/60), mod(p.elapsed, 60)))); @@ -174,6 +187,11 @@ function event(obj, name, t) end function expStarted(obj, rig, evt) + % EXPSTARTED Callback for the ExpStarted event. + % Updates the ExpRunning flag, the panel title and status label to + % show that the experiment has officially begun. + % + % See also EXPSTOPPED if strcmp(evt.Ref, obj.Ref) set(obj.StatusLabel, 'String', 'Running'); %staus to running set(obj.StopButtons, 'Enable', 'on', 'Visible', 'on'); %enable stop buttons @@ -190,6 +208,13 @@ function expStarted(obj, rig, evt) end function expStopped(obj, rig, evt) + % EXPSTOPPED Callback for the ExpStopped event. + % Updates the ExpRunning flag, the panel title and status label to + % show that the experiment has ended. This function also records to Alyx the + % amount of water, if any, that the subject received during the + % task. + % + % See also EXPSTARTED, ALYX.POSTWATER set(obj.StatusLabel, 'String', 'Completed'); %staus to completed obj.ExpRunning = false; set(obj.StopButtons, 'Enable', 'off'); %disable stop buttons @@ -197,15 +222,10 @@ function expStopped(obj, rig, evt) obj.Listeners = []; obj.Root.TitleColor = [1 0.3 0.22]; % red title area %post water to Alyx - ai = obj.Alyx.AlyxInstance; + ai = rig.AlyxInstance; subject = obj.SubjectRef; if ~isempty(ai)&&~strcmp(subject,'default') switch class(obj) - case 'eui.SqueakExpPanel' - infoFields = {obj.InfoFields.String}; - inc = cellfun(@(x) any(strfind(x(:)','µl')), {obj.InfoFields.String}); % Find event values ending with 'ul'. - reward = cell2mat(cellfun(@str2num,strsplit(infoFields{find(inc,1)},'µl'),'UniformOutput',0)); - amount = iff(isempty(reward),0,@()reward); case 'eui.ChoiceExpPanel' if ~isfield(obj.Block.trial,'feedbackType'); return; end % No completed trials if any(strcmp(obj.Parameters.TrialSpecificNames,'rewardVolume')) % Reward is trial specific @@ -218,16 +238,18 @@ function expStopped(obj, rig, evt) end if numel(amount)>1; amount = amount(1); end % Take first element (second being laser) otherwise - return + infoFields = {obj.InfoFields.String}; + inc = cellfun(@(x) any(strfind(x(:)','µl')), {obj.InfoFields.String}); % Find event values ending with 'ul'. + reward = cell2mat(cellfun(@str2num,strsplit(infoFields{find(inc,1)},'µl'),'UniformOutput',0)); + amount = iff(isempty(reward),0,@()reward); end if ~any(amount); return; end % Return if no water was given try alyx.postWater(ai, subject, amount*0.001, now, false); catch - log('Water administration of %.2f for %s failed to post to alyx', amount, subject); + warning('Failed to post the water %s recieved during the experiment to Alyx', amount*0.001, subject); end - end - + end end function expUpdate(obj, rig, evt) @@ -253,15 +275,15 @@ function expUpdate(obj, rig, evt) obj.event(evt.Data{2}, evt.Data{3}); end end - - function AlyxRequest(obj, rig, evt) - ref = evt.Data; % expRef - try rig.setAlyxInstance(obj.Alyx.AlyxInstance); % StimulusControl obj - catch %log(['Failed to submit Alyx token for' ref ' to ' rig.Name]); - end - end - + function mergeTrialData(obj, idx, data) + % MERGETRIALDATA Update the local block structure with data from the + % last trial + % This is only used by CHOICEEXPPANEL, etc. where trial data we + % constant and had a predefined structure. This is not used by the + % SQEUEAKEXPPANEL sub-class. + % + % See also EXPUPDATE fields = fieldnames(data); for i = 1:numel(fields) f = fields{i}; @@ -270,10 +292,22 @@ function mergeTrialData(obj, idx, data) end function saveLogEntry(obj) + % SAVELOGENTRY Saves the obj.LogEntry to disk and to Alyx + % As the log entry has been updated throughout the experiment with + % comments and experiment end times, it must be saved to disk. In + % addition if an Alyx Instance is set, the comments are saved to the + % subsession's narrative field. + % + % See also DAT.UPDATELOGENTRY, COMMENTSCHANGED dat.updateLogEntry(obj.SubjectRef, obj.LogEntry.id, obj.LogEntry); end function viewParams(obj) + % VIEWPARAMS The callback for the Parameters button. + % Creates a new figure to display the current experimental + % parameters (the sructure in obj.Parameters). + % + % See also EUI.PARAMEDITOR f = figure('Name', sprintf('%s Parameters', obj.Ref),... 'MenuBar', 'none',... 'Toolbar', 'none',... @@ -282,7 +316,7 @@ function viewParams(obj) % 'OuterPosition', [0.1 0.2 0.8 0.7]); params = obj.Parameters; editor = eui.ParamEditor(params, f); - editor.Enable = 'off'; + editor.Enable = 'off'; % The parameter field should not be editable as the experiment has already started end function [fieldCtrl] = addInfoField(obj, label, field) @@ -299,7 +333,12 @@ function viewParams(obj) obj.MainVBox.Sizes(1) = FieldHeight*nRows; end - function commentsChanged(obj, src, evt) + function commentsChanged(obj, src, ~) + % COMMENTSCHANGED Callback for saving comments to server and Alyx + % This function is called when text in the comments box is changed + % and reports this in the command window + % + % See also SAVELOGENTRY, LIVE disp('saving comments'); obj.LogEntry.comments = get(src, 'String'); obj.saveLogEntry(); diff --git a/+eui/Log.m b/+eui/Log.m index 3172fa76..2821326f 100644 --- a/+eui/Log.m +++ b/+eui/Log.m @@ -110,50 +110,6 @@ function buildUI(obj, parent) 'RowName', [],... 'ColumnWidth', obj.columnWidths,... 'CellSelectionCallback', @obj.cellSelected); - -% obj.Table = uitable('Style', 'popupmenu', 'Enable', 'on',... -% 'String', {''},... -% 'Callback', @(src, evt) obj.showStack(get(src, 'Value')),... -% 'Parent', vbox); -% -% % set up the axes for displaying current frame image -% obj.Axes = bui.Axes(vbox); -% obj.Axes.ActivePositionProperty = 'Position'; -% obj.Image = imagesc(0, 'Parent', obj.Axes.Handle); -% obj.Axes.XTickLabel = []; -% obj.Axes.YTickLabel = []; -% obj.Axes.DataAspectRatio = [1 1 1]; -% -% % configure handling mouse events over axes to update selector cursor -% obj.Axes.addlistener('MouseLeft',... -% @(src, evt) handleMouseLeft(obj)); -% obj.Axes.addlistener('MouseMoved', @(src, evt) handleMouseMovement(obj, evt)); -% obj.Axes.addlistener('MouseButtonDown', @(src, evt) handleMouseDown(obj, evt)); -% obj.Axes.addlistener('MouseDragged', @(src, evt) handleMouseDragged(obj, evt)); -% -% bottombox = uiextras.HBox('Parent', vbox, 'Padding', 1); -% -% obj.PlayButton = uicontrol('String', '|>',... -% 'Callback', @(src, evt) obj.playStack(),... -% 'Parent', topbox); -% obj.StopButton = uicontrol('String', '||',... -% 'Callback', @(src, evt) obj.stopStack(),... -% 'Enable', 'off',... -% 'Parent', topbox); -% obj.SpeedMenu = uicontrol('Style', 'popupmenu', 'Enable', 'on',... -% 'String', {'', '', '', '', ''},... -% 'Value', find(obj.PlaySpeed == 1, 1),... -% 'Parent', topbox,... -% 'Callback', @(s,e) obj.updatePlayStep()); -% -% obj.FrameSlider = uicontrol('Style', 'slider', 'Enable', 'off',... -% 'Parent', bottombox,... -% 'Callback', @(src, ~) obj.showFrame(get(src, 'Value'))); -% obj.StatusText = uicontrol('Style', 'edit', 'String', '', ..., -% 'Enable', 'inactive', 'Parent', bottombox); -% set(vbox, 'Sizes', [24 -1 24]); -% set(topbox, 'Sizes', [-1 24 24 58]); -% set(bottombox, 'Sizes', [-1 160]); end end diff --git a/+eui/MControl.m b/+eui/MControl.m index 25cb75b0..0f09f9ff 100644 --- a/+eui/MControl.m +++ b/+eui/MControl.m @@ -6,7 +6,11 @@ % - improve it. % - ensure all Parent objects specified explicitly (See GUI Layout % Toolbox 2.3.1/layoutdoc/Getting_Started2.html) - % See also MC. + % - Do PrePostExpDelayEdits still store handles now it's moved to new + % dialog? + % - Tidy Options dialog + % - Comment rigOptions function + % See also MC, EUI.ALYXPANEL, EUI.EXPPANEL, EUI.LOG, EUI.PARAMEDITOR % % Part of Rigbox @@ -16,39 +20,38 @@ % specific expDef that was selected properties - AlyxInstance = []; - AlyxUsername = []; - weighingsUnpostedToAlyx = {}; % holds weighings until someone logs in, to be posted + LoggingDisplay % control for showing log output end properties (SetAccess = private) - LogSubject %subject selector control - NewExpSubject - NewExpType - WeighingScale - Log %log control - RemoteRigs - TabPanel - LastExpPanel + LogSubject % Subject selector control + NewExpSubject % Experiment selector control + NewExpType % Experiment type selector control + WeighingScale % HW.WEIGHINGSCALE object for interacting with a balance + Log % Handle to the UI element containing the Log tabs + RemoteRigs % An array of SRV.STIMULUSCONTROL objects with connection information for each romote rig + TabPanel % Handle to the UI element containing the Log and Experiment tabs + LastExpPanel % Handle to the most recently instantiated EUI.EXPPANEL object end properties (Access = private) - LoggingDisplay %control for showing log output ParamEditor ParamPanel - BeginExpButton - NewExpFactory + AlyxPanel % holds the AlyxPanel object (see buildUI(), eui.AlyxPanel()) + BeginExpButton % The 'Start' button that begins an experiment + RigOptionsButton % The 'Options' button that opens the rig options dialog + NewExpFactory % A struct containing all availiable experiment types and function handles to constructors for their default parameters RootContainer - Parameters - WeightAxes + Parameters % A structure containing the currently selected set of parameters + WeightAxes % Handle to the BUI.AXES object that holds the axes for the weight plot in the Log tab WeightReadingPlot NewExpParamProfile LogTabs ExpTabs ActiveExpsGrid Listeners - %handles to pre (i=1) and post (i=2) experiment delay edit text controls - PrePostExpDelayEdits + PrePostExpDelayEdits % Handles to pre (i=1) and post (i=2) experiment delay edit text cotrols + Services % Cell array of selected services RecordWeightButton ParamProfileLabel RefreshTimer @@ -85,6 +88,8 @@ obj.RefreshTimer = timer('Period', 0.1, 'ExecutionMode', 'fixedSpacing',... 'TimerFcn', @(~,~)notify(obj, 'Refresh')); start(obj.RefreshTimer); + addlistener(obj.AlyxPanel, 'Connected', @obj.expSubjectChanged); + addlistener(obj.AlyxPanel, 'Disconnected', @obj.expSubjectChanged); try if isfield(rig, 'scale') && ~isempty(rig.scale) obj.WeighingScale = fieldOrDefault(rig, 'scale'); @@ -92,7 +97,7 @@ obj.Listeners = [obj.Listeners,... {event.listener(obj.WeighingScale, 'NewReading', @obj.newScalesReading)}]; end - catch ex + catch obj.log('Warning: could not connect to weighing scales'); end @@ -105,19 +110,50 @@ function delete(obj) delete(obj.RootContainer); end end -% end -% -% methods (Access = protected) % test by NS - remove protection of these -% methods, so that alyxPanel can access them... not sure what is the -% tradeoff/danger + end + + methods (Access = protected) function newScalesReading(obj, ~, ~) if obj.TabPanel.SelectedChild == 1 && obj.LogTabs.SelectedChild == 2 obj.plotWeightReading(); %refresh weighing scale reading end end - function expSubjectChanged(obj) - obj.expTypeChanged(); + function tabChanged(obj) + % Function to change which subject Alyx uses when user changes tab + if isempty(obj.AlyxPanel.AlyxInstance); return; end + if obj.TabPanel.SelectedChild == 1 % Log tab + obj.AlyxPanel.dispWaterReq(obj.LogSubject); + else % SelectedChild == 2 Experiment tab + obj.AlyxPanel.dispWaterReq(obj.NewExpSubject); + end + end + + function expSubjectChanged(obj, ~, src) + % Function deals with subject dropdown list changes + switch src.EventName + case 'SelectionChanged' % user selected a new subject + % 'refresh' experiment type - this method allows the relevent parameters to be loaded for the new subject + obj.expTypeChanged(); + case 'Connected' % user logged in to Alyx + % Change subject list to database list + obj.NewExpSubject.Option = obj.AlyxPanel.SubjectList; + obj.LogSubject.Option = obj.AlyxPanel.SubjectList; + % if selected is not in the database list, switch to + % default + if ~any(strcmp(obj.NewExpSubject.Selected, obj.AlyxPanel.SubjectList)) + obj.NewExpSubject.Selected = 'default'; + obj.expTypeChanged(); + end + if ~any(strcmp(obj.LogSubject.Selected, obj.AlyxPanel.SubjectList)) + obj.LogSubject.Selected = 'default'; + obj.expTypeChanged(); + end + obj.AlyxPanel.dispWaterReq(obj.NewExpSubject); + case 'Disconnected' % user logged out of Alyx + obj.NewExpSubject.Option = dat.listSubjects; + obj.LogSubject.Option = dat.listSubjects; + end end function expTypeChanged(obj) @@ -291,102 +327,98 @@ function paramChanged(obj) function rigExpStarted(obj, rig, evt) % Announce that the experiment has started in the log box obj.log('''%s'' on ''%s'' started', evt.Ref, rig.Name); - - [thisSubj,~,thisExpNum] = dat.parseExpRef(evt.Ref); - - ai = obj.AlyxInstance; - if ~isempty(ai) %Find/create BASE session, then create subsession - - if strcmp(thisSubj,'default'); return; end - thisDate = alyx.datestr(now); - sessions = alyx.getData(ai, ['sessions?type=Base&subject=' thisSubj]); - - %If the date of this latest base session is not the same date as - %today, then create a new base session for today - if isempty(sessions) || ~strcmp(sessions{end}.start_time(1:10), thisDate(1:10)) - d = struct; - d.subject = thisSubj; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = thisDate; - d.type = 'Base'; - - base_submit = alyx.postData(ai, 'sessions', d); - if ~isfield(base_submit,'subject') - obj.log(['Failed to create new session in Alyx for: ' thisSubj]); - disp(d); - end - obj.log(['Created new session in Alyx for: ' thisSubj]); - - %Now retrieve the sessions again - sessions = alyx.getData(ai, ['sessions?type=Base&subject=' thisSubj]); - end - latest_base = sessions{end}; - disp(latest_base); - obj.log(['Using existing session in Alyx for ' thisSubj]); - - - %Now create a new SUBSESSION, using the same experiment number - d = struct; - d.subject = thisSubj; - d.procedures = {'Behavior training/tasks'}; - d.narrative = 'auto-generated session'; - d.start_time = thisDate; - d.type = 'Experiment'; - d.parent_session = latest_base.url; - d.number = thisExpNum; - - subsession = alyx.postData(ai, 'sessions', d); - if ~isfield(subsession,'subject') - obj.log(['Failed to create new sub-session in Alyx for: ' thisSubj]); - disp(d); - end - obj.log(['Created new sub-session in Alyx for: ', thisSubj]); - - end - end function rigExpStopped(obj, rig, evt) % Announce that the experiment has stopped in the log box obj.log('''%s'' on ''%s'' stopped', evt.Ref, rig.Name); if rig == obj.RemoteRigs.Selected - set(obj.BeginExpButton, 'Enable', 'on'); % Re-enable 'Start' button so a new experiment can be started on that rig + set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'on'); % Re-enable 'Start' button so a new experiment can be started on that rig end - % Alyx water reporting: indicate amount of water this mouse still needs - if ~isempty(obj.AlyxInstance) - try + if ~isempty(rig.AlyxInstance) + try subject = dat.parseExpRef(evt.Ref); - sd = alyx.getData(obj.AlyxInstance, ... + sd = alyx.getData(rig.AlyxInstance, ... sprintf('subjects/%s', subject)); - obj.log(sprintf(... - 'Water requirement remaining for %s: %.2f (%.2f already given)', ... + obj.log('Water requirement remaining for %s: %.2f (%.2f already given)', ... subject, sd.water_requirement_remaining, ... - sd.water_requirement_total-sd.water_requirement_remaining)); + sd.water_requirement_total-sd.water_requirement_remaining); catch subject = dat.parseExpRef(evt.Ref); - obj.log(sprintf('Warning: unable to query Alyx about %s''s water requirements', subject)); + obj.log('Warning: unable to query Alyx about %s''s water requirements', subject); end + rig.AlyxInstance = []; % remove AlyxInstance from rig; no longer required end end - function rigConnected(obj, rig, evt) % If rig is connected... - obj.log('Connected to ''%s''', rig.Name); % Say so in the log box - if rig == obj.RemoteRigs.Selected - set(obj.BeginExpButton, 'Enable', 'on'); % Enable 'Start' button - set(obj.PrePostExpDelayEdits, 'Enable', 'on'); % % Enable 'Delays' boxes + function rigConnected(obj, rig, ~) + % RIGDCONNECTED Callback when notified of the rig object's 'Connected' event + % Called when the rig object status is changed to 'connected', i.e. + % when the mc computer has successfully connected to the remote + % rig. Enables the 'Start' and 'Rig optoins' buttons and logs the + % event in the Logging Display Now that the rig is connected, this + % function queries the status of the remote rig. If the remote rig + % is running an experiment, it returns the expRef for the + % experiment that is running. In this case a dialog is spawned + % notifying the user. The user can either do nothing or choose to + % view the active experiment, in which case a new EXPPANEL is + % created + % + % See also REMOTERIGCHANGED, SRV.STIMULUSCONTROL, EUI.EXPPANEL + + % If rig is connected check no experiments are running... + expRef = rig.ExpRunnning; % returns expRef if running + if expRef +% error('Experiment %s already running of %s', expDef, rig.Name) + choice = questdlg(['Attention: An experiment is already running on ', rig.Name], ... + upper(rig.Name), 'View', 'Cancel', 'Cancel'); + switch choice + case 'View' + % Load the parameters from file + paramStruct = load(dat.expFilePath(expRef, 'parameters', 'master')); + if ~isfield(paramStruct.parameters, 'type') + paramStruct.type = 'custom'; % override type name with preferred + end + % Instantiate an ExpPanel and pass it the expRef and parameters + panel = eui.ExpPanel.live(obj.ActiveExpsGrid, expRef, rig, paramStruct.parameters); + obj.LastExpPanel = panel; + % Add a listener for the new panel + panel.Listeners = [panel.Listeners + event.listener(obj, 'Refresh', @(~,~)panel.update())]; + obj.ExpTabs.SelectedChild = 2; % switch to the active exps tab + case 'Cancel' + return + end + else % The rig is idle... + obj.log('Connected to ''%s''', rig.Name); % ...say so in the log box + if rig == obj.RemoteRigs.Selected + set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'on'); % Enable 'Start' button + end end end - function rigDisconnected(obj, rig, evt) % If rig is disconnected... - obj.log('Disconnected from ''%s''', rig.Name); % Say so in the log box + function rigDisconnected(obj, rig, ~) + % RIGDISCONNECTED Callback when notified of the rig object's 'Disconnected' event + % Called when the rig object status is changed to 'disconnected', + % i.e. when the rig is no longer connected to the mc computer. + % Disables the 'Start' and 'Rig optoins' buttons and logs the event + % in the Logging Display + % + % See also REMOTERIGCHANGED, SRV.STIMULUSCONTROL + % + % If rig is disconnected... + obj.log('Disconnected from ''%s''', rig.Name); % ...say so in the log box if rig == obj.RemoteRigs.Selected - set(obj.BeginExpButton, 'enable', 'off'); % Grey out 'Start' button - set(obj.PrePostExpDelayEdits, 'Enable', 'off'); % Grey out 'Delays' boxes + set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'off'); % Grey out 'Start' button end end function log(obj, varargin) + % LOG Displayes timestamped information about occurrences in mc + % The log is stored in the LoggingDisplay property. + % log(formatSpec, A1,... An) + % + % See also FPRINTF message = sprintf(varargin{:}); timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); str = sprintf('[%s] %s', timestamp, message); @@ -395,7 +427,7 @@ function log(obj, varargin) end function remoteRigChanged(obj) - set([obj.BeginExpButton obj.PrePostExpDelayEdits], 'enable', 'off'); + set(obj.RigOptionsButton, 'Enable', 'off'); rig = obj.RemoteRigs.Selected; if ~isempty(rig) && strcmp(rig.Status, 'disconnected') %attempt to connect to rig @@ -411,47 +443,150 @@ function remoteRigChanged(obj) obj.log('Could not connect to ''%s'' (%s)', rig.Name, errmsg); end elseif strcmp(rig.Status, 'idle') - set([obj.BeginExpButton obj.PrePostExpDelayEdits], 'enable', 'on'); + set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'on'); + else + obj.rigConnected(rig); end - set(obj.PrePostExpDelayEdits(1), 'String', num2str(rig.ExpPreDelay)); - set(obj.PrePostExpDelayEdits(2), 'String', num2str(rig.ExpPostDelay)); end + function rigOptions(obj) + % RIGOPTIONS A callback for the 'Rig Options' button + % Opens a dialog allowing one to select which of the selected + % rig's services to start during experiment initialization, and + % to set any pre- and post-experiment delays. + % + % See also SRV.STIMULUSCONTROL + rig = obj.RemoteRigs.Selected; % Find which rig is selected + % Create a dialog to display the selected rig options + d = dialog('Position',[300 300 150 150],'Name', [upper(rig.Name) ' options']); + vbox = uix.VBox('Parent', d, 'Padding', 5); + bui.label('Services:', vbox); % Add 'Services' label + c = gobjects(1, length(rig.Services)); + % Ensure that SelectedServices is the correct size + if length(rig.SelectedServices)~=length(rig.Services) + rig.SelectedServices = true(size(rig.Services)); + end + if numel(rig.Services) % If the rig has any services... + for i = 1:length(rig.Services) % ...create a check box for each of them + c(i) = uicontrol('Parent', vbox, 'Style', 'checkbox',... + 'String', rig.Services{i}, 'Value', rig.SelectedServices(i)); + end + else % Otherwise indicate that no services are availible + bui.label('No services available', vbox); + i = 1; % The number of services+1, used to set the container heights + end + uix.Empty('Parent', vbox); + bui.label('Delays:', vbox); % Add 'Delyas' label next to rig dropdown + preDelaysBox = uix.HBox('Parent', vbox); + bui.label('Pre', preDelaysBox); + pre = uicontrol('Parent', preDelaysBox,... % Add 'Pre' textbox + 'Style', 'edit',... + 'BackgroundColor', [1 1 1],... + 'HorizontalAlignment', 'left',... + 'Enable', 'on',... + 'Callback', @(src, evt) put(obj.RemoteRigs.Selected,... % SetField 'ExpPreDelay' in obj.RemoteRigs.Selected to what ever was enetered + 'ExpPreDelay', str2double(get(src, 'String')))); + postDelaysBox = uix.HBox('Parent', vbox); + bui.label('Post', postDelaysBox); % Add 'Post' label + post = uicontrol('Parent', postDelaysBox,... % Add 'Post' textbox + 'Style', 'edit',... + 'BackgroundColor', [1 1 1],... + 'HorizontalAlignment', 'left',... + 'Enable', 'on',... + 'Callback', @(src, evt) put(obj.RemoteRigs.Selected,... % SetField 'ExpPostDelay' in obj.RemoteRigs.Selected to what ever was enetered + 'ExpPostDelay', str2double(get(src, 'String')))); + obj.PrePostExpDelayEdits = [pre post]; % Store Pre and Post values in obj + uix.Empty('Parent', vbox); + uicontrol('Parent',vbox,... + 'Position',[89 20 70 25],... + 'String','Okay',... + 'Callback',@rigOptions_callback); + vbox.Heights = [20 repmat(15,1,i) -1 15 15 15 15 20]; + set(obj.PrePostExpDelayEdits(1), 'String', num2str(rig.ExpPreDelay)); + set(obj.PrePostExpDelayEdits(2), 'String', num2str(rig.ExpPostDelay)); + function rigOptions_callback(varargin) + % RIGOPTIONS_CALLBACK A callback for the rig options 'Okay' + % button. Here we process the state of each services + % check-box and set the selected services accordingly + % + % See also SRV.STIMULUSCONTROL + vals = get(c,'Value'); + if iscell(vals); vals = cell2mat(vals); end + rig.SelectedServices = logical(vals'); + close(gcf) % close the + end + end + function beginExp(obj) - set(obj.BeginExpButton, 'enable', 'off'); % Grey out 'Start' button - rig = obj.RemoteRigs.Selected; % Find which rig is selected - obj.Parameters.set('services', rig.Services(:),... - 'List of experiment services to use during the experiment'); - expRef = dat.newExp(obj.NewExpSubject.Selected, now, obj.Parameters.Struct); % Create new experiment reference - panel = eui.ExpPanel.live(obj.ActiveExpsGrid, expRef, rig, obj.Parameters.Struct, obj); - obj.LastExpPanel = panel; - panel.Listeners = [panel.Listeners - event.listener(obj, 'Refresh', @(~,~)panel.update())]; - obj.ExpTabs.SelectedChild = 2; % switch to the active exps tab - rig.startExperiment(expRef); % Tell rig to start experiment - %update the parameter set label to indicate used for this experiment - subject = dat.parseExpRef(expRef); - parLabel = sprintf('from last experiment of %s (%s)', subject, expRef); - set(obj.ParamProfileLabel, 'String', parLabel, 'ForegroundColor', [0 0 0]); + % BEGINEXP The callback for the 'Start' button + % Disables the start buttons, commands the remote rig to being an + % experiment and calls the EXPPANEL object for monitoring the + % experiment. Additionally if the user is not logged into Alyx + % (i.e. no Alyx token is set), the user is prompted to log in and + % the token is stored in the rig object so that EXPPANEL can + % later post any events to Alyx (for example the amount of water + % received during the task). An Alyx Experiment and, if required, Base + % session are also created here. + % + % See also SRV.STIMULUSCONTROL, EUI.EXPPANEL, EUI.ALYXPANEL + set([obj.BeginExpButton obj.RigOptionsButton], 'Enable', 'off'); % Grey out buttons + rig = obj.RemoteRigs.Selected; % Find which rig is selected + % Save the current instance of Alyx so that eui.ExpPanel can register water to the correct account + if isempty(obj.AlyxPanel.AlyxInstance)&&~strcmp(obj.NewExpSubject.Selected,'default') + try + obj.AlyxPanel.login(); + catch + log('Warning: Must be logged in to Alyx before running an experiment') + return + end + end + % Find which services should be started by expServer + services = rig.Services(rig.SelectedServices); + % Add these services to the parameters + obj.Parameters.set('services', services(:),... + 'List of experiment services to use during the experiment'); + % Create new experiment reference + [expRef, ~, url] = dat.newExp(obj.NewExpSubject.Selected, now,... + obj.Parameters.Struct, obj.AlyxPanel.AlyxInstance); + % Add a copy of the AlyxInstance to the rig object for later + % water registration, &c. + rig.AlyxInstance = obj.AlyxPanel.AlyxInstance; + rig.AlyxInstance.subsessionURL = url; + + panel = eui.ExpPanel.live(obj.ActiveExpsGrid, expRef, rig, obj.Parameters.Struct); + obj.LastExpPanel = panel; + panel.Listeners = [panel.Listeners + event.listener(obj, 'Refresh', @(~,~)panel.update())]; + obj.ExpTabs.SelectedChild = 2; % switch to the active exps tab + rig.startExperiment(expRef); % Tell rig to start experiment + %update the parameter set label to indicate used for this experiment + subject = dat.parseExpRef(expRef); + parLabel = sprintf('from last experiment of %s (%s)', subject, expRef); + set(obj.ParamProfileLabel, 'String', parLabel, 'ForegroundColor', [0 0 0]); end function updateWeightPlot(obj) entries = obj.Log.entriesByType('weight-grams'); datenums = floor([entries.date]); obj.WeightAxes.clear(); - if numel(datenums) > 0 - obj.WeightAxes.plot(datenums, [entries.value], '-o'); - dateticks = min(datenums):floor(now); - set(obj.WeightAxes.Handle, 'XTick', dateticks); - obj.WeightAxes.XTickLabel = datestr(dateticks, 'dd-mm'); - obj.WeightAxes.yLabel('Weight (g)'); - xl = [min(datenums) floor(now)]; - if diff(xl) <= 0 - xl(1) = xl(2) - 0.5; - xl(2) = xl(2) + 0.5; - end - obj.WeightAxes.XLim = xl; + if ~isempty(obj.AlyxPanel.AlyxInstance)&&~strcmp(obj.LogSubject.Selected,'default') + obj.AlyxPanel.viewSubjectHistory(obj.WeightAxes.Handle) rotateticklabel(obj.WeightAxes.Handle, 45); + else + if numel(datenums) > 0 + obj.WeightAxes.plot(datenums, [entries.value], '-o'); + dateticks = min(datenums):floor(now); + set(obj.WeightAxes.Handle, 'XTick', dateticks); + obj.WeightAxes.XTickLabel = datestr(dateticks, 'dd-mm'); + obj.WeightAxes.yLabel('Weight (g)'); + xl = [min(datenums) floor(now)]; + if diff(xl) <= 0 + xl(1) = xl(2) - 0.5; + xl(2) = xl(2) + 0.5; + end + obj.WeightAxes.XLim = xl; + rotateticklabel(obj.WeightAxes.Handle, 45); + end end end @@ -482,6 +617,8 @@ function cleanup(obj) delete(obj.RefreshTimer); obj.RefreshTimer = []; end + % delete the AlyxPanel object + if ~isempty(obj.AlyxPanel); delete(obj.AlyxPanel); end %close connectiong to weighing scales if ~isempty(obj.WeighingScale) obj.WeighingScale.cleanup(); @@ -501,22 +638,8 @@ function recordWeight(obj) dat.addLogEntry(subject, now, 'weight-grams', grams, ''); obj.log('Logged weight of %.1fg for ''%s''', grams, subject); - - d.subject = subject; - d.weight = grams; - d.user = obj.AlyxUsername; - - if ~isempty(obj.AlyxInstance) - try - w = alyx.postData(obj.AlyxInstance, 'weighings/', d); - obj.log('Alyx weight posting succeeded: %.2f for %s', w.weight, w.subject); - catch - obj.log('Warning: Alyx weight posting failed!'); - end - else - obj.weighingsUnpostedToAlyx{end+1} = d; - obj.log('Warning: Weight not posted to Alyx; will be posted upon login.'); - end + % post weight to Alyx + obj.AlyxPanel.recordWeight(grams, subject) %refresh log entries so new weight reading is plotted obj.Log.setSubject(obj.LogSubject.Selected); @@ -531,7 +654,7 @@ function buildUI(obj, parent) % Parent here is the MC window (figure) % tabs for doing different things with the selected subject obj.TabPanel = uiextras.TabPanel('Parent', obj.RootContainer, 'Padding', 5); obj.LoggingDisplay = uicontrol('Parent', obj.RootContainer, 'Style', 'listbox',... - 'Enable', 'inactive', 'String', {}); % This is the messege area at the bottom of mc + 'Enable', 'inactive', 'String', {}, 'Tag', 'Logging Display'); % This is the messege area at the bottom of mc obj.RootContainer.Sizes = [-1 72]; % TabPanel variable size with wieght 1; LoggingDisplay fixed height of 72px %% Log tab @@ -583,7 +706,7 @@ function buildUI(obj, parent) % Parent here is the MC window (figure) obj.NewExpSubject = bui.Selector(topgrid, dat.listSubjects); % Subject dropdown box set(subjectLabel, 'FontSize', 11); % Make 'Subject' label larger set(obj.NewExpSubject.UIControl, 'FontSize', 11); % Make dropdown box text larger - obj.NewExpSubject.addlistener('SelectionChanged', @(~,~) obj.expSubjectChanged()); % Add listener for subject selection + obj.NewExpSubject.addlistener('SelectionChanged', @obj.expSubjectChanged); % Add listener for subject selection obj.NewExpType = bui.Selector(topgrid, {obj.NewExpFactory.label}); % Make experiment type dropdown box obj.NewExpType.addlistener('SelectionChanged', @(~,~) obj.expTypeChanged()); % Add listener for experiment type change @@ -596,34 +719,24 @@ function buildUI(obj, parent) % Parent here is the MC window (figure) obj.RemoteRigs = bui.Selector(controlbox, srv.stimulusControllers); % Rig dropdown box obj.RemoteRigs.addlistener('SelectionChanged', @(src,~) obj.remoteRigChanged); % Add listener for rig selection change obj.Listeners = arrayfun(@obj.listenToRig, obj.RemoteRigs.Option, 'Uni', false); % Add listeners for each rig (keep track of whether they're connected, running, etc.) - bui.label('Delays: Pre', controlbox); % Add 'Delyas' label next to rig dropdown - pre = uicontrol('Parent', controlbox,... % Add 'Pre' textbox - 'Style', 'edit',... - 'BackgroundColor', [1 1 1],... - 'HorizontalAlignment', 'left',... - 'Enable', 'off',... - 'Callback', @(src, evt) put(obj.RemoteRigs.Selected,... % SetField 'ExpPreDelay' in obj.RemoteRigs.Selected to what ever was enetered - 'ExpPreDelay', str2double(get(src, 'String')))); - bui.label('Post', controlbox); % Add 'Post' label - post = uicontrol('Parent', controlbox,... % Add 'Post' textbox - 'Style', 'edit',... - 'BackgroundColor', [1 1 1],... - 'HorizontalAlignment', 'left',... - 'Enable', 'off',... - 'Callback', @(src, evt) put(obj.RemoteRigs.Selected,... % SetField 'ExpPostDelay' in obj.RemoteRigs.Selected to what ever was enetered - 'ExpPostDelay', str2double(get(src, 'String')))); - obj.PrePostExpDelayEdits = [pre post]; % Store Pre and Post values in obj + obj.RigOptionsButton = uicontrol('Parent', controlbox, 'Style', 'pushbutton',... % Add 'Options' button + 'String', 'Options',... + 'TooltipString', 'Set services and delays',... + 'Callback', @(~,~) obj.rigOptions(),... % When pressed run 'rigOptions' function + 'Enable', 'off'); obj.BeginExpButton = uicontrol('Parent', controlbox, 'Style', 'pushbutton',... % Add 'Start' button 'String', 'Start',... 'TooltipString', 'Start an experiment using the parameters',... 'Callback', @(~,~) obj.beginExp(),... % When pressed run 'beginExp' function 'Enable', 'off'); - controlbox.Sizes = [80 200 60 50 30 50 80]; % Resize the Rig and Delay boxes + controlbox.Sizes = [80 200 80 80]; % Resize the Rig and Delay boxes leftSideBox.Heights = [55 22]; % Create the Alyx panel - eui.AlyxPanel(obj, headerBox); + obj.AlyxPanel = eui.AlyxPanel(headerBox); + addlistener(obj.NewExpSubject, 'SelectionChanged', @(src, evt)obj.AlyxPanel.dispWaterReq(src, evt)); + addlistener(obj.LogSubject, 'SelectionChanged', @(src, evt)obj.AlyxPanel.dispWaterReq(src, evt)); % a titled panel for the parameters editor param = uiextras.Panel('Parent', newExpBox, 'Title', 'Parameters', 'Padding', 5); @@ -664,8 +777,8 @@ function buildUI(obj, parent) % Parent here is the MC window (figure) obj.TabPanel.SelectedChild = 2; obj.ExpTabs.TabNames = {'New' 'Current'}; obj.ExpTabs.SelectedChild = 1; + obj.TabPanel.SelectionChangedFcn = @(~,~)obj.tabChanged; end end -end - +end \ No newline at end of file diff --git a/+eui/ParamEditor.m b/+eui/ParamEditor.m index d9b1a8fa..a513ad6a 100644 --- a/+eui/ParamEditor.m +++ b/+eui/ParamEditor.m @@ -205,8 +205,38 @@ function newCondition(obj) end function deleteSelectedConditions(obj) + %DELETESELECTEDCONDITIONS Removes the selected conditions from table + % The callback for the 'Delete condition' button. This removes the + % selected conditions from the table and if less than two conditions + % remain, globalizes them. + % TODO: comment function better, index in a clearer fashion + % + % See also EXP.PARAMETERS, GLOBALISESELECTEDPARAMETERS rows = unique(obj.SelectedCells(:,1)); + % If the number of remaining conditions is 1 or less... + names = obj.Parameters.TrialSpecificNames; + numConditions = size(obj.Parameters.Struct.(names{1}),2); + if numConditions-length(rows) <= 1 + remainingIdx = find(all(1:numConditions~=rows,1)); + if isempty(remainingIdx); remainingIdx = 1; end + % change selected cells to be all fields (except numRepeats which + % is assumed to always be the last column) + obj.SelectedCells =[ones(length(names)-1,1)*remainingIdx, (1:length(names)-1)']; + %... globalize them + obj.globaliseSelectedParameters; + obj.Parameters.removeConditions(rows) +% for i = 1:numel(names) +% newValue = iff(any(remainingIdx), obj.Struct.(names{i})(:,remainingIdx), obj.Struct.(names{i})(1)); +% % If the parameter is Num repeats, set the value +% if strcmp(names{i}, 'numRepeats') +% obj.Struct.(names{i}) = newValue; +% else +% obj.makeGlobal(names{i}, newValue); +% end +% end + else % Otherwise delete the selected conditions as usual obj.Parameters.removeConditions(rows); + end obj.fillConditionTable(); %refresh the table of conditions end diff --git a/+eui/SqueakExpPanel.m b/+eui/SqueakExpPanel.m index 248866d8..865a0c0d 100644 --- a/+eui/SqueakExpPanel.m +++ b/+eui/SqueakExpPanel.m @@ -1,5 +1,5 @@ classdef SqueakExpPanel < eui.ExpPanel - %eui.SqueakExpPanel Basic UI control for monitoring an experiment + %EUI.SQUEAKEXPPANEL Basic UI control for monitoring an experiment % TODO % % Part of Rigbox diff --git a/+eui/alyxGUI.m b/+eui/alyxGUI.m deleted file mode 100644 index c51bdf51..00000000 --- a/+eui/alyxGUI.m +++ /dev/null @@ -1,68 +0,0 @@ -classdef alyxGUI < handle - % eui.alyxGUI - % Standalone gui for alyx, without the rest of MC - % Needs only three things: subject selector, log box, alyx panel - % - % Part of Rigbox - - properties - AlyxInstance = []; - AlyxUsername = []; - weighingsUnpostedToAlyx = {}; % holds weighings until someone logs in, to be posted - end - - properties (SetAccess = private) - NewExpSubject - - - end - - properties (Access = private) - LoggingDisplay %control for showing log output - - RootContainer - - Listeners - - end - - methods - function obj = alyxGUI() - - f = figure('Name', 'alyx GUI',... - 'MenuBar', 'none',... - 'Toolbar', 'none',... - 'NumberTitle', 'off',... - 'Units', 'normalized',... - 'OuterPosition', [0.1 0.1 0.4 .4]); - - obj.RootContainer = uiextras.VBox('Parent', f,... - 'Visible', 'on'); - - % subject selector - sbox = uix.HBox('Parent', obj.RootContainer); - bui.label('Select subject: ', sbox); - obj.NewExpSubject = bui.Selector(sbox, {'default'}); % Subject dropdown box - - % alyx panel - eui.AlyxPanel(obj, obj.RootContainer); - - % logging message area - obj.LoggingDisplay = uicontrol('Parent', obj.RootContainer, 'Style', 'listbox',... - 'Enable', 'inactive', 'String', {}); - - obj.RootContainer.Sizes = [50 150 150]; - - - end - - function log(obj, varargin) - message = sprintf(varargin{:}); - timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); - str = sprintf('[%s] %s', timestamp, message); - current = get(obj.LoggingDisplay, 'String'); - set(obj.LoggingDisplay, 'String', [current; str], 'Value', numel(current) + 1); - end - - end -end diff --git a/+exp/Experiment.m b/+exp/Experiment.m index 35db458d..b0229875 100644 --- a/+exp/Experiment.m +++ b/+exp/Experiment.m @@ -776,9 +776,18 @@ function saveData(obj) if isempty(obj.AlyxInstance) warning('No Alyx token set'); else - [subject,~,~] = dat.parseExpRef(obj.Data.expRef); - if strcmp(subject,'default'); return; end - alyx.registerFile(subject,[],'Block',savepaths{end},'zserver',obj.AlyxInstance); + try + [subject,~,~] = dat.parseExpRef(obj.Data.expRef); + if strcmp(subject,'default'); return; end + % Register saved files + alyx.registerFile(savepaths{end}, 'mat',... + obj.AlyxInstance.subsessionURL, 'Block', [], obj.AlyxInstance); + % Save the session end time + alyx.putData(obj.AlyxInstance, obj.AlyxInstance.subsessionURL,... + struct('end_time', alyx.datestr(now), 'subject', subject)); + catch + warning('couldnt register files to alyx because no subsession found'); + end end end end diff --git a/+exp/Parameters.m b/+exp/Parameters.m index 25a2690c..03c544f6 100644 --- a/+exp/Parameters.m +++ b/+exp/Parameters.m @@ -71,7 +71,7 @@ function set(obj, name, value, description, units) n = numel(obj.pNames); obj.IsTrialSpecific = struct; isTrialSpecificDefault = @(n) ... - ~any(strcmp(n, {'defFunction'})) &&... + ~any(strcmp(n, {'defFunction', 'expPanelFun'})) &&... (strcmp(n, {'numRepeats'})... || size(obj.pStruct.(n), 2) > 1); for i = 1:n @@ -183,7 +183,7 @@ function makeGlobal(obj, name, newValue) % concatenate trial parameter trialParamValues = cat(1, trialParamValues{:}); if isempty(trialParamValues) - trialParamValues = {}; + trialParamValues = {1}; end trialParams = cell2struct(trialParamValues, trialParamNames, 1)'; globalParams = cell2struct(globalParamValues, globalParamNames, 1); diff --git a/+exp/SignalsExp.m b/+exp/SignalsExp.m index c4020cf1..74c3e059 100644 --- a/+exp/SignalsExp.m +++ b/+exp/SignalsExp.m @@ -128,9 +128,9 @@ obj.Inputs = sig.Registry(clockFun); obj.Outputs = sig.Registry(clockFun); obj.Visual = StructRef; - nAudChannels = getOr(paramStruct, 'numAudChannels', 2); - audSampleRate = getOr(paramStruct, 'audSampleRate', 192e3); % Hz - audDevIdx = getOr(paramStruct, 'audDevIdx', -1); % -1 means use system default + nAudChannels = getOr(paramStruct, 'numAudChannels', rig.audioDevice.NrOutputChannels); + audSampleRate = getOr(paramStruct, 'audSampleRate', rig.audioDevice.DefaultSampleRate); % Hz + audDevIdx = getOr(paramStruct, 'audDevIdx', rig.audioDevice.DeviceIndex); % -1 means use system default obj.Audio = audstream.Registry(audSampleRate, nAudChannels, audDevIdx); obj.Events = sig.Registry(clockFun); %% configure signals @@ -215,7 +215,7 @@ function useRig(obj, rig) obj.DaqController.ChannelNames)); % Find matching channel from rig hardware file if id % if the output is present, create callback obj.Listeners = [obj.Listeners - obj.Outputs.(outputNames{m}).onValue(@(v)obj.DaqController.command([zeros(1,id-1) v])) % pad value with zeros in order to output to correct channel + obj.Outputs.(outputNames{m}).onValue(@(v)obj.DaqController.command([zeros(size(v,1),id-1) v])) % pad value with zeros in order to output to correct channel obj.Outputs.(outputNames{m}).onValue(@(v)fprintf('delivering output of %.2f\n',v)) ]; elseif strcmp(outputNames{m}, 'reward') % special case; rewardValve is always first signals generator in list @@ -607,6 +607,10 @@ function cleanup(obj) deleteGlTextures(obj); KbQueueStop(); KbQueueRelease(); + + % delete cached experiment definition function from memory + [~, exp_func] = fileparts(obj.Data.expDef); + clear(exp_func) end function deleteGlTextures(obj) @@ -828,11 +832,17 @@ function saveData(obj) warning('No Alyx token set'); else try - [subject,~,~] = dat.parseExpRef(obj.Data.expRef); + [subject,~,~] = dat.parseExpRef(obj.Data.expRef); if strcmp(subject,'default'); return; end - alyx.registerFile(subject,[],'Block',savepaths{end},'zserver',obj.AlyxInstance); - catch - warning('couldnt register files to alyx because no subsession found'); + % Register saved files + alyx.registerFile(savepaths{end}, 'mat',... + obj.AlyxInstance.subsessionURL, 'Block', [], obj.AlyxInstance); + % Save the session end time + alyx.putData(obj.AlyxInstance, obj.AlyxInstance.subsessionURL,... + struct('end_time', alyx.datestr(now), 'subject', subject)); + catch ex + warning('couldnt register files to alyx'); + disp(ex) end end diff --git a/+exp/StartServices.m b/+exp/StartServices.m index 85a6321c..0c3ac13c 100644 --- a/+exp/StartServices.m +++ b/+exp/StartServices.m @@ -3,7 +3,7 @@ % Convenience action for use with an EventHandler. This will start the % associated services, by calling start(ref) on them, where 'ref' is % taken from the Experiment's reference and the current instance of - % Alyx (if any). See also SRV.SERVICE. + % Alyx (if any). See also SRV.SERVICE, EXP.STOPSERVICES % % Part of Rigbox @@ -28,8 +28,8 @@ end function perform(obj, eventInfo, dueTime) - ref = dat.parseAlyxInstance(eventInfo.Experiment.AlyxInstance,... - eventInfo.Experiment.Data.expRef); + ref = dat.parseAlyxInstance(eventInfo.Experiment.Data.expRef,... + eventInfo.Experiment.AlyxInstance); n = numel(obj.Services); for i = 1:n try diff --git a/+exp/inferParameters.m b/+exp/inferParameters.m index deb3428b..bc879029 100644 --- a/+exp/inferParameters.m +++ b/+exp/inferParameters.m @@ -20,6 +20,8 @@ e.pars.CacheSubscripts = true; e.visual = net.subscriptableOrigin('visual'); e.audio = net.subscriptableOrigin('audio'); +e.audio.SampleRate = 44100; +e.audio.NChannels = 2; e.inputs = net.subscriptableOrigin('inputs'); e.outputs = net.subscriptableOrigin('outputs'); @@ -43,6 +45,11 @@ parsStruct.numRepeats = ones(1,max(sz)); % add 'numRepeats' parameter parsStruct.defFunction = expdef; parsStruct.type = 'custom'; + % Define the ExpPanel to use (automatically by name convention for now) + [path, name, ext] = fileparts(expdef); + ExpPanel_name = [name 'ExpPanel']; + ExpPanel_fn = [path filesep ExpPanel_name ext]; + if exist(ExpPanel_fn,'file'); parsStruct.expPanelFun = ExpPanel_name; end catch ex net.delete(); rethrow(ex) diff --git a/+hw/+ptb/Window.m b/+hw/+ptb/Window.m index 294d2628..8cab6568 100644 --- a/+hw/+ptb/Window.m +++ b/+hw/+ptb/Window.m @@ -571,8 +571,7 @@ function applyCalibration(obj, cal) list = find(diff(thisTable) <= 0, 1); if ~isempty(list) - announce = sprintf('Gamma table %d NOT MONOTONIC. We are adjusting.',igun); - disp(announce) + fprintf('Gamma table %d NOT MONOTONIC. We are adjusting.',igun); % We assume that the non-monotonic points only differ due to noise % and so we can resort them without any consequences @@ -606,12 +605,12 @@ function applyCalibration(obj, cal) interp1(monTable,posLocs-1,(0:(numEntries-1))/(numEntries-1))'; end - if any(isnan(c.monitorGamInv)), + if any(isnan(c.monitorGamInv)) msgbox('Warning: NaNs in inverse gamma table -- may need to recalibrate.'); end end - function storeDaqData(obj, src, event) + function storeDaqData(obj, ~, event) n = length(event.TimeStamps); ii = obj.DaqData.nSamples+(1:n); obj.DaqData.timeStamps(ii) = event.TimeStamps; diff --git a/+hw/ControlSignalGenerator.m b/+hw/ControlSignalGenerator.m index 2a0b8190..731ebe70 100644 --- a/+hw/ControlSignalGenerator.m +++ b/+hw/ControlSignalGenerator.m @@ -1,6 +1,40 @@ classdef ControlSignalGenerator < matlab.mixin.Heterogeneous & handle - %UNTITLED4 Summary of this class goes here - % Detailed explanation goes here + %HW.CONTROLSIGNALGENERATOR Generates a waveform from a command value + % This is an abstract class for generating a simple waveform (e.g. + % sinewave) based on a command value or values. The principle method + % must return an array of samples that can be queued to an NI DAQ. + % This is a member of the SignalGenerators property of + % HW.DAQCONTROLLER. + % + % Below is a list of some subclasses and their functions: + % HW.REWARDVALVECONTROL - Generates an array of voltage samples to + % open and close a valve in order to deliver a specified amount of + % water based on calibration data + % HW.SINEWAVEGENERATOR - Generates a sinewave + % HW.PULSESWITCHER - Generates a squarewave pulse train + % HW.DIGITALONOFF - Generates a simple TTL pulse + % + % Example: Setting up laser shutter reward in a Signals behavour task + % %Load the romote rig's hardware.mat + % load('hardware.mat'); + % %Add a new channel + % daqController.ChannelNames{end+1} = 'laserShutter'; + % %Define the channel ID to output on + % daqController.DaqChannelIds{end+1} = 'ai1'; + % %As it is an analogue output, set the AnalogueChannelsIdx to true + % daqController.AnalogueChannelIdx(end+1) = true; + % %Add a signal generator that will return the correct samples for a + % %specified train of pulses + % daqController.SignalGenerators(1) = hw.PulseSwitcher(duration, + % nPulses, freq); + % %Save your hardware file + % save('hardware.mat', 'daqController', '-append'); + % + % See also HW.DAQCONTROLLER + % + % Part of Rigbox + + % 2013 CB created properties DefaultCommand %optional, for generating a default control waveform diff --git a/+hw/DaqController.m b/+hw/DaqController.m index ea44a4f8..4a480fcf 100644 --- a/+hw/DaqController.m +++ b/+hw/DaqController.m @@ -3,12 +3,40 @@ % This class deals with creating DAQ sessions, assigning output % channels and generating the relevant waveforms to output to each % channel. + % + % Example: Setting up water valve interface for a Signals behavour task + % %In the romote rig's hardware.mat, instantiate a HW.DAQCONTROLLER + % %object to interface with an NI DAQ + % daqController = hw.DaqController; + % %Set the DAQ id (can be found with daq.getDevices) + % daqController.DaqIds = 'Dev1'; + % %Add a new channel + % daqController.ChannelNames = {'rewardValve'}; + % %Define the channel ID to output on + % daqController.DaqChannelIds = {'ai0'}; + % %As it is an analogue output, set the AnalogueChannelsIdx to true + % daqController.AnalogueChannelIdx(1) = true; + % %Add a signal generator that will return the correct samples for + % %delivering a reward of a specified volume + % daqController.SignalGenerators(1) = hw.RewardValveControl; + % %Set some of the required fields (see HW.REWARDVALVECONTROL for + % %more info + % daqController.SignalGenerators(1).OpenValue = 5; + % daqController.SignalGenerators(1).Calibrations = + % valveDeliveryCalibration(openTimeRange, scalesPort, openValue,... + % closedValue, daqChannel, daqDevice); + % %Save your hardware file + % save('hardware.mat', 'daqController', '-append'); % % TODO: % * Currently can not deal with having no analogue channels % * The digital channels must be output only (no support for % bi-directional digital channels % * Untested with multiple devices + % + % See also HW.CONTROLSIGNALGENERATOR, HW.DAQROTARYENCODER + % 2013 CB created + % 2017-07 MW added digital output support properties ChannelNames = {} % name to refer to each channel @@ -126,13 +154,15 @@ function command(obj, varargin) end channelNames = obj.ChannelNames(1:n); analogueChannelsIdx = obj.AnalogueChannelsIdx(1:n); - if any(analogueChannelsIdx)&&any(values(analogueChannelsIdx)~=0) + if any(analogueChannelsIdx)&&any(any(values(:,analogueChannelsIdx)~=0)) queue(obj, channelNames(analogueChannelsIdx), waveforms(analogueChannelsIdx)); if foreground startForeground(obj.DaqSession); else startBackground(obj.DaqSession); end + readyWait(obj); + obj.DaqSession.release; elseif any(~analogueChannelsIdx) waveforms = waveforms(~analogueChannelsIdx); for n = 1:length(waveforms) diff --git a/+hw/DaqDataManager.m b/+hw/DaqDataManager.m deleted file mode 100644 index 97886388..00000000 --- a/+hw/DaqDataManager.m +++ /dev/null @@ -1,16 +0,0 @@ -classdef DaqDataManager - %UNTITLED2 Summary of this class goes here - % Detailed explanation goes here - - properties - end - - methods - function id = manageAnalogOutputChannel(chan, defaultValue) - end - - function submit - end - -end - diff --git a/+hw/DaqLaser.m b/+hw/DaqLaser.m deleted file mode 100644 index fb51b1ba..00000000 --- a/+hw/DaqLaser.m +++ /dev/null @@ -1,89 +0,0 @@ -classdef DaqLaser < hw.RewardController - %DAQLASER Controls a laser via a DAQ to deliver reward - % Must (currently) be sole outputer on DAQ session - - properties - DaqSession % should be a DAQ session containing just one output channel - DaqId = 'Dev1' % the DAQ's device ID, e.g. 'Dev1' - DaqChannelId = 'ao1' % the DAQ's ID for the counter channel. e.g. 'ao0' - DaqOutputChannelIdx = 2 - % for controlling the reward valve - OpenValue = 5 - ClosedValue = 0 - MeasuredDeliveries % - PulseLength = 10e-3 % seconds - StimDuration = 0.5 % seconds - PulseFrequency = 25 %Hz - end - - properties (Access = protected) - CurrValue - end - - methods - function createDaqChannel(obj) - obj.DaqSession.addAnalogOutputChannel(obj.DaqId, obj.DaqChannelId, 'Voltage'); -% obj.DaqSession.outputSingleScan(obj.ClosedValue); - obj.CurrValue = 0; - end - function open(obj) - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.outputSingleScan(obj.OpenValue); - obj.CurrValue = obj.OpenValue; - end - function close(obj) - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.outputSingleScan(obj.ClosedValue); - obj.CurrValue = obj.ClosedValue; - end - function closed = toggle(obj) - if obj.CurrValue == obj.ClosedValue; - open(obj); - closed = false; - else - close(obj); - closed = true; - end - end - function samples = waveformFor(obj, size) - % Returns the waveform that should be sent to the DAQ to control - % reward output given a certain reward size - sampleRate = obj.DaqSession.Rate; - - nCycles = ceil(obj.PulseFrequency*obj.StimDuration); - nSamples = nCycles/obj.PulseFrequency*sampleRate; - samples = zeros(nSamples/nCycles, nCycles); - nPulseSamples = obj.PulseLength*sampleRate; - samples(1:nPulseSamples,:) = obj.OpenValue; - samples = samples(:); - end - - function deliverBackground(obj, size) - % size not implemeneted yet - lasersamples = waveformFor(obj, size); - samples = zeros(numel(lasersamples), numel(obj.DaqSession.Channels)); - samples(:,obj.DaqOutputChannelIdx) = lasersamples; - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.queueOutputData(samples); - daqSession.startBackground(); - time = obj.Clock.now; - obj.CurrValue = obj.ClosedValue; - logSample(obj, size, time); - end - - function deliverMultiple(obj, size, interval, n, sizeIsOpenDuration) - error('not implemented') - end - end - -end - diff --git a/+hw/DaqRewardValve.m b/+hw/DaqRewardValve.m deleted file mode 100644 index e99b9bd7..00000000 --- a/+hw/DaqRewardValve.m +++ /dev/null @@ -1,161 +0,0 @@ -classdef DaqRewardValve < hw.RewardController - %HW.DAQREWARDVALVE Controls a valve via a DAQ to deliver reward - % Must (currently) be sole outputer on DAQ session - % TODO - % - % Part of Rigbox - - % 2013-01 CB created - - properties - DaqSession; % should be a DAQ session containing just one output channel - DaqId = 'Dev1'; % the DAQ's device ID, e.g. 'Dev1' - DaqChannelId = 'ao0'; % the DAQ's ID for the counter channel. e.g. 'ao0' - DaqOutputChannelIdx = 1 - % for controlling the reward valve - OpenValue = 6; - ClosedValue = 0; - MeasuredDeliveries; % deliveries with measured volumes for calibration. - % This should be a struct array with fields 'durationSecs' & - % 'volumeMicroLitres' indicating the duration the valve was open, and the - % measured volume (in ul) for that delivery. These points are interpolated - % to work out how long to open the valve for arbitrary volumes. - end - - properties (Access = protected) - CurrValue; - end - - methods - function createDaqChannel(obj) - obj.DaqSession.addAnalogOutputChannel(obj.DaqId, obj.DaqChannelId, 'Voltage'); - obj.DaqSession.outputSingleScan(obj.ClosedValue); - obj.CurrValue = obj.ClosedValue; - end - function open(obj) - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.outputSingleScan(obj.OpenValue); - obj.CurrValue = obj.OpenValue; - end - function close(obj) - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - daqSession.outputSingleScan(obj.ClosedValue); - obj.CurrValue = obj.ClosedValue; - end - function closed = toggle(obj) - if obj.CurrValue == obj.ClosedValue; - open(obj); - closed = false; - else - close(obj); - closed = true; - end - end - function duration = openDurationFor(obj, microLitres) - % Returns the duration the valve should be opened for to deliver - % microLitres of reward. Is calibrated using interpolation of the - % measured delivery data. - volumes = [obj.MeasuredDeliveries.volumeMicroLitres]; - durations = [obj.MeasuredDeliveries.durationSecs]; - if microLitres > max(volumes) || microLitres < min(volumes) - fprintf('Warning requested delivery of %.1f is outside calibration range\n',... - microLitres); - end - duration = interp1(volumes, durations, microLitres, 'pchip'); - end - function ul = microLitresFromDuration(obj, duration) - % Returns the amount of reward the valve would delivery by being open - % for the duration specified. Is calibrated using interpolation of the - % measured delivery data. - volumes = [obj.MeasuredDeliveries.volumeMicroLitres]; - durations = [obj.MeasuredDeliveries.durationSecs]; - ul = interp1(durations, volumes, duration, 'pchip'); - end - - function sz = deliverBackground(obj, sz) - % size is the volume to deliver in microlitres (ul). This is turned - % into an open duration for the valve using interpolation of the - % calibration measurements. - if nargin < 2 - sz = obj.DefaultRewardSize; - end - duration = openDurationFor(obj, sz); - daqSession = obj.DaqSession; - sampleRate = daqSession.Rate; - nOpenSamples = round(duration*sampleRate); - samples = zeros(nOpenSamples + 3, numel(obj.DaqSession.Channels)); - samples(:,obj.DaqOutputChannelIdx) = [obj.OpenValue*ones(nOpenSamples, 1) ; ... - obj.ClosedValue*ones(3,1)]; - if daqSession.IsRunning - daqSession.wait(); - end -% fprintf('Delivering %gul by opening valve for %gms\n', size, 1000*duration); - daqSession.queueOutputData(samples); - daqSession.startBackground(); - time = obj.Clock.now; - obj.CurrValue = obj.ClosedValue; - logSample(obj, sz, time); - end - - function deliverMultiple(obj, size, interval, n, sizeIsOpenDuration) - % Delivers n rewards in shots spaced in time by at least interval. - % Useful for example, for obtaining calibration data. - % If sizeIsOpenDuration is true, then specified size is the open - % duration of the valve, if false (default), then specified size is the - % usual micro litres size converted to open duration using the measurement - % data for calibration. - if nargin < 5 || isempty(sizeIsOpenDuration) - sizeIsOpenDuration = false; % defaults to size is in microlitres - end - if isempty(interval) - interval = 0.1; % seconds - good interval given open/close delays - end - daqSession = obj.DaqSession; - if daqSession.IsRunning - daqSession.wait(); - end - if sizeIsOpenDuration - duration = size; - size = microLitresFromDuration(obj, size); - else - duration = openDurationFor(obj, size); - end - sampleRate = daqSession.Rate; - nsamplesOpen = round(sampleRate*duration); - nsamplesClosed = round(sampleRate*interval); - period = 1/sampleRate * (nsamplesOpen + nsamplesClosed); - signal = [obj.OpenValue*ones(nsamplesOpen, 1) ; ... - obj.ClosedValue*ones(nsamplesClosed, 1)]; - blockReps = 20; - blockSignal = repmat(signal, [blockReps 1]); - nBlocks = floor(n/blockReps); - - for i = 1:nBlocks - % use the reward timer controller to open and close the reward valve - daqSession.queueOutputData(blockSignal); - time = obj.Clock.now; - daqSession.startForeground(); - fprintf('rewards %i-%i delivered.\n', blockReps*(i - 1) + 1, blockReps*i); - logSamples(obj, repmat(size, [1 blockReps]), ... - time + cumsum(period*ones(1, blockReps)) - period); - end - remaining = n - blockReps*nBlocks; - for i = 1:remaining - % use the reward timer controller to open and close the reward valve - daqSession.queueOutputData(signal); - time = obj.Clock.now; - daqSession.startForeground(); - logSample(obj, size, time); - end - fprintf('rewards %i-%i delivered.\n', blockReps*nBlocks + 1, blockReps*nBlocks + remaining); - end - end - -end - diff --git a/+hw/DaqRotaryEncoder.m b/+hw/DaqRotaryEncoder.m index dfc1eb68..c3d014fc 100644 --- a/+hw/DaqRotaryEncoder.m +++ b/+hw/DaqRotaryEncoder.m @@ -116,7 +116,7 @@ function delete(obj) function deleteListeners(obj) if ~isempty(obj.DaqListener) delete(obj.DaqListener); - end; + end end function x = decodeDaq(obj, newValue) @@ -148,7 +148,7 @@ function deleteListeners(obj) end methods (Access = protected) - function daqListener(obj, src, event) + function daqListener(obj, ~, event) acqStartTime = obj.Clock.fromMatlab(event.TriggerTime); values = decode(obj, event.Data(:,obj.DaqInputChannelIdx)) - obj.ZeroOffset; times = acqStartTime + event.TimeStamps(:,obj.DaqInputChannelIdx); diff --git a/+hw/DaqSingleScan.m b/+hw/DaqSingleScan.m new file mode 100644 index 00000000..2bb53282 --- /dev/null +++ b/+hw/DaqSingleScan.m @@ -0,0 +1,29 @@ +classdef DaqSingleScan < hw.ControlSignalGenerator + %HW.DaqSingleScan Outputs a single value, just changing the level of the + %analog output + % + % + + properties + Scale % multiplicatively scale the output + % for instance, make this a conversion factor between your + % desired output units (like mm of a galvo, or mW of a laser) and + % voltage + end + + methods + function obj = DaqSingleScan(scale) + obj.DefaultValue = 0; + obj.Scale = scale; + end + + function samples = waveform(obj, v) + % just take the first value (if multiple were provided) and output + % it, scaled, as a single number. This will result in the analog + % output channel switching to that value and staying there. + samples = v(1)*obj.Scale; + end + end + +end + diff --git a/+hw/DigitalOnOff.m b/+hw/DigitalOnOff.m index 16ff8306..2eea3c64 100644 --- a/+hw/DigitalOnOff.m +++ b/+hw/DigitalOnOff.m @@ -1,12 +1,18 @@ classdef DigitalOnOff < hw.ControlSignalGenerator - %HW.DigitalOnOff Digital HIGH/LOW switch + %HW.DIGITALONOFF Digital HIGH/LOW switch % Currently converts input to either HIGH or LOW digital output. In the % future timed switch should be introduced. + % + % See also HW.DAQCONTROLLER + % + % Part of Rigbox + + % 2017-07 MW created properties - OnValue = 5 - OffValue = 0 - ParamsFun = @(in) logical(in); % converts input to logical + OnValue = 5 % The HIGH voltage to output + OffValue = 0 % The LOW voltage to output + ParamsFun = @(in) logical(in); % Converts input to logical end methods diff --git a/+hw/DummyFeedback.m b/+hw/DummyFeedback.m deleted file mode 100644 index ca3da58a..00000000 --- a/+hw/DummyFeedback.m +++ /dev/null @@ -1,23 +0,0 @@ -classdef DummyFeedback < hw.RewardController - %HW.DUMMYFEEDBACK hw.RewardController implementation that does nothing - % Detailed explanation goes here - % - % Part of Rigbox - - % 2012-10 CB created - - properties - end - - methods - function deliverBackground(obj, size) - % do nothing for now - fprintf('reward %f\n', size); - logSample(obj, size, obj.Clock.now); - end - function deliverMultiple(obj, size, interval, n) - % do nothing for now - end - end -end - diff --git a/+hw/PulseSwitcher.m b/+hw/PulseSwitcher.m index 9dfadb81..e5b0e5f3 100644 --- a/+hw/PulseSwitcher.m +++ b/+hw/PulseSwitcher.m @@ -1,6 +1,9 @@ classdef PulseSwitcher < hw.ControlSignalGenerator %HW.PULSESWITCHER Generates a train of pulses % Detailed explanation goes here + % See also HW.DAQCONTROLLER + % + % Part of Rigbox properties OpenValue = 5 diff --git a/+hw/RewardController.m b/+hw/RewardController.m deleted file mode 100644 index cefbf257..00000000 --- a/+hw/RewardController.m +++ /dev/null @@ -1,35 +0,0 @@ -classdef RewardController < hw.DataLogging - %HW.REWARDCONTROLLER Abstract interface for controlling reward devices - % Detailed explanation goes here - % - % Part of Rigbox - - % 2012-10 CB created - - properties - DefaultRewardSize = 2.5 % reward size if no size was specified - end - - properties (Dependent = true) - DeliveredSizes - DeliveryTimes - end - - methods (Abstract) - %deliverBackground(size) call deliver to deliver a reward of the - %specified size and return before completion of delivery. - sz = deliverBackground(obj, sz) - deliverMultiple(obj, size, interval, n) %for calibration - end - - methods - function value = get.DeliveredSizes(obj) - value = obj.DataBuffer(1:obj.SampleCount); - end - function value = get.DeliveryTimes(obj) - value = obj.TimesBuffer(1:obj.SampleCount); - end - end - -end - diff --git a/+hw/RewardValveControl.m b/+hw/RewardValveControl.m index c79cebe8..aa94b999 100644 --- a/+hw/RewardValveControl.m +++ b/+hw/RewardValveControl.m @@ -9,7 +9,6 @@ properties Calibrations - % deliveries with measured volumes for calibration. % This should be a struct array with fields 'durationSecs' & % 'volumeMicroLitres' indicating the duration the valve was open, and the diff --git a/+hw/SinePulseGenerator.m b/+hw/SinePulseGenerator.m new file mode 100644 index 00000000..95576183 --- /dev/null +++ b/+hw/SinePulseGenerator.m @@ -0,0 +1,45 @@ +classdef SinePulseGenerator < hw.ControlSignalGenerator + %HW.PULSESWITCHER Generates a train of pulses + % Detailed explanation goes here + + properties + Offset + end + + methods + function obj = SinePulseGenerator(offset) + obj.DefaultValue = 0; + obj.Offset = offset; + end + + function samples = waveform(obj, sampleRate, pars) + dt = pars(1); + + if numel(pars)==3 + f = pars(2); + amp = pars(3); + else + f = 40; + amp = 1; + end + + % first construct one cycle at this frequency + oneCycleDt = 1/f; + t = linspace(0, oneCycleDt - 1/sampleRate, sampleRate*oneCycleDt); + samples = amp/2*(-cos(2*pi*f*t) + 1); + + % if dt is greater than the duration of that cycle, then put zeros in + % the middle + if dt>oneCycleDt + nSamp = round(dt*sampleRate)-numel(samples); + m = round(numel(samples)/2); + samples = [samples(1:m) amp*ones(1,nSamp) samples(m+1:end)]; + end + + % add a zero so it turns off at the end + samples = [samples'; 0]; + end + end + +end + diff --git a/+hw/SineWaveGenerator.m b/+hw/SineWaveGenerator.m index 7cdefe60..a2fb1092 100644 --- a/+hw/SineWaveGenerator.m +++ b/+hw/SineWaveGenerator.m @@ -1,11 +1,13 @@ classdef SineWaveGenerator < hw.ControlSignalGenerator - %HW.PULSESWITCHER Generates a train of pulses - % Detailed explanation goes here - + %HW.SINEWAVEGENERATOR Generates a sinewave + % Outputs a signwave of a particular frequency, duration and phase. + % See also HW.DAQCONTROLLER + % + % Part of Rigbox properties - Frequency - Duration - Offset + Frequency % The frequency of the sinewave in Hz + Duration % The duration of the signwave in seconds + Offset % The phase of the sinewave end methods diff --git a/+hw/TLOutput.m b/+hw/TLOutput.m new file mode 100644 index 00000000..0d7dff65 --- /dev/null +++ b/+hw/TLOutput.m @@ -0,0 +1,54 @@ +classdef TLOutput < matlab.mixin.Heterogeneous & handle + %HW.TLOUTPUT Code to specify an output channel for timeline + % This is an abstract class. + % + % Below is a list of some subclasses and their functions: + % hw.TLOutputClock - clocked output on a counter channel + % hw.TLOutputChrono - the default, flip/flop status check output + % hw.TLOutputAcqLive - a digital channel that signals that + % aquisition has begun or ended with either a constant on signal or a + % brief pulse. + % + % The timeline object will call the init, start, process, and stop + % methods. Example: + % + % tl = hw.Timeline; + % tl.Outputs(1) = hw.TLOutputAcqLive('Instra-Triggar', 'Dev1', + % 'PFI4'); + % tl.start('2018-01-01_1_mouse2', alyxInstance); + % >> initializing Instra-Triggar + % >> start Instra-Triggar + % >> Timeline started successfully + % tl.stop; + % + % See Also HW.TLOutputChrono, HW.TLOutputAcqLive, HW.TLOutputClock + % + % Part of Rigbox + + % 2018-01 NS created + + properties + Name % The name of the timeline output, for easy identification + Enable = true % Will not do anything with it unless this is true + Verbose = false % Flag to output status updates. Initialization message outputs regardless of verbose. + end + + properties (Transient, Hidden, Access = protected) + Session % Holds an NI DAQ session object + end + + methods (Abstract) + % Called when timeline is initialized (see HW.TIMELINE/INIT), e.g. to open daq session and set parameters + init(obj, timeline) + % Called when timeline is started (see HW.TIMELINE/START), e.g. to start outputs + start(obj, timeline) + % Called every time Timeline processes a chunk of data, in case output needs to react to it + process(obj, timeline, event) + % Called when timeline is stopped (see HW.TIMELINE/STOP), to close and clean up + stop(obj, timeline) + % Returns a string that describes the object succintly + s = toStr(obj) + end + +end + diff --git a/+hw/TLOutputAcqLive.m b/+hw/TLOutputAcqLive.m new file mode 100644 index 00000000..6d2cd20e --- /dev/null +++ b/+hw/TLOutputAcqLive.m @@ -0,0 +1,153 @@ +classdef TLOutputAcqLive < hw.TLOutput + %HW.TLOUTPUTACQLIVE A digital signal that goes up when the recording starts, + % down when it ends. + % Used for triggaring external instruments during data aquisition. Will + % either output a constant high voltage signal while Timeline is + % running, or if obj.PulseDuration is set to a value > 0 and < Inf, the + % DAQ will output a pulse of that duration at the start and end of the + % aquisition. + % + % Example: + % tl = hw.Timeline; + % tl.Outputs(1) = hw.TLOutputAcqLive('Instra-Triggar', 'Dev1', 'PFI4'); + % tl.start('2018-01-01_1_mouse2', alyxInstance); + % >> initializing Instra-Triggar + % >> start Instra-Triggar + % >> Timeline started successfully + % tl.stop; + % + % See also HW.TLOUTPUT, HW.TIMELINE + % + % Part of Rigbox + % 2018-01 NS + + properties + DaqDeviceID % The name of the DAQ device ID, e.g. 'Dev1', see DAQ.GETDEVICES + DaqChannelID % The name of the DAQ channel ID, e.g. 'port1/line0', see DAQ.GETDEVICES + DaqVendor = 'ni' % Name of the DAQ vendor + InitialDelay double {mustBeNonnegative} = 0 % sec, time to wait before starting + PulseDuration {mustBeNonnegative} = Inf; % sec, time that the pulse is on at beginning and end + end + + properties (Transient, Access = private) + Timer + end + + methods + function obj = TLOutputAcqLive(hw) + % TLOUTPUTCHRONO Constructor method + % Can take the struct form of a previous instance (as saved in the + % Timeline hw struct) to intantiate a new object with the same + % properties. + % + % See Also HW.TIMELINE + if nargin + obj.Name = hw.Name; + obj.DaqDeviceID = hw.DaqDeviceID; + obj.DaqVendor = hw.DaqVendor; + obj.DaqChannelID = hw.DaqChannelID; + obj.InitialDelay = hw.InitialDelay; + obj.PulseDuration = hw.PulseDuration; + obj.Enable = hw.Enable; + obj.Verbose = hw.Verbose; + else % Some safe defaults + obj.Name = 'Acquire Live'; + obj.DaqDeviceID = 'Dev1'; + obj.DaqChannelID = 'port1/line2'; + end + end + + function init(obj, ~) + % INIT Initialize the output session + % INIT(obj, timeline) is called when timeline is initialized. + % Creates the DAQ session and ensures it is outputting a low + % (digital off) signal. + % + % See Also HW.TIMELINE/INIT + if obj.Enable + fprintf(1, 'initializing %s\n', obj.toStr); + obj.Session = daq.createSession(obj.DaqVendor); + % Turn off warning about clocked sampling availability + warning('off', 'daq:Session:onDemandOnlyChannelsAdded'); + % Add on-demand digital channel + obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); + warning('on', 'daq:Session:onDemandOnlyChannelsAdded'); + outputSingleScan(obj.Session, false); % start in the off/false state + % If the initial delay is greater than zero, create a timer for + % starting the signal late + if obj.InitialDelay > 0 + obj.Timer = timer('StartDelay', obj.InitialDelay); + obj.Timer.TimerFcn = @(~,~)obj.start(); + obj.Timer.StopFcn = @(src,~)delete(src); + end + end + end + + function start(obj, ~) + % START Output a high voltage signal + % Called when timeline is started, this outputs the first high + % voltage signal to triggar external instrument aquisition + % + % See Also HW.TIMELINE/START + if obj.Enable + % If the initial delay is greater than 0 and the timer is empty, + % create and start the timer + if ~isempty(obj.Timer) && obj.InitialDelay > 0 ... + && strcmp(obj.Timer.Running, 'off') + start(obj.Timer); % wait for some duration before starting + return + end + + if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end + outputSingleScan(obj.Session, true); % set digital output true: acquisition is "live" + if obj.PulseDuration ~= Inf + pause(obj.PulseDuration); + outputSingleScan(obj.Session, false); + end + end + end + + function process(~, ~, ~) + % PROCESS() Listener for processing acquired Timeline data + % PROCESS(obj, source, event) is a listener callback + % function for handling tl data acquisition. Called by the + % 'main' DAQ session with latest chunk of data. + % + % See Also HW.TIMELINE/PROCESS + %fprintf(1, 'process acqLive\n'); + % -- pass + end + + function stop(obj,~) + % STOP Stops the DAQ session object. + % Called when timeline is stopped. Outputs a low voltage signal, + % the stops and releases the session object. + % + % See Also HW.TIMELINE/STOP + if obj.Enable + % set digital output false: acquisition is no longer "live" + if obj.PulseDuration ~= Inf + outputSingleScan(obj.Session, true); + pause(obj.PulseDuration); + end + outputSingleScan(obj.Session, false); + + if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end + stop(obj.Session); + release(obj.Session); + obj.Session = []; + obj.Timer = []; + end + end + + function s = toStr(obj) + % TOSTR Returns a string that describes the object succintly + % + % See Also INIT + s = sprintf('"%s" on %s/%s (acqLive, initial delay %.2f, pulse duration %.2f)',... + obj.Name, obj.DaqDeviceID, obj.DaqChannelID, obj.InitialDelay, obj.PulseDuration); + end + end + +end + diff --git a/+hw/TLOutputChrono.m b/+hw/TLOutputChrono.m new file mode 100644 index 00000000..373cad10 --- /dev/null +++ b/+hw/TLOutputChrono.m @@ -0,0 +1,171 @@ +classdef TLOutputChrono < hw.TLOutput + %HW.TLOUTPUTCHRONO Principle output channel class which sets timeline clock offset + % Timeline uses this to monitor that acquisition is proceeding normally + % during a recording and to update the synchronization between the + % system time and the timeline time (to prevent drift between daq and + % computer clock). + % + % Example: + % tl = hw.Timeline; + % tl.Outputs(1) = hw.TLOutputChrono('Chrono', 'Dev1', 'PFI4'); + % tl.start('2018-01-01_1_mouse2', alyxInstance); + % >> initializing Chrono + % >> start Chrono + % >> Timeline started successfully + % tl.stop; + % + % See also HW.TLOUTPUT, HW.TIMELINE + % + % Part of Rigbox + % 2018-01 NS + + properties + DaqDeviceID % The name of the DAQ device ID, e.g. 'Dev1', see DAQ.GETDEVICES + DaqChannelID % The name of the DAQ channel ID, e.g. 'port1/line0', see DAQ.GETDEVICES + DaqVendor = 'ni' % Name of the DAQ vendor + NextChronoSign = 1 % The value to output on the chrono channel, the sign is changed each 'Process' event + end + + properties (SetAccess = private) + CurrSysTimeTimelineOffset = 0 % difference between the system time when the last chrono flip occured and the timestamp recorded by the DAQ, see tl.process() + LastClockSentSysTime % the mean of the system time before and after the last chrono flip. Used to calculate CurrSysTimeTimelineOffset, see tl.process() + end + + methods + function obj = TLOutputChrono(hw) + % TLOUTPUTCHRONO Constructor method + % Can take the struct form of a previous instance (as saved in the + % Timeline hw struct) to intantiate a new object with the same + % properties. + % + % See Also HW.TIMELINE + if nargin + obj.Name = hw.Name; + obj.DaqDeviceID = hw.DaqDeviceID; + obj.DaqVendor = hw.DaqVendor; + obj.DaqChannelID = hw.DaqChannelID; + obj.Enable = hw.Enable; + obj.Verbose = hw.Verbose; + else % Some safe defaults + obj.Name = 'Chrono'; + obj.DaqDeviceID = 'Dev1'; + obj.DaqChannelID = 'port1/line0'; + end + end + + function init(obj, timeline) + % INIT Initialize the output session + % INIT(obj, timeline) is called when timeline is initialized. + % Creates the DAQ session and ensures that the clocking pulse test + % can not be read back + % + % See Also HW.TIMELINE/INIT + if obj.Enable + fprintf(1, 'initializing %s\n', obj.toStr); + obj.Session = daq.createSession(obj.DaqVendor); + % Turn off warning about clocked sampling availability + warning('off', 'daq:Session:onDemandOnlyChannelsAdded'); + % Add on-demand digital channel + obj.Session.addDigitalChannel(obj.DaqDeviceID, obj.DaqChannelID, 'OutputOnly'); + warning('on', 'daq:Session:onDemandOnlyChannelsAdded'); + tls = timeline.getSessions('main'); + + %%Send a test pulse low, then high to clocking channel & check we read it back + idx = cellfun(@(s2)strcmp('chrono',s2), {timeline.Inputs.name}); + outputSingleScan(obj.Session, false) + x1 = tls.inputSingleScan; + outputSingleScan(obj.Session, true) + x2 = tls.inputSingleScan; + assert(x1(timeline.Inputs(idx).arrayColumn) < 2.5 && x2(timeline.Inputs(idx).arrayColumn) > 2.5,... + 'The clocking pulse test could not be read back'); + obj.CurrSysTimeTimelineOffset = GetSecs; % to initialize this, will be a bit off but fixed after the first pulse + end + end + + function start(obj, ~) + % START Starts the first chrono flip + % Called when timeline is started, this outputs the first low + % voltage output on the chrono output channel + % + % See Also HW.TIMELINE/START + if obj.Enable % If the object is to be used + if obj.Verbose; fprintf(1, 'start %s\n', obj.name); end + t = GetSecs; % system time before output + outputSingleScan(obj.Session, false) % this will be the clocking pulse detected the first time process is called + obj.LastClockSentSysTime = (t + GetSecs)/2; + end + end + + function process(obj, timeline, event) + % PROCESS Record the timestamp of last chrono flip, and output again + % OBJ.PROCESS(TIMELINE, EVENT) is called every time Timeline + % processes a chunk of data. The sign of the chrono signal is + % flipped on each call (at LastClockSentSysTime), and the time of + % the previous flip is found in the data and its timestamp noted. + % This is used by TL.TIME() to convert between system time and + % acquisition time. + % + % See Also HW.TIMELINE/TIME() and HW.TIMELINE/PROCESS + + if obj.Enable && timeline.IsRunning && ~isempty(obj.Session) + if obj.Verbose + fprintf(1, 'process %s\n', obj.Name); + end + + % The chrono "out" value is flipped at a recorded time, and the + % sample index that this flip is measured is noted First, find + % the index of the flip in the latest chunk of data + idx = elementByName(timeline.Inputs, 'chrono'); + clockChangeIdx = find(sign(event.Data(:,timeline.Inputs(idx).arrayColumn) - 2.5) == obj.NextChronoSign, 1); + + if obj.Verbose + fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... + obj.CurrSysTimeTimelineOffset, obj.LastClockSentSysTime); + end + + % Ensure the clocking pulse was detected + if ~isempty(clockChangeIdx) + clockChangeTimestamp = event.TimeStamps(clockChangeIdx); + obj.CurrSysTimeTimelineOffset = obj.LastClockSentSysTime - clockChangeTimestamp; + else + warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); + end + + % Now send the next clock pulse + obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono + t = GetSecs; % system time before output + outputSingleScan(obj.Session, obj.NextChronoSign > 0); % send next chrono flip + obj.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time + if obj.Verbose + fprintf(1, ' CurrOffset=%.2f, LastClock=%.2f\n', ... + obj.CurrSysTimeTimelineOffset, obj.LastClockSentSysTime); + end + end + end + + function stop(obj,~) + % STOP Stops the DAQ session object. + % Called when timeline is stopped. Stops and releases the + % session object. + % + % See Also HW.TIMELINE/STOP + if obj.Enable + if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end + stop(obj.Session); + release(obj.Session); + obj.Session = []; + end + end + + function s = toStr(obj) + % TOSTR Returns a string that describes the object succintly + % + % See Also HW.TIMELINE/INIT + s = sprintf('"%s" on %s/%s (chrono)', obj.Name, ... + obj.DaqDeviceID, obj.DaqChannelID); + end + + end + +end + diff --git a/+hw/TLOutputClock.m b/+hw/TLOutputClock.m new file mode 100644 index 00000000..74830732 --- /dev/null +++ b/+hw/TLOutputClock.m @@ -0,0 +1,143 @@ +classdef TLOutputClock < hw.TLOutput + %HW.TLOUTPUTCLOCK A regular pulse at a specified frequency and duty + % cycle. Can be used to trigger camera frames. + % + % Example: + % tl = hw.Timeline; + % tl.Outputs(end+1) = hw.TLOutputClock('Cam-Triggar', 'Dev1', 'PFI4'); + % tl.Outputs(end).InitialDelay = 5 % Add initial delay before start + % tl.start('2018-01-01_1_mouse2', alyxInstance); + % >> initializing Cam-Triggar + % >> start Cam-Triggar + % >> Timeline started successfully + % tl.stop; + % + % See also HW.TLOUTPUT, HW.TIMELINE + % + % Part of Rigbox + % 2018-01 NS + + properties + DaqDeviceID % The name of the DAQ device ID, e.g. 'Dev1', see DAQ.GETDEVICES + DaqChannelID % The name of the DAQ channel ID, e.g. 'ctr0', see DAQ.GETDEVICES + DaqVendor = 'ni' % Name of the DAQ vendor + InitialDelay double {mustBeNonnegative} = 0 % delay from session start to clock output + Frequency double = 60; % Hz, of the clocking pulse + DutyCycle double = 0.2; % proportion of each cycle that the pulse is "true" + end + + properties (Transient, Hidden, Access = protected) + ClockChan % Holds an instance of the PulseGeneration channel + Timer + end + + methods + function obj = TLOutputClock(hw) + % TLOUTPUTCHRONO Constructor method + % Can take the struct form of a previous instance (as saved in the + % Timeline hw struct) to intantiate a new object with the same + % properties. + % + % See Also HW.TIMELINE + if nargin + obj.Name = hw.Name; + obj.DaqDeviceID = hw.DaqDeviceID; + obj.DaqVendor = hw.DaqVendor; + obj.DaqChannelID = hw.DaqChannelID; + obj.InitialDelay = hw.InitialDelay; + obj.Frequency = hw.Frequency; + obj.DutyCycle = hw.DutyCycle; + obj.Enable = hw.Enable; + obj.Verbose = hw.Verbose; + else % Some safe defaults + obj.Name = 'Clock'; + obj.DaqDeviceID = 'Dev1'; + obj.DaqChannelID = 'ctr0'; + end + end + + function init(obj, ~) + % INIT Initialize the output session + % INIT(obj, timeline) is called when timeline is initialized. + % Creates the DAQ session and adds a PulseGeneration channel with + % the specified frequency, duty cycle and delay. + % + % See Also HW.TIMELINE/INIT + if obj.Enable + fprintf(1, 'initializing %s\n', obj.toStr); + obj.Session = daq.createSession(obj.DaqVendor); + obj.Session.IsContinuous = true; + clocked = obj.Session.addCounterOutputChannel(obj.DaqDeviceID, obj.DaqChannelID, 'PulseGeneration'); + clocked.Frequency = obj.Frequency; + clocked.DutyCycle = obj.DutyCycle; + clocked.InitialDelay = obj.InitialDelay; + obj.ClockChan = clocked; + + % If the initial delay is greater than zero, create a timer for + % starting the signal late + if obj.InitialDelay > 0 + obj.Timer = timer('StartDelay', obj.InitialDelay); + obj.Timer.TimerFcn = @(~,~)obj.start(); + obj.Timer.StopFcn = @(src,~)delete(src); + end + end + end + + function start(obj, ~) + % START Starts the clocking pulse + % Called when timeline is started, this uses STARTBACKGROUND to + % start the clocking pulse + % + % See Also HW.TIMELINE/START + if obj.Enable + % If the initial delay is greater than 0 and the timer is empty, + % create and start the timer + if ~isempty(obj.Timer) && obj.InitialDelay > 0 ... + && strcmp(obj.Timer.Running, 'off') + start(obj.Timer); % wait for some duration before starting + return + end + if obj.Verbose; fprintf(1, 'start %s\n', obj.Name); end + startBackground(obj.Session); + end + end + + function process(~, ~, ~) + % PROCESS() Listener for processing acquired Timeline data + % PROCESS(obj, source, event) is a listener callback + % function for handling tl data acquisition. Called by the + % 'main' DAQ session with latest chunk of data. + % + % See Also HW.TIMELINE/PROCESS + + %fprintf(1, 'process Clock\n'); + % -- pass + end + + function stop(obj,~) + % STOP Stops the DAQ session object. + % Called when timeline is stopped. Stops and releases the + % session object. + % + % See Also HW.TIMELINE/STOP + if obj.Enable + if obj.Verbose; fprintf(1, 'stop %s\n', obj.Name); end + stop(obj.Session); + release(obj.Session); + obj.Session = []; + obj.ClockChan = []; + obj.Timer = []; + end + end + + function s = toStr(obj) + % TOSTR Returns a string that describes the object succintly + % + % See Also INIT + s = sprintf('"%s" on %s/%s (clock, %dHz, %.2f duty cycle)', obj.Name, ... + obj.DaqDeviceID, obj.DaqChannelID, obj.Frequency, obj.DutyCycle); + end + end + +end + diff --git a/+hw/Timeline.m b/+hw/Timeline.m index 59db4bc4..e03e212a 100644 --- a/+hw/Timeline.m +++ b/+hw/Timeline.m @@ -1,36 +1,67 @@ -classdef (Sealed) Timeline < handle +classdef Timeline < handle % HW.TIMELINE Returns an object that generate and aquires clocking pulses % Timeline (tl) manages the aquisition and generation of experimental % timing data using an NI data aquisition device. The main timing signal % is called 'chrono' and consists of a digital squarewave that flips each % time a new chunk of data is availible from the DAQ (see % NotifyWhenDataAvailableExceeds for more information). A callback -% function to this event (see tl.process()) collects the timestamp from the -% DAQ of the precise scan where the chrono signal flipped. The +% function to this event (see tl.process()) collects the timestamp from +% the DAQ of the precise scan where the chrono signal flipped. The % difference between this and the system time recorded when the flip % command was given is recorded as the CurrSysTimeTimelineOffset and can % be used to unify all timestamps across computers during an experiment -% (see tl.time() and tl.ptbSecsToTimeline()). In is assumed that the -% time between sending the chrono pulse and recieving it is negligible. +% (see tl.time(), tl.ptbSecsToTimeline() and hw.TLOutputChrono). In is +% assumed that the time between sending the chrono pulse and recieving it +% is negligible. % -% There are two other available clocking signals: 'acqLive' and 'clock'. +% There are other available clocking signals, for instance: 'acqLive' and 'clock'. % The former outputs a high (+5V) signal the entire time tl is aquiring % (0V otherwise), and can be used to trigger devices with a TTL input. % The 'clock' output is a regular pulse at a frequency of % ClockOutputFrequency and duty cycle of ClockOutputDutyCycle. This can -% be used to trigger a camera at a specific frame rate. +% be used to trigger a camera at a specific frame rate. See "properties" below for +% further details on output configurations. % % Besides the chrono signal, tl can aquire any number of inputs and % record their values on the same clock. For example a photodiode to -% record the times at which the screen updates (see tl.addInput). +% record the times at which the screen updates (see tl.addInput). You +% can view the wiring information for any given channel by running +% wiringInfo(name). % % Timeline uses the PsychToolbox function GetSecs() to get the most % reliable system time (see GetSecs() and GetSecsTest()). NB: both the -% system time and the DAQ times can (and do) drift. +% system time and the DAQ times can (and do) drift. It also requires the +% Data Aquisition Toolbox and the JSONlab add-on. % -% TODO fix for AlyxInstance ref +% Example: setting up Timeline for the use with a Signals behavoural +% experiment +% %Open your hardware.mat file and instantiate a new Timeline object +% timeline = hw.Timeline; +% %Set tl to be started by default +% timeline.UseTimeline = true; +% %To set up chrono a wire must bridge the terminals defined in +% timeline.Outputs(1).DaqChannelID and timeline.Inputs(1).daqChannelID +% timeline.wiringInfo('chrono'); +% %Add the rotary encoder +% timeline.addInput('rotaryEncoder', 'ctr0', 'Position'); +% %For a lick detector +% timeline.addInput('lickDetector', 'ctr2', 'EdgeCount'); +% %We want use camera frame acquisition trigger by default +% timeline.UseOutputs{end+1} = 'clock'; +% %Save your hardware.mat file +% save('hardware.mat', 'timeline', '-append') +% +% TODO: +% - Register files to Alyx +% - Comment livePlot function +% - In future could implement option to only write to disk to avoid +% memory limitations when aquiring a lot of data +% - Delete local binary files once timeline has successfully saved to zserver? +% +% See also HW.TIMELINECLOCK, HW.TLOUTPUT % % Part of Rigbox + % 2014-01 CB created % 2017-10 MW updated @@ -39,81 +70,76 @@ DaqIds = 'Dev1' % Device ID can be found with daq.getDevices() DaqSampleRate = 1000 % rate at which daq aquires data in Hz, see Rate DaqSamplesPerNotify % determines the number of data samples to be processed each time, see Timeline.process(), constructor and NotifyWhenDataAvailableExceeds - Outputs % structure of outputs with their type, delays and ports, see constructor + Outputs = hw.TLOutputChrono % array of output classes, defining any signals you desire to be sent from the daq. See Also HW.TLOUTPUT, HW.TLOUTPUTCLOCK Inputs = struct('name', 'chrono',... 'arrayColumn', -1,... % -1 is default indicating unused, this is update when the channels are added during tl.start() 'daqChannelID', 'ai0',... 'measurement', 'Voltage',... - 'terminalConfig', 'SingleEnded') + 'terminalConfig', 'SingleEnded',... + 'axesScale', 1) % multiplicative vertical scaling for when live plotting the input UseInputs = {'chrono'} % array of inputs to record while tl is running - UseOutputs = {'chrono'} % array of output pulses to use while tl is running StopDelay = 2 % currently pauses for at least 2 secs as 'hack' before stopping main DAQ session - MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) - ClockOutputFrequency = 60 % if using 'clock' output, this specifies the frequency of pulses (Hz) - ClockOutputDutyCycle = 0.2 % if using 'clock' output, this specifies the duty cycle (as a fraction) + MaxExpectedDuration = 2*60*60 % expected experiment time so data structure is initialised to sensible size (in secs) AquiredDataType = 'double' % default data type for the acquired data array (i.e. Data.rawDAQData) UseTimeline = false % used by expServer. If true, timeline is started by default (otherwise can be toggled with the t key) + LivePlot = false % if true the data are plotted as the data are aquired + FigureScale = []; % figure position in normalized units, default is [0 0 1 1] (full screen) + WriteBufferToDisk = false % if true the data buffer is written to disk as they're aquired NB: in the future this will happen by default end - - properties (SetAccess = private) - IsRunning = false % flag is set to true when the first chrono pulse is aquired and set to false when tl is stopped, see tl.process and tl.stop - end - + properties (Dependent) SamplingInterval % defined as 1/DaqSampleRate + IsRunning = false % flag is set to true when the first chrono pulse is aquired and set to false when tl is stopped (and everything saved), see tl.process and tl.stop end - properties (Transient, Access = private) + properties (Transient, Access = protected) Listener % holds the listener for 'DataAvailable', see DataAvailable and Timeline.process() - Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() - CurrSysTimeTimelineOffset % difference between the system time when the last chrono flip occured and the timestamp recorded by the DAQ, see tl.process() + Sessions = containers.Map % map of daq sessions and their channels, created at tl.start() LastTimestamp % the last timestamp returned from the daq during the DataAvailable event. Used to check sampling continuity, see tl.process() - LastClockSentSysTime % the mean of the system time before and after the last chrono flip. Used to calculate CurrSysTimeTimelineOffset, see tl.process() - NextChronoSign = 1 % the value to output on the chrono channel, the sign is changed each 'DataAvailable' event (DaqSamplesPerNotify) - Ref % the expRef string, concatenated with the AlyxInstance used when timeline was started (if user was logged in). See tl.start() + Ref % the expRef string. See tl.start() + AlyxInstance % a struct contraining the Alyx token, user and url for ile registration. See tl.start() Data % A structure containing timeline data + Axes % A figure handle for plotting the aquired data as it's processed + DataFID % The data file ID for writing aquired data directly to disk end methods - function obj = Timeline() - % Constructor method + function obj = Timeline(hw) + % TIMELINE Constructor method + % HW.TIMELINE(hw) constructor can take a timeline hardware + % structure as an input, replicating a previous instance. % Adds chrono, aquireLive and clock to the outputs list, % along with default ports and delays - obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify - defaultOutputs = {'chrono', 'acqLive', 'clock';... % names of each output - 'port1/line0', 'port0/line1', 'ctr3';... % their default ports - 'OutputOnly', 'OutputOnly', 'PulseGeneration'; % default output type - 0, 0, 0}; % the initial delay (useful for ensure all systems are ready) - obj.Outputs = cell2struct(defaultOutputs, {'name', 'daqChannelID', 'type', 'initialDelay'}); + + obj.DaqSamplesPerNotify = 1/obj.SamplingInterval; % calculate DaqSamplesPerNotify + if nargin % if old tl hardware struct provided, use these to populate properties + % Configure the inputs + obj.Inputs = hw.inputs; + obj.DaqVendor = hw.daqVendor; + obj.DaqIds = hw.daqDevice; + obj.DaqSampleRate = hw.daqSampleRate; + obj.DaqSamplesPerNotify = hw.daqSamplesPerNotify; + % Configure the outputs + outputs = catStructs(hw.Outputs); + obj.Outputs = objfun(@(o)eval([o.Class '(o)']), outputs, 'Uni', false); + obj.Outputs = [obj.Outputs{:}]; + end end - function start(obj, expRef, varargin) - % Starts tl data acquisition - % TL.START(obj, expRef, [disregardInputs]) starts all DAQ - % sessions and adds the relevent output and input channels. - % 'disregardInputs' is a cell array of input names (e.g. - % 'rotaryEncoder' that temporarily not aquired if used by - % other sessions. For example to turn off rotary encoder - % recording in tl so the experiment object can access it - if nargin > 2 - disregardInputs = ensureCell(varargin{1}); - else; disregardInputs = {}; - end + function start(obj, expRef, Alyx) + % START Starts timeline data acquisition + % START(obj, ref, Alyx) starts all DAQ sessions and adds + % the relevent output and input channels. + % + % See Also HW.TLOUTPUT/START + if obj.IsRunning % check if it's already running, and if so, stop it disp('Timeline already running, stopping first'); obj.stop(); end obj.Ref = expRef; % set the current experiment ref - init(obj, disregardInputs); % start the relevent sessions and add channels - - %%Send a test pulse low, then high to clocking channel & check we read it back - idx = cellfun(@(s2)strcmp('chrono',s2), {obj.Inputs.name}); - outputSingleScan(obj.Sessions('chrono'), false) - x1 = obj.Sessions('main').inputSingleScan; - outputSingleScan(obj.Sessions('chrono'), true) - x2 = obj.Sessions('main').inputSingleScan; - assert(x1(obj.Inputs(idx).arrayColumn) < 2.5 && x2(obj.Inputs(idx).arrayColumn) > 2.5,... - 'The clocking pulse test could not be read back'); + obj.AlyxInstance = Alyx; % set the current instance of Alyx + init(obj); % start the relevent sessions and add channels obj.Listener = obj.Sessions('main').addlistener('DataAvailable', @obj.process); % add listener @@ -121,14 +147,28 @@ function start(obj, expRef, varargin) numSamples = obj.DaqSampleRate*obj.MaxExpectedDuration; channelDirs = io.daqSessionChannelDirections(obj.Sessions('main')); numInputChannels = sum(strcmp(channelDirs, 'Input')); + + obj.Data.savePaths = dat.expFilePath(expRef, 'timeline'); + %find the local path to save the data to file during aquisition + if obj.WriteBufferToDisk + fprintf(1, 'opening binary file for writing\n'); + localPath = dat.expFilePath(expRef, 'timeline', 'local'); % get the local exp data path + if ~dat.expExists(expRef); mkdir(fileparts(localPath)); end % if the folder doesn't exist, create it + obj.DataFID = fopen([localPath(1:end-4) '.dat'], 'w'); % open a binary data file + % save params now so if things crash later you at least have this record of the data type and size so you can load the dat + parfid = fopen([localPath(1:end-4) '.par'], 'w'); % open a parameter file + fprintf(parfid, 'type = %s\n', obj.AquiredDataType); % record the data type + fprintf(parfid, 'nChannels = %d\n', numInputChannels); % record the number of channels + fprintf(parfid, 'Fs = %d\n', obj.DaqSampleRate); % record the DAQ sample date + fclose(parfid); % close the file + end + obj.Data.rawDAQData = zeros(numSamples, numInputChannels, obj.AquiredDataType); obj.Data.rawDAQSampleCount = 0; obj.Data.startDateTime = now; obj.Data.startDateTimeStr = datestr(obj.Data.startDateTime); - %%Start the DAQ acquiring - outputSingleScan(obj.Sessions('chrono'), false) % make sure chrono is low %LastTimestamp is the timestamp of the last acquisition sample, which is %saved to ensure continuity of acquisition. Here it is initialised as if a %previous acquisition had been made in negative time, since the first @@ -136,38 +176,23 @@ function start(obj, expRef, varargin) obj.LastTimestamp = -obj.SamplingInterval; startBackground(obj.Sessions('main')); % start aquisition - %%Output clocking pulse and wait for first acquisition to complete - % output first clocking high pulse - t = GetSecs; %system time before outputting chrono flip - outputSingleScan(obj.Sessions('chrono'), obj.NextChronoSign > 0); % flip chrono signal - obj.LastClockSentSysTime = (t + GetSecs)/2; % log mean before/after system time - % wait for first acquisition processing to begin - while ~obj.IsRunning - pause(5e-3); - end + while ~obj.IsRunning; pause(5e-3); end - if isKey(obj.Sessions, 'acqLive') % is acqLive being used? - % set acquisition live signal to true - pause(obj.Outputs(cellfun(@(s2)strcmp('chrono',s2), {obj.Outputs.name})).delay); - outputSingleScan(obj.Sessions('acqLive'), true); - end - if isKey(obj.Sessions, 'clock') % is the clock output being used? - % start session to send timing output pulses - startBackground(obj.Sessions('clock')); - end + % Start each output + arrayfun(@start, obj.Outputs) % Report success fprintf('Timeline started successfully for ''%s''.\n', expRef); end - function record(obj, name, event, time) + function record(obj, name, event, t) % Records an event in Timeline % TL.RECORD(name, event, [time]) records an event in the Timeline % struct in fields prefixed with 'name', with data in 'event'. Optionally % specify 'time', otherwise the time of call will be used (relative to % Timeline acquisition). - if nargin < 3; time = time(obj); end % default to time now (using Timeline clock) + if nargin < 4; t = time(obj); end % default to time now (using Timeline clock) initLength = 100; % default initial length of event data arrays timesFieldName = [name 'Times']; @@ -197,21 +222,23 @@ function record(obj, name, event, time) end %%store the event at the appropriate index - obj.Data.(timesFieldName)(newCount) = time; + obj.Data.(timesFieldName)(newCount) = t; obj.Data.(eventFieldName){newCount} = event; obj.Data.(countFieldName) = newCount; end function secs = time(obj, strict) - % Time relative to Timeline acquisition + % TIME Time relative to Timeline acquisition % secs = TL.TIME([strict]) Returns the time in seconds relative to % Timeline data acquistion. 'strict' is optional (defaults to true), and % if true, this function will fail if Timeline is not running. If false, % it will just return the time using Psychtoolbox GetSecs if it's not - % running. See also TL.PTBSECSTOTIMELINE(). - if nargin < 1; strict = true; end + % running. + % See also TL.PTBSECSTOTIMELINE(). + if nargin < 2; strict = true; end if obj.IsRunning - secs = GetSecs - obj.CurrSysTimeTimelineOffset; + idx = arrayfun(@(out)isa(out, 'hw.TLOutputChrono'), obj.Outputs); + secs = GetSecs - obj.Outputs(idx).CurrSysTimeTimelineOffset; elseif strict error('Tried to use Timeline clock when Timeline is not running'); else @@ -222,17 +249,19 @@ function record(obj, name, event, time) end function secs = ptbSecsToTimeline(obj, secs) - % Convert from Pyschtoolbox to Timeline time + % PTBSECSTOTIMELINE Convert from Pyschtoolbox to Timeline time % secs = TL.PTBSECSTOTIMELINE(secs) takes a timestamp 'secs' obtained % from Pyschtoolbox's functions and converts to Timeline-relative time. % See also TL.TIME(). assert(obj.IsRunning, 'Timeline is not running.'); - secs = secs - obj.CurrSysTimeTimelineOffset; + idx = arrayfun(@(out)isa(out, 'hw.TLOutputChrono'), obj.Outputs); + secs = secs - obj.Outputs(idx).CurrSysTimeTimelineOffset; end - function addInput(obj, name, channelID, measurement, terminalConfig, use) + function addInput(obj, name, channelID, measurement,... + terminalConfig, axesScale, use) % Add a new input to the object's Input property - % TL.ADDINPUT(name, channelID, measurement, terminalConfig, use) + % ADDINPUT(name, channelID, measurement, terminalConfig, use) % adds a new input 'name' to the Inputs list. If use is % true, the input is also added to the UseInputs array. @@ -240,8 +269,11 @@ function addInput(obj, name, channelID, measurement, terminalConfig, use) % DAQ default for that port if nargin < 5; terminalConfig = []; end + % if use is not specified, assume user wants normal scaling + if nargin < 6; axesScale = 1; end + % if use is not specified, assume user wants to record input - if nargin < 6; use = true; end + if nargin < 7; use = true; end assert(~any(strcmp(name, {obj.Inputs.name})),... 'An input by the name of ''%s'' has already been added.', name); @@ -268,7 +300,8 @@ function addInput(obj, name, channelID, measurement, terminalConfig, use) 'arrayColumn', -1,... % -1 is default indicating unused 'daqChannelID', channelID,... 'measurement', measurement,... - 'terminalConfig', terminalConfig); + 'terminalConfig', terminalConfig,... + 'axesScale', axesScale); obj.Inputs = [obj.Inputs s]; % add the new input if use; obj.UseInputs = [obj.UseInputs {name}]; end % add to UseInputs @@ -276,46 +309,78 @@ function addInput(obj, name, channelID, measurement, terminalConfig, use) fprintf('Timeline input ''%s'' successfully added.\n', name); end + function wiringInfo(obj, name) + % WIRINGINFO Return information about how the input/output + % 'name' is wired. If no name is provided, the different port + % naming conventions of the NI DAQ are returned. + if nargin < 2 + fprintf('For NI USB-6211 the following ports are by default equivelant:\n') + fprintf('PFI0-3 = port0/line0-3 = ctr0-3\n') + fprintf('PFI4-7 = port1/line0-3\n') + fprintf('ctr0-3 = port1/line0-3\n') + else + outputClasses = arrayfun(@class, obj.Outputs, 'uni', false); + if strcmp(name, 'chrono') % Chrono wiring info + idI = cellfun(@(s2)strcmp('chrono',s2), {obj.Inputs.name}); + idO = find(cellfun(@(s2)strcmp('tlOutputChrono',s2), outputClasses),1); + fprintf('Bridge terminals %s and %s\n',... + obj.Outputs(idO).daqChannelID, obj.Inputs(idI).daqChannelID) + elseif any(strcmp(name, {obj.Outputs.name})) % Output wiring info + idx = cellfun(@(s2)strcmp(name,s2), {obj.Outputs.name}); + fprintf('Connect device to terminal %s of the DAQ\n',... + obj.Outputs(idx).daqChannelID) + elseif any(strcmp(name, {obj.Inputs.name})) % Input wiring info + idx = cellfun(@(s2)strcmp(name,s2), {obj.Inputs.name}); + fprintf('Connect device to terminal %s of the DAQ\n',... + obj.Inputs(idx).daqChannelID) + else + fprintf('No inputs or outputs of that name were found\n') + end + end + end + function v = get.SamplingInterval(obj) + %GET.SAMPLINGINTERVAL Defined as the reciprocal of obj.DaqSampleRate v = 1/obj.DaqSampleRate; end + function bool = get.IsRunning(obj) + % TL.ISRUNNING Determine whether tl is running. + % timeline is officially 'running' when first acquisition + % samples are in, i.e. the raw sample count is greater than 0 + if isfield(obj.Data, 'rawDAQSampleCount')&&... + obj.Data.rawDAQSampleCount > 0 + % obj.Data.rawDAQSampleCount is greater than 0 during the first call to tl.process + bool = true; + else % obj.Data is cleared in tl.stop, after all data are saved + bool = false; + end + end + function stop(obj) %TL.STOP Stops Timeline data acquisition % TL.STOP() Deletes the listener, saves the aquired data, % stops all running DAQ sessions % + % See Also HW.TLOUTPUT/STOP if ~obj.IsRunning warning('Nothing to do, Timeline is not running!') return end - % kill acquisition output signals - if isKey(obj.Sessions, 'acqLive') - obj.Sessions('acqLive').outputSingleScan(false); % live -> false - end - for i = 1:length(obj.UseOutputs) - name = obj.UseOutputs{i}; - stop(obj.Sessions(name)); - end - pause(obj.StopDelay) + + % stop acquisition output signals + arrayfun(@stop, obj.Outputs) % stop actual DAQ aquisition stop(obj.Sessions('main')); % wait before deleting the listener to ensure most recent samples are % collected pause(1.5); - delete(obj.Listener) % now delete the data listener - - % turn off the timeline running flag - obj.IsRunning = false; + delete(obj.Listener) % now delete the data listener - % release hardware resources - sessions = keys(obj.Sessions); % find names of all current sessions - for i = 1:length(sessions) - name = sessions{i}; - release(obj.Sessions(name)); - end + % only keep the used part of the daq input array + obj.Data.rawDAQData((obj.Data.rawDAQSampleCount + 1):end,:) = []; % generate timestamps in seconds for the samples obj.Data.rawDAQTimestamps = ... @@ -324,75 +389,140 @@ function stop(obj) % replicate old tl data struct for legacy code idx = cellfun(@(s2)strcmp('chrono',s2), {obj.Inputs.name}); arrayChronoColumn = obj.Inputs(idx).arrayColumn; - savePaths = dat.expFilePath(obj.Ref, 'timeline'); %TODO fix for AlyxInstance ref + inputsIdx = cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs); + + % this block finds the daqChannelID for chrono and acqLive if + % they exist, plus some clock parameters - all for legacy + % metadata saving, see below + outputClasses = arrayfun(@class, obj.Outputs, 'uni', false); + chronoChan = []; nextChrono = []; acqLiveChan = []; useClock = false; clockF = []; clockD = []; + LastClockSentSysTime = []; CurrSysTimeTimelineOffset = []; + chronoOutputIdx = find(strcmp(outputClasses, 'hw.TLOutputChrono'),1); + if ~isempty(chronoOutputIdx) + chronoChan = obj.Outputs(chronoOutputIdx).DaqChannelID; + nextChrono = obj.Outputs(chronoOutputIdx).NextChronoSign; + LastClockSentSysTime = obj.Outputs(chronoOutputIdx).LastClockSentSysTime; + CurrSysTimeTimelineOffset = obj.Outputs(chronoOutputIdx).CurrSysTimeTimelineOffset; + end + acqLiveOutputIdx = find(strcmp(outputClasses, 'hw.TLOutputAcqLive'),1); + if ~isempty(acqLiveOutputIdx) + acqLiveChan = obj.Outputs(acqLiveOutputIdx).DaqChannelID; + end + clockOutputIdx = find(strcmp(outputClasses, 'hw.TLOutputClock'),1); + if ~isempty(clockOutputIdx) + useClock = true; + clockF = obj.Outputs(clockOutputIdx).Frequency; + clockD = obj.Outputs(clockOutputIdx).DutyCycle; + end + + % legacy metadata obj.Data.hw = struct('daqVendor', obj.DaqVendor, 'daqDevice', obj.DaqIds,... 'daqSampleRate', obj.DaqSampleRate, 'daqSamplesPerNotify', obj.DaqSamplesPerNotify,... - 'chronoOutDaqChannelID', obj.Outputs(1).daqChannelID, 'acqLiveOutDaqChannelID', obj.Outputs(2).daqChannelID,... - 'useClockOutput', any(strcmp('clock', obj.UseOutputs)), 'clockOutputFrequency ', obj.ClockOutputFrequency,... - 'clockOutputDutyCycle', obj.ClockOutputDutyCycle, 'samplingInterval', obj.SamplingInterval,... - 'inputs', obj.Inputs, 'arrayChronoColumn', arrayChronoColumn); - obj.Data.expRef = obj.Ref; %TODO fix for AlyxInstance ref - obj.Data.savePaths = savePaths; + 'chronoOutDaqChannelID', chronoChan, 'acqLiveOutDaqChannelID', acqLiveChan,... + 'useClockOutput', useClock, 'clockOutputFrequency', clockF,... + 'clockOutputDutyCycle', clockD, 'samplingInterval', obj.SamplingInterval,... + 'inputs', obj.Inputs(inputsIdx), ... % find the correct inputs, in the correct order + 'arrayChronoColumn', arrayChronoColumn); + obj.Data.expRef = obj.Ref; % save experiment ref obj.Data.isRunning = obj.IsRunning; - obj.Data.nextChronoSign = obj.NextChronoSign; + obj.Data.nextChronoSign = nextChrono; obj.Data.lastTimestamp = obj.LastTimestamp; - obj.Data.lastClockSentSysTime = obj.LastClockSentSysTime; - obj.Data.currSysTimeTimelineOffset = obj.CurrSysTimeTimelineOffset; + obj.Data.lastClockSentSysTime = LastClockSentSysTime; + obj.Data.currSysTimeTimelineOffset = CurrSysTimeTimelineOffset; + + % saving hardware metadata for each output + warning('off', 'MATLAB:structOnObject'); % sorry, don't care + for outIdx = 1:numel(obj.Outputs) + s = struct(obj.Outputs(outIdx)); + s.Class = class(obj.Outputs(outIdx)); + obj.Data.hw.Outputs{outIdx} = s; + end + warning('on', 'MATLAB:structOnObject'); % save tl to all paths - superSave(savePaths, struct('Timeline', obj.Data)); % TODO replicate old tl struct + superSave(obj.Data.savePaths, struct('Timeline', obj.Data)); + + % write hardware info to a JSON file for compatibility with database + if exist('savejson', 'file') + % save local copy + savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{1}), 'TimelineHW.json')); + % save server copy + savejson('hw', obj.Data.hw, fullfile(fileparts(obj.Data.savePaths{2}), 'TimelineHW.json')); + else + warning('JSONlab not found - hardware information not saved to ALF') + end + + % save each recorded vector into the correct format in Timeline + % timebase for Alyx and optionally into universal timebase if + % conversion is provided + if ~isempty(which('alf.timelineToALF'))&&~isempty(which('writeNPY')) + alf.timelineToALF(obj.Data, [],... + fileparts(dat.expFilePath(obj.Data.expRef, 'timeline', 'master'))) + else + warning('did not write files into alf format. Check that alyx-matlab and npy-matlab repositories are in path'); + end + + % register Timeline.mat file to Alyx database + [subject,~,~] = dat.parseExpRef(obj.Data.expRef); + if ~isempty(obj.AlyxInstance) && ~strcmp(subject,'default') + try + alyx.registerFile(obj.Data.savePaths{end}, 'mat',... + obj.AlyxInstance.subsessionURL, 'Timeline', [], obj.AlyxInstance); + catch + warning('couldn''t register files to alyx'); + end + end + %TODO: Register ALF components to alyx, incl TimelineHW.json + + % delete data from memory, tl is now officially no longer running + obj.Data = []; % reset arrayColumn fields [obj.Inputs.arrayColumn] = deal(-1); + % delete the figure axes, if necessary + if obj.LivePlot; close(get(obj.Axes, 'Parent')); obj.Axes = []; end + + % close binary file, if necessary + if ~isempty(obj.DataFID); fclose(obj.DataFID); end + % Report successful stop fprintf('Timeline for ''%s'' stopped and saved successfully.\n', obj.Ref); end + + function s = getSessions(obj, name) + % GETSESSIONS() Returns the Sessions property + % returns the Sessions property. Some things (e.g. output + % classes) need this. + % + % See Also HW.TLOUTPUT + s = obj.Sessions(name); + end + end methods (Access = private) - function init(obj, disregardInputs) + function init(obj) % Create DAQ session and add channels - % TL.INIT(disregardInputs) creates all the DAQ sessions + % TL.INIT() creates all the DAQ sessions % and stores them in the Sessions map by their Outputs name. % Also add a 'main' session to which all input channels are - % added. See daq.createSession - - %%Create session objects for chrono and other outputs - [use, idx] = intersect({obj.Outputs.name}, obj.UseOutputs); % find which outputs to use -% assert(numel(idx) == numel(obj.UseOutputs), 'Not all outputs were recognised'); - for i = 1:length(use) - out = obj.Outputs(idx(i)); % get channel info, etc. - switch use{i} - case 'chrono' - obj.Sessions('chrono') = daq.createSession(obj.DaqVendor); - obj.Sessions('chrono').addDigitalChannel(obj.DaqIds, out.daqChannelID, out.type); - - case 'acqLive' - obj.Sessions('acqLive') = daq.createSession(obj.DaqVendor); - obj.Sessions('acqLive').addDigitalChannel(obj.DaqIds, out.daqChannelID, out.type); - obj.Sessions('acqLive').outputSingleScan(false); % ensure acq live is false - - case 'clock' - obj.Sessions('clock') = daq.createSession(obj.DaqVendor); - obj.Sessions('clock').IsContinuous = true; - clocked = obj.Sessions('clock').addCounterOutputChannel(obj.DaqIds, out.daqChannelID, out.type); - clocked.Frequency = obj.ClockOutputFrequency; - clocked.DutyCycle = obj.ClockOutputDutyCycle; - clocked.InitialDelay = out.delay; - end - end - %%Create channels for each input + % added. + % + % See Also DAQ.CREATESESSION + + %%reate channels for each input [use, idx] = intersect({obj.Inputs.name}, obj.UseInputs);% find which inputs to use assert(numel(idx) == numel(obj.UseInputs), 'Not all inputs were recognised'); - inputSession = daq.createSession(obj.DaqVendor); - inputSession.Rate = obj.DaqSampleRate; + inputSession = daq.createSession(obj.DaqVendor); %create DAQ session for input aquisition + inputSession.Rate = obj.DaqSampleRate; % set the aquisition sample rate inputSession.IsContinuous = true; % once started, continue acquiring until manually stopped inputSession.NotifyWhenDataAvailableExceeds = obj.DaqSamplesPerNotify; % when to process data obj.Sessions('main') = inputSession; for i = 1:length(use) - if any(strcmp(use{i}, disregardInputs)); continue; end - in = obj.Inputs(idx(i)); % get channel info, etc. + in = obj.Inputs(strcmp({obj.Inputs.name}, obj.UseInputs(i))); + fprintf(1, 'adding channel %s on %s\n', in.name, in.daqChannelID); + switch in.measurement case 'Voltage' ch = obj.Sessions('main').addAnalogInputChannel(obj.DaqIds, in.daqChannelID, in.measurement); @@ -406,12 +536,15 @@ function init(obj, disregardInputs) % we assume quadrature encoding (X4) for position measurement ch.EncoderType = 'X4'; end - obj.Inputs(idx(i)).arrayColumn = i; + obj.Inputs(strcmp({obj.Inputs.name}, obj.UseInputs(i))).arrayColumn = i; end + + %Initialize outputs + arrayfun(@(out)out.init(obj), obj.Outputs) end function process(obj, ~, event) - % Listener for processing acquired Timeline data + % PROCESS() Listener for processing acquired Timeline data % TL.PROCESS(source, event) is a listener callback % function for handling tl data acquisition. Called by the % 'main' DAQ session with latest chunk of data. This is @@ -425,22 +558,18 @@ function process(obj, ~, event) % LastTimestamp is the time of the last scan in the previous % data chunk, and is used to ensure no data samples have been % lost. - - % timeline is officially 'running' when first acquisition samples are in - if ~obj.IsRunning; obj.IsRunning = true; end + % + % See Also HW.TLOUTPUT/PROCESS - % assert continuity of this data from previous + %Assert continuity of this data from previous assert(abs(event.TimeStamps(1) - obj.LastTimestamp - obj.SamplingInterval) < 1e-8,... 'Discontinuity of DAQ acquistion detected: last timestamp was %f and this one is %f',... obj.LastTimestamp, event.TimeStamps(1)); + + %Process methods for outputs + arrayfun(@(out)out.process(obj, event), obj.Outputs); - %Now send the next clock pulse - obj.NextChronoSign = -obj.NextChronoSign; % flip next chrono - t = GetSecs; % system time before output - outputSingleScan(obj.Sessions('chrono'), obj.NextChronoSign > 0); % send next chrono flip - obj.LastClockSentSysTime = (t + GetSecs)/2; % record mean before/after system time - - %%Store new samples into the timeline array + %Store new samples into the timeline array prevSampleCount = obj.Data.rawDAQSampleCount; newSampleCount = prevSampleCount + size(event.Data, 1); @@ -456,7 +585,79 @@ function process(obj, ~, event) %Update continuity timestamp for next check obj.LastTimestamp = event.TimeStamps(end); + % if writing to binary file, save data there + if obj.WriteBufferToDisk && ~isempty(obj.DataFID) + datToWrite = cast(event.Data, obj.AquiredDataType); % Ensure data are the correct type + fwrite(obj.DataFID, datToWrite', obj.AquiredDataType); % Write to file + end + + %If plotting the channels live, plot the new data + if obj.LivePlot; obj.livePlot(event.Data); end end + function livePlot(obj, data) + % Plot the data scans as they're aquired + % TL.LIVEPLOT(source, event) plots the data aquired by the + % DAQ while the PlotLive property is true. + if isempty(obj.Axes) + f = figure('Units', 'Normalized', 'Position', [0 0 1 1]); % create a figure for plotting aquired data + obj.Axes = gca; % store a handle to the axes + if isprop(obj, 'FigurePosition') && ~isempty(obj.FigurePosition) + set(f, 'Position', obj.FigurePosition); % set the figure position + end + end + + % get the names of the inputs being recorded (in the correct + % order) + names = pick({obj.Inputs.name}, cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs), 'cell'); + nSamps = size(data,1); % Get the number of samples in this chunck + nChans = size(data,2); % Get the number of channels + traceSep = 7; % unit is Volts - for most channels the max is 5V so this is a good separation + offsets = (1:nChans)*traceSep; + + % scales control a vertical scaling of each trace + % (multiplicative) and can be set manually in the config. A + % nicer future version would put a scroll wheel callback on the + % figure and scale by scrolling the one that's hovered over + scales = pick([obj.Inputs.axesScale], cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs)); + + traces = get(obj.Axes, 'Children'); + if isempty(traces) + Fs = obj.DaqSampleRate; + plot((1:Fs*10)/Fs, zeros(Fs*10, length(names))+repmat(offsets, Fs*10, 1)); + traces = get(obj.Axes, 'Children'); + set(obj.Axes, 'YTick', offsets); + set(obj.Axes, 'YTickLabel', names); + end + + % get the measurement type of each channel, since Position-type + % inputs are plotted differently. + meas = pick({obj.Inputs.measurement}, cellfun(@(x)find(strcmp({obj.Inputs.name}, x),1), obj.UseInputs), 'cell'); + + for t = 1:length(traces) + if strcmp(meas{t}, 'Position') + % if a position sensor (i.e. rotary encoder) scale + % by the first point and allow negative values + if any(data(:,t)>2^31); data(data(:,t)>2^31,t) = data(data(:,t)>2^31,t)-2^32; end + data(:,t) = data(:,t)-data(1,t); + end + yy = get(traces(end-t+1), 'YData'); % get current data for trace + yy(1:end-nSamps) = yy(nSamps+1:end); % add the new chuck for channel + % scale and offset the traces + if strcmp(meas{t}, 'Position') + % for position-type inputs, plot velocity (take the + % diff, and smooth) rather than absolute. this is + % necessary to prevent the value from wandering way off + % the range and making it impossible to see any of the + % other traces. Plus it is probably more useful, + % anyway. + yy(end-nSamps+1:end) = conv(diff([data(1,t); data(:,t)]),... + gausswin(50)./sum(gausswin(50)), 'same') * scales(t) + offsets(t); + else + yy(end-nSamps+1:end) = data(:,t)*scales(t)+offsets(t); + end + set(traces(end-t+1), 'YData', yy); % replot with the new data + end + end end end \ No newline at end of file diff --git a/+hw/TimelineClock.m b/+hw/TimelineClock.m index eb3d0d3c..6df0ed6c 100644 --- a/+hw/TimelineClock.m +++ b/+hw/TimelineClock.m @@ -7,10 +7,30 @@ % 2013-01 CB created + properties + Timeline % Handle to rig timeline object + end + + methods + + function obj = TimelineClock(tl) + if nargin + obj.Timeline = tl; + end + end + + end + methods (Access = protected) + function t = absoluteTime(obj) - t = tl.time; + if isempty(obj.Timeline) + t = tl.time; + else + t = obj.Timeline.time(); + end end + end end \ No newline at end of file diff --git a/+hw/WeighingScale.m b/+hw/WeighingScale.m index 0a14701e..1a2630a0 100644 --- a/+hw/WeighingScale.m +++ b/+hw/WeighingScale.m @@ -1,6 +1,8 @@ classdef WeighingScale < handle %HW.WEIGHINGSCALE Interface to a weighing scale connected via serial - % Allows you to read the current weight from scales and tare it. + % Allows you to read the current weight from scales and tare it. This + % class has been tested only with the ES-300HA 300gx0.01g Precision + % Scale + RS232 to USB Converter. % % Part of Rigbox diff --git a/+hw/calibrate.m b/+hw/calibrate.m index 6bdbc0e5..50cc778f 100644 --- a/+hw/calibrate.m +++ b/+hw/calibrate.m @@ -1,6 +1,9 @@ function calibration = calibrate(channel, rewardController, scales, tMin, tMax) %HW.CALIBRATE Performs measured reward deliveries for calibration -% TODO. This needs sanitising and incoporating into HW.REWARDCONTROLLER +% This function is used by srv.expServer to return a water calibration. It still requires some scales to be attached to +% the computer. TODO: Sanitize and integrate into HW.REWARDVALVECONTROL +% +% See also HW.REWARDVALVECONTROL % % Part of Rigbox diff --git a/+hw/devices.m b/+hw/devices.m index 098dbbf5..82598ab6 100644 --- a/+hw/devices.m +++ b/+hw/devices.m @@ -34,8 +34,11 @@ end rig = load(fn); rig.name = name; -rig.useTimeline = pick(rig, 'useTimeline', 'def', false); -rig.clock = iff(rig.useTimeline, hw.TimelineClock, hw.ptb.Clock); +if isfield(rig, 'timeline')&&rig.timeline.UseTimeline + rig.clock = hw.TimelineClock(rig.timeline); +else + rig.clock = hw.ptb.Clock; +end rig.useDaq = pick(rig, 'useDaq', 'def', true); %% Configure common devices, if present @@ -62,14 +65,6 @@ % end %% Set up controllers -if isfield(rig, 'rewardCalibrations') && isfield(rig, 'rewardController')... - && ~isfield(rig, 'daqController') &&... - ~isa(rig.rewardController, 'hw.DummyFeedback') - % create a daq controller based on legacy rig.rewardController - rig.daqController = hw.daqControllerForValve(... - rig.rewardController, rig.rewardCalibrations); -end - if init if isfield(rig, 'daqController') rig.daqController.createDaqChannels(); @@ -90,23 +85,13 @@ InitializePsychSound; IsPsychSoundInitialize = true; end - if isfield(rig, 'audioDevice') - audioDevice = rig.audioDevice; - audioSR = 96e3; - audioChannels = 2; -% elseif isfield(rig, 'audioDetails') -% % Pip Modified 25/03/2016 -% audioSR = rig.audioDetails.audioSR; -% audioDevice = rig.audioDetails.audioDevice; -% audioChannels = rig.audioDetails.audioChannels; - else - audioDevice = []; - audioSR = 96e3; - audioChannels = 2; - end + idx = pick(rig, 'audioDevice', 'def', 0); + rig.audioDevice = PsychPortAudio('GetDevices', [], idx); % setup playback audio device - no configurable settings for now % 96kHz sampling rate, 2 channels, try to very low audio latency - rig.audio = aud.open(audioDevice, audioChannels, audioSR, 1); + rig.audio = aud.open(rig.audioDevice.DeviceIndex,... + rig.audioDevice.NrOutputChannels,... + rig.audioDevice.DefaultSampleRate, 1); end rig.paths = paths; diff --git a/+hw/valveDeliveryCalibration.m b/+hw/valveDeliveryCalibration.m index fbda0fd0..5bb908c1 100644 --- a/+hw/valveDeliveryCalibration.m +++ b/+hw/valveDeliveryCalibration.m @@ -1,7 +1,20 @@ function c = valveDeliveryCalibration(openTimeRange, scalesPort, openValue,... closedValue, daqChannel, daqDevice) -%UNTITLED Summary of this function goes here -% Detailed explanation goes here +%HW.VALVEDELIVERYCALIBRATION Returns a calibration struct for water reward +% Returns a struct containing a range of valve open-close times and the +% resulting mean volume of water delivered. This can be used to +% calibrate water delivery without having to run SRV.EXPSERVER. +% +% The calibration requires the use of a weighing scale that can interface +% with the computer via either USB or serial cable. For example the +% ES-300HA 300gx0.01g Precision Scale + RS232 to USB Converter +% +% +% See also HW.REWARDVALVECONTROL, HW.WEIGHINGSCALE, HW.DAQCONTROLLER +% +% Part of Rigbox + +% c. 2013 CB created if nargin < 3 || isempty(openValue) openValue = 5; @@ -29,7 +42,7 @@ daqController.SignalGenerators.ClosedValue = closedValue; daqController.SignalGenerators.DefaultValue = closedValue; daqController.SignalGenerators.OpenValue = openValue; -daqController.SignalGenerators.DefaultCommand = 3; +daqController.SignalGenerators.DefaultCommand = 5; try daqController.createDaqChannels(); c = hw.calibrate('rewardValve', daqController, scales, openTimeRange(1), openTimeRange(2)); diff --git a/+srv/BasicUDPService.m b/+srv/BasicUDPService.m index 1ecf305f..f384704b 100644 --- a/+srv/BasicUDPService.m +++ b/+srv/BasicUDPService.m @@ -10,6 +10,21 @@ % back). To receive messaged only, simply use bind() and add a % listener to the MessageReceived event. % + % Examples: + % remoteTL = srv.BasicUDPService('tl-host', 10000, 10000); + % remoteTL.start('2017-10-27-1-default'); % Start remote service with + % an experiment reference + % remoteTL.stop; remoteTL.delete; % Clean up after stopping remote + % rig + % + % experimentRig = srv.BasicUDPService('mainRigHostName', 10000, 10000); + % experimentRig.bind(); % Connect to the remote rig + % remoteStatus = requestStatus(experimentRig); % Get the status of + % the experimental rig + % lh = events.listener(experimentRig, 'MessageReceived', + % @(srv, evt)processMessage(srv, evt)); % Add a listener to do + % something when a message is received. + % % See also SRV.PRIMITIVEUDPSERVICE, UDP. % % Part of Rigbox @@ -23,6 +38,8 @@ properties LocalStatus % Local status to send upon request ResponseTimeout = Inf % How long to wait for confirmation of receipt + StartCallback % Function to be run upon receiving remote start command + StopCallback % Function to be run upon receiving remote stop command end properties (SetObservable, AbortSet = true) @@ -61,6 +78,7 @@ function delete(obj) obj.Socket = []; % Delete udp object obj.Listener = []; % Delete any listeners to that object if ~isempty(obj.ResponseTimer) % If there is a timer object + stop(obj.ResponseTimer) % Stop the timer.. delete(obj.ResponseTimer) % Delete the timer... obj.ResponseTimer = []; % ... and remove it end @@ -79,24 +97,24 @@ function delete(obj) 'RemotePort', obj.RemotePort, 'LocalPort', obj.ListenPort); obj.Socket.ReadAsyncMode = 'continuous'; obj.Socket.BytesAvailableFcnCount = 10; % Number of bytes in buffer required to trigger BytesAvailableFcn - obj.Socket.BytesAvailableFcn = @obj.receiveUDP; % Add callback to receiveUDP when enough bytes arrive in the buffer + obj.Socket.BytesAvailableFcn = @(~,~)obj.receiveUDP; % Add callback to receiveUDP when enough bytes arrive in the buffer % Add listener to MessageReceived event, notified when receiveUDP is % called. This event can be listened to by anyone. - obj.Listener = event.listener(obj, 'MessageReceived', @processMsg); + obj.Listener = event.listener(obj, 'MessageReceived', @(src,evt)obj.processMsg(src,evt)); % Add listener for when the observable properties are set obj.addlistener({'RemoteHost', 'ListenPort', 'RemotePort', 'EnablePortSharing'},... - 'PostSet',@obj.update); + 'PostSet',@(src,~)obj.update(src)); % Add listener for when the remote service's status is requested - obj.addlistener('Status', 'PreGet', @obj.requestStatus); + obj.addlistener('Status', 'PreGet', @(~,~)obj.requestStatus); end - function update(obj, evt, ~) + function update(obj, src) % Callback for setting udp relevant properties. Some properties can % only be set when the socket is closed. % Check if socket is open isOpen = strcmp(obj.Socket.Status, 'open'); % Close connection before setting, if required to do so - if any(strcmp(evt.name, {'RemoteHost', 'LocalPort', 'EnablePortSharing'}))&&isOpen + if any(strcmp(src.Name, {'RemoteHost', 'ListenPort', 'RemotePort', 'EnablePortSharing'}))&&isOpen fclose(obj.Socket); end % Set all the relevant properties @@ -115,18 +133,27 @@ function bind(obj) function start(obj, expRef) % Send start message to remotehost and await confirmation - obj.confirmedSend(sprintf('GOGO%s*%s', expRef, obj.RemoteHost)); + obj.confirmedSend(sprintf('GOGO%s*%s', expRef, hostname)); + while obj.AwaitingConfirmation&&... + ~strcmp(obj.LastReceivedMessage, obj.LastSentMessage) + pause(0.2); + end end function stop(obj) % Send stop message to remotehost and await confirmation - obj.confirmedSend(sprintf('STOP*%s', obj.RemoteHost)); + obj.confirmedSend(sprintf('STOP*%s', hostname)); + while obj.AwaitingConfirmation&&... + ~strcmp(obj.LastReceivedMessage, obj.LastSentMessage) + pause(0.1) + end + obj.delete end function requestStatus(obj) % Request a status update from the remote service obj.ConfirmID = randi(1e6); - obj.sendUDP(sprintf('WHAT%i*%s', obj.ConfirmID, obj.RemoteHost)); + obj.sendUDP(sprintf('WHAT%i*%s', obj.ConfirmID, hostname)); disp('Requested status update from remote service') end @@ -134,16 +161,18 @@ function confirmedSend(obj, msg) sendUDP(obj, msg) obj.AwaitingConfirmation = true; % Add timer to impose a response timeout - if ~isinf(obj.ResponseTimout) - obj.ResponseTimer = timer('Period', obj.ResponseTimout,... - 'TimerFcn', @processMsg); + if ~isinf(obj.ResponseTimeout) + obj.ResponseTimer = timer('StartDelay', obj.ResponseTimeout,... + 'TimerFcn', @(src,evt)obj.processMsg(src,evt), 'StopFcn', @(src,~)delete(src)); + start(obj.ResponseTimer) % start the timer end end function receiveUDP(obj) - obj.LastReceivedMessage = fscanf(obj.Socket); + obj.LastReceivedMessage = strtrim(fscanf(obj.Socket)); + % Remove any more accumulated inputs to the listener - obj.Socket.flushinput(); +% obj.Socket.flushinput(); notify(obj, 'MessageReceived') end @@ -157,29 +186,47 @@ function sendUDP(obj, msg) end methods (Access = protected) - function processMsg(obj, ~, ~) - response = obj.LastReceivedMessage; + function processMsg(obj, src, evt) + % Parse the message into its constituent parts + response = regexp(obj.LastReceivedMessage,... + '(?[A-Z]{4})(?.*)\*(?\w*)', 'names'); + % Check that the message was from the correct host, otherwise ignore + if ~isempty(response)&&~any(strcmp(response.host, {obj.RemoteHost hostname})) + warning('Received message from %s, ignoring', response.host); + return + end + % We no longer need the timer, stop it + if ~isempty(obj.ResponseTimer); stop(obj.ResponseTimer); end + if obj.AwaitingConfirmation - % Check the confirmation message is the same as the sent message - assert(~isempty(response)||... % something received - strcmp(response, 'WHAT')||... % status update - strcmp(response, obj.LastSentMessage),... % is echo + % Reset AwaitingConfirmation + obj.AwaitingConfirmation = false; + % Check the confirmation message is the same as the sent message + assert(~isempty(response)&&... % something received + strcmp(response.status, 'WHAT')||... % status update + strcmp(obj.LastReceivedMessage, obj.LastSentMessage),... % is echo 'Confirmation failed') - % We no longer need the timer, delete it - if ~isempty(obj.ResponseTimer);delete(obj.ResponseTimer); end - obj.ResponseTimer = []; end % At the moment we just disply some stuff, other functions listening % to the MessageReceived event can do their thing - switch obj.LastReceivedMessage(1:4) + if isempty(response); return; end + switch response.status case 'GOGO' if obj.AwaitingConfirmation obj.Status = 'running'; disp(['Service on ' obj.RemoteHost ' running']) else disp('Received start request') - obj.LocalStatus = 'running'; - obj.sendUDP(obj.LastReceivedMessage) + obj.LocalStatus = 'starting'; + if ~isempty(obj.StartCallback)&&isa(obj.StartCallback,'function_handle') + try + feval(obj.StartCallback, src, evt); + obj.LocalStatus = 'running'; + obj.sendUDP(obj.LastReceivedMessage) + catch ex + error('Failed to start service: %s', ex.message) + end + end end case 'STOP' if obj.AwaitingConfirmation @@ -188,11 +235,18 @@ function processMsg(obj, ~, ~) else disp('Received stop request') obj.LocalStatus = 'stopping'; - obj.sendUDP(obj.LastReceivedMessage) + if ~isempty(obj.StopCallback)&&isa(obj.StopCallback,'function_handle') + try + feval(obj.StopCallback, src, evt); + obj.LocalStatus = 'idle'; + obj.sendUDP(obj.LastReceivedMessage) + catch + error('Failed to stop service') + end + end end case 'WHAT' - % TODO fix status updates so that they're meaningful - parsed = regexp(response, '(?[A-Z]+)(?\d+)(?[a-z]*)\*', 'names'); + parsed = regexp(response.body, '(?\d+)(?[a-z]*)', 'names'); if obj.AwaitingConfirmation try assert(strcmp(parsed.id, int2str(obj.ConfirmID)), 'Rigbox:srv:unexpectedUDPResponse',... @@ -206,14 +260,19 @@ function processMsg(obj, ~, ~) catch obj.Status = 'unavailable'; end - else % Received status request NB: Currently no way of determining status - obj.sendUDP([parsed.status parsed.id obj.LocalStatus]) + else + % Ignore if it is our on + if strcmp(response.host, hostname); return; end + try + obj.sendUDP(['WHAT' parsed.id obj.LocalStatus '*' obj.RemoteHost]) + disp(['Sent status update to ' obj.RemoteHost]) % Display success + catch + error('Failed to send status update to %s', obj.RemoteHost) + end end otherwise disp(['Received ''' obj.LastReceivedMessage ''' from ' obj.RemoteHost]) end - % Reset AwaitingConfirmation - obj.AwaitingConfirmation = false; end end end \ No newline at end of file diff --git a/+srv/RemoteTLService.m b/+srv/RemoteTLService.m new file mode 100644 index 00000000..69014fc9 --- /dev/null +++ b/+srv/RemoteTLService.m @@ -0,0 +1,262 @@ +classdef RemoteTLService < srv.Service + %SRV.REMOTETLSERVICE UDP-based service for starting and stopping Timeline + % A UDP interface that uses the udp function of the Instument Control + % Toolbox. Unlike SRV.PRIMITIVEUDPSERVICE, this can send and recieve + % messages asynchronously and can be used both to start remote services + % and, service side, to listen for remote start/stop commands. + % + % To send a message simply use sentUDP(msg). Use confirmedSend(msg) to + % send send a message and await a confirmation (the same message echoed + % back). To receive messaged only, simply use bind() and add a + % listener to the MessageReceived event. + % + % Examples: + % remoteTL = srv.BasicUDPService('tl-host', 10000, 10000); + % remoteTL.start('2017-10-27-1-default'); % Start remote service with + % an experiment reference + % remoteTL.stop; remoteTL.delete; % Clean up after stopping remote + % rig + % + % experimentRig = srv.BasicUDPService('mainRigHostName', 10000, 10000); + % experimentRig.bind(); % Connect to the remote rig + % remoteStatus = requestStatus(experimentRig); % Get the status of + % the experimental rig + % lh = events.listener(experimentRig, 'MessageReceived', + % @(srv, evt)processMessage(srv, evt)); % Add a listener to do + % something when a message is received. + % + % See also SRV.PRIMITIVEUDPSERVICE, UDP. + % + % Part of Rigbox + + % 2017-10 MW created + + properties (GetObservable, SetAccess = protected) + Status % Status of remote service + end + + properties + LocalStatus % Local status to send upon request + ResponseTimeout = Inf % How long to wait for confirmation of receipt + Timeline % Holds an instance of Timeline + end + + properties (SetObservable, AbortSet = true) + RemoteHost % Host name of the remote service + ListenPort = 10000 % Localhost port number to listen for messages on + RemotePort = 10000 % Which port to send messages to remote service on + EnablePortSharing = 'off' % If set to 'on' other applications can use the listen port + end + + properties (SetAccess = protected) + RemoteIP % The IP address of the remote service + Socket % A handle to the udp object + LastSentMessage = '' % A copy of the message sent from this host + LastReceivedMessage = '' % A copy of the message received by this host + end + + properties (Access = private) + Listener % A listener for the MessageReceived event + ResponseTimer % A timer object set when expecting a confirmation message (if ResponseTimeout < Inf) + AwaitingConfirmation = false % True when awaiting a confirmation message + ConfirmID % A random integer to confirm UDP status response. See requestStatus() + end + + events (NotifyAccess = 'protected') + MessageReceived % Notified by receiveUDP() when a UDP message is received + end + + methods + function delete(obj) + % To be called before destroying BasicUDPService object. Deletes all + % timers, sockets and listeners Tidy up after ourselves by closing + % the listening sockets + if ~isempty(obj.Socket) + fclose(obj.Socket); % Close the connection + delete(obj.Socket); % Delete the socket + obj.Socket = []; % Delete udp object + obj.Listener = []; % Delete any listeners to that object + if ~isempty(obj.ResponseTimer) % If there is a timer object + stop(obj.ResponseTimer) % Stop the timer.. + delete(obj.ResponseTimer) % Delete the timer... + obj.ResponseTimer = []; % ... and remove it + end + end + end + + function obj = RemoteTLService(remoteHost, remotePort, listenPort) + % SRV.REMOTETLSERVICE(remoteHost [remotePort, listenPort]) + % remoteHost is the hostname of the service with which to send and + % receive messages. + paths = dat.paths(hostname); % Get list of paths for timeline + try + load(fullfile(paths.rigConfig, 'hardware.mat'), 'timeline'); + catch + timeline = hw.Timeline; + end + obj.Timeline = timeline; % Load timeline object + obj.RemoteHost = remoteHost; % Set hostname + obj.RemoteIP = ipaddress(remoteHost); % Get IP address + if nargin >= 3; obj.ListenPort = listenPort; end % Set local port + if nargin >= 2; obj.RemotePort = remotePort; end % Set remote port + obj.Socket = udp(obj.RemoteIP,... % Create udp object + 'RemotePort', obj.RemotePort, 'LocalPort', obj.ListenPort); + obj.Socket.ReadAsyncMode = 'continuous'; + obj.Socket.BytesAvailableFcnCount = 10; % Number of bytes in buffer required to trigger BytesAvailableFcn + obj.Socket.BytesAvailableFcn = @(~,~)obj.receiveUDP(); % Add callback to receiveUDP when enough bytes arrive in the buffer + % Add listener to MessageReceived event, notified when receiveUDP is + % called. This event can be listened to by anyone. + obj.Listener = event.listener(obj, 'MessageReceived', @(~,~)obj.processMsg); + % Add listener for when the observable properties are set + obj.addlistener({'RemoteHost', 'ListenPort', 'RemotePort', 'EnablePortSharing'},... + 'PostSet',@obj.update); + % Add listener for when the remote service's status is requested + obj.addlistener('Status', 'PreGet', @(~,~)obj.requestStatus); + end + + function update(obj, evt, ~) + % Callback for setting udp relevant properties. Some properties can + % only be set when the socket is closed. + % Check if socket is open + isOpen = strcmp(obj.Socket.Status, 'open'); + % Close connection before setting, if required to do so + if any(strcmp(evt.name, {'RemoteHost', 'LocalPort', 'EnablePortSharing'}))&&isOpen + fclose(obj.Socket); + end + % Set all the relevant properties + obj.RemoteIP = ipaddress(obj.RemoteHost); + obj.Socket.LocalPort = obj.ListenPort; + obj.Socket.RemotePort = obj.RemotePort; + obj.Socket.RemoteHost = obj.RemoteIP; + if isOpen; bind(obj); end % If socket was open before, re-open + end + + function bind(obj) + % Open the connection to allow messages to be sent and received + if ~isempty(obj.Socket); fclose(obj.Socket); end + fopen(obj.Socket); + end + + function start(obj, expRef) + % Send start message to remotehost and await confirmation + obj.confirmedSend(sprintf('GOGO%s*%s', expRef, hostname)); + end + + function stop(obj) + % Send stop message to remotehost and await confirmation + obj.confirmedSend(sprintf('STOP*%s', hostname)); + end + + function requestStatus(obj) + % Request a status update from the remote service + obj.ConfirmID = randi(1e6); + obj.sendUDP(sprintf('WHAT%i*%s', obj.ConfirmID, hostname)); + disp('Requested status update from remote service') + end + + function confirmedSend(obj, msg) + sendUDP(obj, msg) + obj.AwaitingConfirmation = true; + % Add timer to impose a response timeout + if ~isinf(obj.ResponseTimeout) + obj.ResponseTimer = timer('StartDelay', obj.ResponseTimout,... + 'TimerFcn', @(~,~)obj.processMsg); + start(obj.ResponseTimer) % start the timer + end + end + + function receiveUDP(obj) + obj.LastReceivedMessage = strtrim(fscanf(obj.Socket)); + % Remove any more accumulated inputs to the listener +% obj.Socket.flushinput(); + notify(obj, 'MessageReceived') + end + + function sendUDP(obj, msg) + % Ensure socket is open before sending message + if strcmp(obj.Socket.Status, 'closed'); bind(obj); end + fprintf(obj.Socket, msg); % Send message + obj.LastSentMessage = msg; % Save a copy of the message + end + end + + methods (Access = protected) + function processMsg(obj) + % Parse the message into its constituent parts + response = regexp(obj.LastReceivedMessage,... + '(?[A-Z]{4})(?.*)\*(?[a-z]*)', 'names'); + % Check that the message was from the correct host, otherwise ignore + if ~isempty(response)&&~any(strcmp(response.host, {obj.RemoteHost hostname})) + warning('Received message from %s, ignoring', response.host); + return + end + % We no longer need the timer, stop and delete it + if ~isempty(obj.ResponseTimer) + stop(obj.ResponseTimer) + delete(obj.ResponseTimer) + obj.ResponseTimer = []; + end + if obj.AwaitingConfirmation + % Check the confirmation message is the same as the sent message + assert(~isempty(response)&&... % something received + strcmp(response.status, 'WHAT')||... % status update + strcmp(obj.LastReceivedMessage, obj.LastSentMessage),... % is echo + 'Confirmation failed') + end + % At the moment we just disply some stuff, other functions listening + % to the MessageReceived event can do their thing + switch response.status + case 'GOGO' + if obj.AwaitingConfirmation + obj.Status = 'running'; + disp(['Service on ' obj.RemoteHost ' running']) + else + disp('Received start request') + obj.LocalStatus = 'starting'; + [expRef, Alyx] = dat.parseAlyxInstance(response.body); + obj.Timeline.start(expRef, Alyx) + obj.LocalStatus = 'running'; + obj.sendUDP(obj.LastReceivedMessage) + end + case 'STOP' + if obj.AwaitingConfirmation + obj.Status = 'stopped'; + disp(['Service on ' obj.RemoteHost ' stopped']) + else + disp('Received stop request') + obj.LocalStatus = 'stopping'; + obj.Timeline.stop + obj.LocalStatus = 'idle'; + obj.sendUDP(obj.LastReceivedMessage) + end + case 'WHAT' + parsed = regexp(response.body, '(?\d+)(?[a-z]*)', 'names'); + if obj.AwaitingConfirmation + try + assert(strcmp(parsed.id, int2str(obj.ConfirmID)), 'Rigbox:srv:unexpectedUDPResponse',... + 'Received UDP message ID did not match sent'); + switch parsed.update + case {'running' 'starting'} + obj.Status = 'running'; + otherwise + obj.Status = 'idle'; + end + catch + obj.Status = 'unavailable'; + end + else % Received status request NB: Currently no way of determining status + try + obj.sendUDP(['WHAT' parsed.id obj.LocalStatus obj.RemoteHost]) + disp(['Sent status update to ' obj.RemoteHost]) % Display success + catch + error('Failed to send status update to %s', obj.RemoteHost) + end + end + otherwise + disp(['Received ''' obj.LastReceivedMessage ''' from ' obj.RemoteHost]) + end + % Reset AwaitingConfirmation + obj.AwaitingConfirmation = false; + end + end +end \ No newline at end of file diff --git a/+srv/StimulusControl.m b/+srv/StimulusControl.m index 60d4f859..a8b4e1b8 100644 --- a/+srv/StimulusControl.m +++ b/+srv/StimulusControl.m @@ -1,25 +1,27 @@ classdef StimulusControl < handle %SRV.STIMULUSCONTROL Interface to, and info about a remote rig setup - % This interface is used by srv.expServer and mc to communicate with + % This interface is used and mc to communicate with % one another. The data are sent over TCP/IP through a java Web Socket % (net.entropy_mill.websocket). This object can be used to send % arbitraty data over the network. It is used by expServer to send a % receive parrameter structures and status updates in the form of % strings. % - % NB: This class replaces SRV.REMOTERIG. See also SRV.SERVICE. + % NB: This class replaces SRV.REMOTERIG. See also SRV.SERVICE, + % IO.WSJCOMMUNICATOR, EUI.MCONTROL % % Part of Rigbox % 2013-06 CB created properties - Uri + Uri % Services = {} % List of remote services that are to be interfaced with during an experiment. Should be a list of string ids or hostnames - Name - ExpPreDelay = 0 - ExpPostDelay = 0 - ResponseTimeout = 15 + SelectedServices % Logical array the size of Services, indicating which services to use (set by eui.MControl) + Name % The name of the remote rig, usually the host name + ExpPreDelay = 0 % The delay in seconds before the experiment is to start, set by mc + ExpPostDelay = 0 % The delay in seconds after the experiment has ended before data are saved, listeners deleted, etc. Set by mc + ResponseTimeout = 15 % Time to wait for a response from the remote host before throwing an error end properties (Dependent = true) @@ -40,6 +42,10 @@ LogCount = 0 end + properties (Transient, Hidden) + AlyxInstance = [] % Property to store rig specific Alyx token + end + properties (Constant) DefaultPort = 2014 end @@ -94,18 +100,12 @@ end end end - - function setAlyxInstance(obj, AlyxInstance) - % send AlyxInstance to experiment server - r = obj.exchange({'updateAlyxInstance', AlyxInstance}); - obj.errorOnFail(r); - end - - function quitExperiment(obj, immediately, Alyx) + + function quitExperiment(obj, immediately) if nargin < 2 immediately = false; end - r = obj.exchange({'quit', immediately, Alyx.AlyxInstance}); + r = obj.exchange({'quit', immediately, obj.AlyxInstance}); obj.errorOnFail(r); end @@ -116,8 +116,7 @@ function startExperiment(obj, expRef) preDelay = obj.ExpPreDelay; postDelay = obj.ExpPostDelay; - - r = obj.exchange({'run', expRef, preDelay, postDelay}); + r = obj.exchange({'run', expRef, preDelay, postDelay, obj.AlyxInstance}); obj.errorOnFail(r); end @@ -219,8 +218,12 @@ function onWSReceived(obj, ~, eventArgs) % end end case 'AlyxRequest' + % expServer requested the AlyxInstance ref = data; % expRef - % notify listeners i.e. ExpPanel of request for AlyxInstance + % send AlyxInstance to experiment server + r = obj.exchange({'updateAlyxInstance', obj.AlyxInstance}); + obj.errorOnFail(r); + % notify listeners of request for AlyxInstance notify(obj, 'AlyxRequest', srv.ExpEvent('AlyxRequest', ref)); end end diff --git a/+srv/expServer.m b/+srv/expServer.m index 3f174538..4e4bec05 100644 --- a/+srv/expServer.m +++ b/+srv/expServer.m @@ -16,7 +16,6 @@ function expServer(useTimelineOverride, bgColour) gammaCalibrationKey = KbName('g'); timelineToggleKey = KbName('t'); toggleBackground = KbName('b'); -useTimeline = false; rewardId = 1; %% Initialisation @@ -72,7 +71,7 @@ function expServer(useTimelineOverride, bgColour) if nargin < 1 || isempty(useTimelineOverride) % toggle use of timeline according to rig default setting - toggleTimeline(rig.useTimeline); + toggleTimeline(rig.timeline.UseTimeline); else toggleTimeline(useTimelineOverride); end @@ -170,13 +169,13 @@ function handleMessage(id, data, host) end case 'run' % exp run request - [expRef, preDelay, postDelay] = args{:}; + [expRef, preDelay, postDelay, Alyx] = args{:}; if dat.expExists(expRef) log('Starting experiment ''%s''', expRef); communicator.send(id, []); try communicator.send('status', {'starting', expRef}); - runExp(expRef, preDelay, postDelay); + runExp(expRef, preDelay, postDelay, Alyx); log('Experiment ''%s'' completed', expRef); communicator.send('status', {'completed', expRef}); catch runEx @@ -198,7 +197,7 @@ function handleMessage(id, data, host) else log('Ending experiment'); end - if ~isempty(AlyxInstance) + if ~isempty(AlyxInstance)&&isempty(experiment.AlyxInstance) experiment.AlyxInstance = AlyxInstance; end experiment.quit(immediately); @@ -216,22 +215,24 @@ function handleMessage(id, data, host) end end - function runExp(expRef, preDelay, postDelay) + function runExp(expRef, preDelay, postDelay, Alyx) % disable ptb keyboard listening KbQueueRelease(); rig.stimWindow.flip(); % clear the screen before - if useTimeline + if rig.timeline.UseTimeline %start the timeline system - if isfield(rig, 'disregardTimelineInputs') - disregardInputs = rig.disregardTimelineInputs; + if isfield(rig, 'disregardTimelineInputs') % TODO Depricated, use hw.Timeline.UseInputs instead + [~, idx] = intersect(rig.timeline.UseInputs, rig.disregardTimelineInputs); + rig.timeline.UseInputs(idx) = []; else % turn off rotary encoder recording in timeline by default so % experiment can access it - disregardInputs = {'rotaryEncoder'}; + idx = ~strcmp('rotaryEncoder', rig.timeline.UseInputs); + rig.timeline.UseInputs = rig.timeline.UseInputs(idx); end - tl.start(expRef, disregardInputs); + rig.timeline.start(expRef, Alyx); else %otherwise using system clock, so zero it rig.clock.zero(); @@ -242,6 +243,7 @@ function runExp(expRef, preDelay, postDelay) experiment = srv.prepareExp(params, rig, preDelay, postDelay,... communicator); communicator.EventMode = true; % use event-based callback mode + experiment.AlyxInstance = Alyx; % add Alyx Instance experiment.run(expRef); % run the experiment communicator.EventMode = false; % back to pull message mode % clear the active experiment var @@ -249,9 +251,9 @@ function runExp(expRef, preDelay, postDelay) rig.stimWindow.BackgroundColour = bgColour; rig.stimWindow.flip(); % clear the screen after - if useTimeline + if rig.timeline.UseTimeline %stop the timeline system - tl.stop(); + rig.timeline.stop(); end % re-enable ptb keyboard listening @@ -275,53 +277,29 @@ function calibrateWaterDelivery() rigHwFile = fullfile(pick(dat.paths, 'rigConfig'), 'hardware.mat'); save(rigHwFile, 'daqController', '-append'); - % disp('TODO: implement saving'); - %save the updated rewardCalibrations struct - % save(, 'rewardCalibrations', '-append'); - %apply the calibration to rewardcontroller - % rig.rewardController.MeasuredDeliveries = calibration; - % log('Measured deliveries for reward calibrations saved'); end - function whiteScreen() -% stimWindow = rig.stimWindow; -% set.BackgroundColour(stimWindow, stimWindow.White); -rig.stimWindow.BackgroundColour = 255; -rig.stimWindow.flip(); -rig.stimWindow.BackgroundColour = bgColour; -% stimWindow.pBackgroundColour = stimWindow.White; -% if stimWindow.PtbHandle > -1 - % performing a ptb FillRect will set the new background colour -% Screen('FillRect', stimWindow.PtbHandle, 255); -% end -% pause(30); -% rig.stimWindow.flip(); -% set.BackgroundColour(stimWindow, stimWindow.White); -% rig.stimWindow.BackgroundColour = bgColour; -% rig.stimWindow.flip(); -% stimWindow.pBackgroundColour = bgColour; -% if stimWindow.PtbHandle > -1 - % performing a ptb FillRect will set the new background colour -% Screen('FillRect', stimWindow.PtbHandle, bgColour); -% end - + function whiteScreen() + rig.stimWindow.BackgroundColour = 255; + rig.stimWindow.flip(); + rig.stimWindow.BackgroundColour = bgColour; end function calibrateGamma() - stimWindow = rig.stimWindow; - DaqDev = rig.daqController.DaqIds; - lightIn = 'ai0'; % defaults from hw.psy.Window - clockIn = 'ai1'; - clockOut = 'port1/line0 (PFI4)'; - log(['Please connect photodiode to %s, clockIn to %s and clockOut to %s.\r'... - 'Press any key to contiue\n'],lightIn,clockIn,clockOut); - pause; % wait for keypress - stimWindow.Calibration = stimWindow.calibration(DaqDev); % calibration - pause(1); - saveGamma(stimWindow.Calibration); - stimWindow.applyCalibration(stimWindow.Calibration); - clear('lightIn','clockIn','clockOut','cal'); - log('Gamma calibration complete'); + stimWindow = rig.stimWindow; + DaqDev = rig.daqController.DaqIds; + lightIn = 'ai0'; % defaults from hw.psy.Window + clockIn = 'ai1'; + clockOut = 'port1/line0 (PFI4)'; + log(['Please connect photodiode to %s, clockIn to %s and clockOut to %s.\r'... + 'Press any key to contiue\n'],lightIn,clockIn,clockOut); + pause; % wait for keypress + stimWindow.Calibration = stimWindow.calibration(DaqDev); % calibration + pause(1); + saveGamma(stimWindow.Calibration); + stimWindow.applyCalibration(stimWindow.Calibration); + clear('lightIn','clockIn','clockOut','cal'); + log('Gamma calibration complete'); end function saveGamma(cal) @@ -346,20 +324,20 @@ function setClock(user, clock) function t = toggleTimeline(enable) if nargin < 1 - enable = ~useTimeline; + enable = ~rig.timeline.UseTimeline; end if ~enable - useTimeline = false; + rig.timeline.UseTimeline = false; clock = hw.ptb.Clock; % use psychtoolbox clock else - useTimeline = true; - clock = hw.TimelineClock; % use timeline clock + rig.timeline.UseTimeline = true; + clock = hw.TimelineClock(rig.timeline); % use timeline clock end rig.clock = clock; cellfun(@(user) setClock(user, clock),... - {'mouseInput', 'rewardController', 'lickDetector'}); + {'mouseInput', 'lickDetector'}); - t = useTimeline; + t = rig.timeline.UseTimeline; if enable log('Use of timeline enabled'); else diff --git a/+srv/prepareExp.m b/+srv/prepareExp.m index daf3d9a3..6bb2a2f3 100644 --- a/+srv/prepareExp.m +++ b/+srv/prepareExp.m @@ -19,6 +19,11 @@ if isfield(params, 'services') && ~isempty(params.services) services = srv.findService(params.services); % Uses basicServices % services = srv.loadService(params.services); % Loads BasicUDPService objects + for i = 1:length(services) + if isprop(services{i},'Timeline') + services{i}.Timeline = rig.timeline; + end + end startServices = exp.StartServices(services); stopServices = exp.StopServices(services); experiment.addEventHandler(... diff --git a/+tl/config.m b/+tl/config.m deleted file mode 100644 index beebfce5..00000000 --- a/+tl/config.m +++ /dev/null @@ -1,366 +0,0 @@ -function [hw, inputOptions, useInputs] = config(rig) -%TL.CONFIG Timeline hardware configuration info for rig -% Detailed explanation goes here -% -% Part of Rigbox - -% 2014-01 CB created - -if nargin < 1 || isempty(rig) - rig = hostname; % default rig is hostname -end - - function s = input(name, channelID, measurement, terminalConfig) - if nargin < 4 - % if no terminal config specified, leave empty which means use the - % DAQ default for that port - terminalConfig = []; - end - s = struct('name', name,... - 'arrayColumn', -1,... % -1 is default indicating unused - 'daqChannelID', channelID,... - 'measurement', measurement,... - 'terminalConfig', terminalConfig); - end - - -% measurement types -volts = 'Voltage'; -edgeCount = 'EdgeCount'; -pos = 'Position'; - -% List of all the input channels we can acquire and their configuration - -%% Defaults -% ******************************* -% ******** DO NOT CHANGE ******** -% ******************************* -hw.daqVendor = 'ni'; -hw.daqDevice = 'Dev1'; -hw.daqSampleRate = 1000; % samples per second -% n samples queued for each callback, will default to a seconds worth of -% samples if empty -hw.daqSamplesPerNotify = []; -hw.chronoOutDaqChannelID = 'port0/line0'; % for sending timing pulse out -hw.acqLiveDaqChannelID = 'port0/line1'; % output for acquisition live signal -% details of counter output channel for sending clocked pulses -hw.useClockOutput = true; -hw.clockOutputChannelID = 'ctr3'; % on zoolander's DAQ ctr3 output is PFI15 -hw.clockOutputFrequency = 60; %Hz -hw.clockOutputDutyCycle = 0.2; %Fraction - -% Note: chrono input should use single ended measurement, i.e. relative to -% the DAQ's ground -inputOptions = [... - input('chrono', 'ai0', volts, 'SingleEnded')... % for reading back self timing wave - input('syncEcho', 'ai1', volts)... % sync square echo (e.g. driven by vs) - input('photoDiode', 'ai2', volts)... % monitor light meter (e.g. over sync rec) - input('audioMonitor', 'ai3', volts)... % monitor of audio output to speakers - input('eyeCameraStrobe', 'ai4', volts)... % eye/behaviour imaging frame strobe - input('eyeCameraTrig', 'ai5', volts)... % Timeline-generated eyetracking triggers - input('piezoCommand', 'ai6', volts)... % Control signal sent to fastZ piezo - input('piezoPosition', 'ai7', volts)... % The actual position of fastZ Piezo - input('laserPower', 'ai8', volts)... % laser power mesured by photodiode - input('pockelsControl', 'ai9', volts)... % Control voltage sent to Pockel's (not very informative once you have the laserPower) - input('neuralFrames', 'ctr0', edgeCount)... % neural imaging frame counter - input('rotaryEncoder', 'ctr1', pos)... % rotary encoder position - input('lickDetector', 'ctr2', edgeCount)... % counter recording pulses from lickometer - ]; - -% function to index 'inputOptions' by name, e.g.: -% inputOptions(inputByName('syncEcho')) -% selects the inputOption called 'syncEcho' -inputByName = @(name) elementByName(inputOptions, name); - -useInputs = {... % default set of inputs to use - 'chrono',... % Timeline currently needs chrono - 'photoDiode',... % highly recommended to record photo diode signal - 'syncEcho',... % recommended if using vs - }; - - -%% Rig-specific overrides -% TODO: instead use rig config MAT-files instead of editing me -% ***************************************************************** -% ******** FOR NOW, MAKE YOUR OWN RIGS CONFIG CHANGES HERE ******** -% ***************************************************************** -% e.g. override default hw.useInputs: -% useInputs = {'chrono', 'blah', ...}; -% or DAQ channel IDs for inputOptions: -% inputOptions(inputByName('syncEcho')).daqChannelID = 'ai2'; -switch rig - case 'zbit' - useInputs = {'chrono','rotaryEncoder'}; - hw.daqDevice = 'Dev3'; - - inputOptions(inputByName('chrono')).daqChannelID = 'ai2'; - inputOptions(inputByName('rotaryEncoder')).daqChannelID = 'ctr0'; - hw.chronoOutDaqChannelID = 'port1/line0'; % for sending timing pulse out - hw.clockOutputChannelID = 'ctr1'; % on zoolander's DAQ ctr3 output is PFI15 - hw.acqLiveDaqChannelID = 'port1/line3'; - - case 'zym1' - inputOptions = [inputOptions ... - input('TTL', 'ai1', volts, 'SingleEnded')]; - - inputOptions(inputByName('photoDiode')).daqChannelID = 'ai13'; - inputOptions(inputByName('rotaryEncoder')).daqChannelID = 'ctr0'; - - - useInputs = {'chrono', 'photoDiode', 'TTL','rotaryEncoder'}; - hw.daqDevice = 'Dev1'; - - hw.chronoOutDaqChannelID = 'port1/line0'; % for sending timing pulse out - hw.clockOutputChannelID = 'ctr1'; % on zoolander's DAQ ctr3 output is PFI15 - hw.acqLiveDaqChannelID = 'port1/line3'; - - case 'zym2' - inputOptions = [inputOptions ... - input('TTL', 'ai1', volts, 'SingleEnded')]; - - inputOptions(inputByName('photoDiode')).daqChannelID = 'ai13'; - inputOptions(inputByName('rotaryEncoder')).daqChannelID = 'ctr0'; - - - useInputs = {'chrono', 'photoDiode', 'TTL','rotaryEncoder'}; - hw.daqDevice = 'Dev1'; - - hw.chronoOutDaqChannelID = 'port1/line0'; % for sending timing pulse out - hw.clockOutputChannelID = 'ctr1'; % on zoolander's DAQ ctr3 output is PFI15 - hw.acqLiveDaqChannelID = 'port1/line3'; - - case 'zoolander' - inputOptions = [inputOptions... % add to existing options - input('ephysVoltage', 'ai6', volts, 'SingleEnded')]; % ephys - hw.useClockOutput = true; - hw.clockOutputFrequency = 30; %30Hz for the eye camera trigger - hw.clockOutputInitialDelay = 10; - useInputs = {'chrono', 'photoDiode', 'neuralFrames', 'ephysVoltage'}; - inputOptions(inputByName('photoDiode')).terminalConfig = 'Differential'; - inputOptions(inputByName('eyeCameraTrig')).terminalConfig = 'SingleEnded'; - hw.stopDelay = 1; % allow ScanImage to finish acquiring - hw.daqSampleRate = 10000; % samples per second - case 'zcamp3' - inputOptions = [inputOptions... % add to existing options - input('rewardCommand', 'ai10', volts, 'SingleEnded')]; % command signal for reward - - inputOptions(inputByName('syncEcho')).terminalConfig = 'SingleEnded'; - inputOptions(inputByName('photoDiode')).terminalConfig = 'SingleEnded'; - inputOptions(inputByName('laserPower')).terminalConfig = 'SingleEnded'; - inputOptions(inputByName('pockelsControl')).terminalConfig = 'SingleEnded'; - - useInputs = {'chrono', 'photoDiode', 'syncEcho', 'neuralFrames',... - 'piezoCommand', 'piezoPosition', 'laserPower', 'pockelsControl', ... - 'audioMonitor', 'rewardCommand', 'rotaryEncoder'}; - - hw.daqSampleRate = 1000; % samples per second - hw.stopDelay = 1; % allow ScanImage to finish acquiring - - case 'zooropa' - %hw.chronoOutDaqChannelID = 'ao1'; %DS 2014.2.26 %% port0/line0 - %corresponds to which terminal? - inputOptions = [inputOptions... - input('cam2', 'ai5', volts, 'SingleEnded')... % neural imaging frame 14.3.3 DS - ...%input('cam2', 'ctr1', edgeCount)... %test for choice world 14.9.18 - input('cam1', 'ai7', volts, 'SingleEnded')... % neural imaging frame 16.3.31 DS added singleEnded - input('ao0', 'ai3', volts)... % copy of Analog output 0 15/12/21 DS - input('ao1', 'ai4', volts, 'SingleEnded')... % copy of Analog output 1 15/12/21 DS - input('arduinoBlue', 'ai12', volts, 'SingleEnded')... - input('arduinoGreen', 'ai13', volts, 'SingleEnded')... - input('arduinoRed', 'ai15', volts, 'SingleEnded')... % 16/3/31 DS - ...%input('photoSensor', 'ai8', volts)... % photo sensor on the right of the screesn 14.5.7 DS - input('illuminator', 'ai8', volts)... %illuminator output - input('acqLiveEcho', 'ai14', volts)... - ]; - - inputOptions(inputByName('rotaryEncoder')).daqChannelID = 'ctr0'; - %inputOptions(inputByName('lickDetector')).measurement = volts; - inputOptions(inputByName('lickDetector')).daqChannelID = 'ai6'; - inputOptions(inputByName('lickDetector')).measurement = volts; - inputOptions(inputByName('syncEcho')).terminalConfig = 'SingleEnded'; %16/3/31 DS - useInputs = {'chrono' 'rotaryEncoder', 'syncEcho', 'cam1', 'cam2',... - 'photoDiode', 'lickDetector','ao0','ao1','illuminator', 'arduinoBlue', ... - 'arduinoGreen', 'arduinoRed','acqLiveEcho'};%{'vsStim' 'rotaryEncoder'};% - - % change digital outs - %hw.chronoOutDaqChannelID = 'port0/line1'; % for sending timing pulse out - hw.chronoOutDaqChannelID = 'port0/line3'; % for sending timing pulse out. 20141008 DS - hw.acqLiveDaqChannelID = 'port0/line2'; % output for acquisition live signal - -% hw.useClockOutput = false; -% ** added 2015-12-30 by NS and DS for testing LED -% commented out on 2015-12-31 DS -% hw.useClockOutput = true; -% hw.clockOutputFrequency = 50; %Hz -% hw.clockOutputDutyCycle = 0.5; %Fraction -% hw.clockOutputInitialDelay = 2; -% hw.clockOutputChannelID = 'ctr1'; - % ** end added for testing LED - - %hw.daqSampleRate = 50e3; %18/6/14 DS - %hw.daqSampleRate = 20e3; %LFR - hw.daqSampleRate = 5e3; %6/8/14 DS for recording up to 200Hz - hw.dataType = 'single'; - case 'zpopulation' - inputOptions = [inputOptions... - input('Vm', 'ai1', volts)... % membrane potential - ]; - useInputs = {'chrono', 'photoDiode', 'Vm'}; - hw.daqDevice = 'Dev2'; - hw.daqSampleRate = 20e3; - case 'zintra2' - inputOptions = [inputOptions... - input('Vm1Primary', 'ai3', volts, 'Differential')... % hs1 primary output (Current clamp Vm; voltage clamp Im) - input('Vm1Secondary', 'ai4', volts, 'Differential')... hs1 primary output (Current clamp Vm; voltage clamp Im) - input('Vm2Primary', 'ai5', volts, 'Differential')... % membrane potential - input('Vm2Secondary', 'ai6', volts, 'Differential')... % membrane potential -...% input('laserCommand', 'ai7', volts)... % command signal for laser shutte - input('rewardCommand', 'ai1', volts, 'Differential')... % command signal for reward valve - ]; - % useInputs = {'chrono', 'photoDiode', 'Vm1Primary', 'lickDetector', 'rewardCommand'}; %2013Aug config - % useInputs = {'chrono', 'photoDiode', 'Vm1Primary', 'Vm1Secondary','Vm2Primary'}; %2015Jun config - useInputs = {'chrono', 'photoDiode', 'Vm1Primary', 'Vm1Secondary','lickDetector', 'rewardCommand','eyeCameraStrobe'}; %2015Oct config - hw.daqDevice = 'Dev1'; - hw.daqSampleRate = 20e3; - hw.useClockOutput = true; - % CPB: uncomment the following to notify every 0.1s - hw.daqSamplesPerNotify = round(0.1*hw.daqSampleRate); - hw.stopDelay = 0.1; % small - inputOptions(inputByName('eyeCameraStrobe')).terminalConfig = 'SingleEnded'; - inputOptions(inputByName('eyeCameraStrobe')).daqChannelID = 'ai7'; - - case 'zillion' - hw.useClockOutput = true; - hw.clockOutputChannelID = 'ctr0'; % on zillion's DAQ ctr0 output is PFI12 - hw.clockOutputFrequency = 30; %Hz - hw.clockOutputDutyCycle = 0.1; %Fraction - hw.clockOutputInitialDelay = 2; - % inputOptions(inputByName('rotaryEncoder')).daqChannelID = 'ctr0'; - - inputOptions = [inputOptions ... - input('wheelPosition', 'ai1', volts, 'SingleEnded')... - input('laser', 'ai5', volts, 'SingleEnded'),... - input('piezoLickDetector', 'ai6', volts, 'Differential'),... - input('waterValve', 'ai7', volts, 'SingleEnded')]; - - useInputs = {'chrono', 'wheelPosition','photoDiode', 'audioMonitor','rotaryEncoder', 'eyeCameraStrobe', ... - 'laser','piezoLickDetector','waterValve'}; - - hw.chronoOutDaqChannelID = 'port0/line1'; % for sending timing pulse out - hw.acqLiveDaqChannelID = 'port0/line2'; % output for acquisition live signal - hw.stopDelay = 2; % want this to be less than the amount of time the eye - % computer will wait so triggers stop coming before - % stopping acquisition - - - case 'zlick' - hw.useClockOutput = true; - hw.clockOutputChannelID = 'ctr0'; % on zillion's DAQ ctr0 output is PFI12 - hw.clockOutputFrequency = 30; %Hz - hw.clockOutputDutyCycle = 0.1; %Fraction - hw.clockOutputInitialDelay = 2; - % inputOptions(inputByName('rotaryEncoder')).daqChannelID = 'ctr0'; - - inputOptions = [inputOptions ... - input('wheelPosition', 'ai1', volts, 'SingleEnded')... - input('laser', 'ai5', volts, 'SingleEnded'),... - input('piezoLickDetector', 'ai6', volts, 'Differential'),... - input('waterValve', 'ai7', volts, 'SingleEnded')]; - - useInputs = {'chrono', 'wheelPosition','photoDiode', 'audioMonitor','rotaryEncoder', 'eyeCameraStrobe', ... - 'laser','piezoLickDetector','waterValve'}; - - hw.chronoOutDaqChannelID = 'port0/line1'; % for sending timing pulse out - hw.acqLiveDaqChannelID = 'port0/line2'; % output for acquisition live signal - hw.stopDelay = 2; % want this to be less than the amount of time the eye - % computer will wait so triggers stop coming before - % stopping acquisition - - case 'zenith' % was zgood - kilotrode rig - hw.daqSampleRate = 2500; % to be confident about measuring 1ms laser pulses -% hw.daqSampleRate = 1000; - - % These for use with triggering eye camera -% hw.useClockOutput = true; -% hw.clockOutputChannelID = 'ctr0'; % on zillion's DAQ ctr0 output is PFI12 -% hw.clockOutputFrequency = 30; %Hz -% hw.clockOutputDutyCycle = 0.1; %Fraction -% hw.clockOutputInitialDelay = 2; - - % These for use with IR LED sync method - % hw.clockOutputFrequency = 0.01; %Hz - % hw.clockOutputDutyCycle = 0.001; %Fraction - % hw.clockOutputInitialDelay = 1; - - hw.acqLiveIsPulse = true; - hw.acqLivePulsePauseDuration = 0.2; - %hw.acqLiveStartDelay = 5; - - % add new inputs - inputOptions = [inputOptions... - input('piezoLickDetector', 'ai6', volts, 'Differential')... - input('waveOutput', 'ai7', volts, 'SingleEnded')... - input('openChan1', 'ai3', volts, 'SingleEnded')... - input('openChan2', 'ai5', volts, 'SingleEnded')... - input('camSync', 'ai16', volts, 'SingleEnded')... - input('whiskCamStrobe', 'ai17', volts, 'SingleEnded')... - input('rewardEcho', 'ai15', volts, 'SingleEnded')... - input('faceCamStrobe', 'ai12', volts, 'SingleEnded')]; - - % change existing inputs - inputOptions(inputByName('photoDiode')).terminalConfig = 'SingleEnded'; - inputOptions(inputByName('eyeCameraStrobe')).terminalConfig = 'SingleEnded'; - inputOptions(inputByName('audioMonitor')).daqChannelID = 'ai8'; - inputOptions(inputByName('rotaryEncoder')).daqChannelID = 'ctr0'; - -% inputOptions(inputByName('eyeCameraTrig')).terminalConfig = 'SingleEnded'; -% inputOptions(inputByName('eyeCameraTrig')).daqChannelID = 'ai1'; - - useInputs = {'chrono', 'photoDiode', 'rotaryEncoder', ... - 'eyeCameraStrobe', 'waveOutput', 'openChan1', 'piezoLickDetector', ... - 'openChan2', 'camSync', 'whiskCamStrobe', 'rewardEcho', 'audioMonitor',... - 'faceCamStrobe'}; - - hw.chronoOutDaqChannelID = 'port0/line1'; % for sending timing pulse out - hw.acqLiveDaqChannelID = 'port0/line3'; % output for acquisition live signal - hw.stopDelay = 2; % want this to be less than the amount of time the eye - % computer will wait so triggers stop coming before - % stopping acquisition - - hw.makePlots = true; - hw.figPosition = [50 50 1700 900]; - hw.figScales = [1 1/2 3 1 1 1 3 1 1 10 1 8 1]; -% hw.daqSamplesPerNotify = round(0.1*hw.daqSampleRate); % plots will update this often, then - - - hw.writeDat = true; - - hw.dataType = 'double'; - case 'zurprise' - inputOptions(inputByName('syncEcho')).terminalConfig = 'SingleEnded'; - inputOptions(inputByName('photoDiode')).terminalConfig = 'SingleEnded'; - inputOptions(inputByName('laserPower')).terminalConfig = 'SingleEnded'; - inputOptions(inputByName('pockelsControl')).terminalConfig = 'SingleEnded'; - - inputOptions = [inputOptions ... - input('piezoLickDetector', 'ai4', volts, 'Differential') ... - input('scanimageTrigger', 'ai5', volts, 'SingleEnded')]; - - useInputs = {'chrono', 'photoDiode', 'syncEcho', 'neuralFrames',... - 'piezoCommand', 'piezoPosition', 'laserPower', 'pockelsControl', ... - 'audioMonitor', 'rotaryEncoder', 'piezoLickDetector', 'scanimageTrigger'}; - -% useInputs = {'chrono', 'laserPower', 'scanimageTrigger'}; - - hw.useClockOutput = false; - - hw.acqLiveStartDelay = 3; % make sure that ScanImage is ready to go - - hw.daqSampleRate = 1000; % samples per second - hw.stopDelay = 3; % allow ScanImage to finish acquiring -end - -hw.samplingInterval = 1/hw.daqSampleRate; - -end - diff --git a/+tl/defaultOutputs.m b/+tl/defaultOutputs.m deleted file mode 100644 index ec7e5ab5..00000000 --- a/+tl/defaultOutputs.m +++ /dev/null @@ -1,4 +0,0 @@ -function defaults = defaultOutputs - tlOutputs = {'chrono', 'acqLive', 'clock'; 'port0/line0', 'port0/line1', 'ctr3'}; - names = {'name', 'daqChannelID'}; - defaults = cell2struct(tlOutputs, names); diff --git a/+tl/processAcquiredData.m b/+tl/processAcquiredData.m deleted file mode 100644 index d8d31bed..00000000 --- a/+tl/processAcquiredData.m +++ /dev/null @@ -1,61 +0,0 @@ -function processAcquiredData(src, event) -%TL.PROCESSACQUIREDDATA Listener for processing acquired Timeline data -% Listener function for handling Timeline data acquisition. Called by DAQ -% session with latest chunk of data. This is compiled in an array. -% Additionally, a clocking pulse is send on each call, and the sample -% index of the previous one send is found and noted. This is used by the -% timelineSecs function to convert between system time and acquisition -% time. -% -% Part of Rigbox - -% 2014-01 CB created - -global Timeline % Eek!! 'Timeline' is a global variable. - -% Timeline is officially 'running' when first acquisition samples are in -Timeline.isRunning = true; -% Assert continuity of this data from previous -assert(abs(event.TimeStamps(1) - Timeline.lastTimestamp - Timeline.hw.samplingInterval) < 1e-8,... - 'Discontinuity of DAQ acquistion detected: last timestamp was %f and this one is %f',... - Timeline.lastTimestamp, event.TimeStamps(1)); - -%% Self-clocking: -%The chrono "out" value is flipped at a recorded time, and the sample -%index that this flip is measured is noted - -% First, find the index of the flip in the latest chunk of data -clockChangeIdx = find(sign(event.Data(:,Timeline.hw.arrayChronoColumn) - 2.5) == Timeline.nextChronoSign, 1); - -%Ensure the clocking pulse was detected -if ~isempty(clockChangeIdx) - clockChangeTimestamp = event.TimeStamps(clockChangeIdx); - Timeline.currSysTimeTimelineOffset = Timeline.lastClockSentSysTime - clockChangeTimestamp; -else - warning('Rigging:Timeline:timing', 'clocking pulse not detected - probably lagging more than one data chunk'); -end - -%Now send the next clock pulse -Timeline.nextChronoSign = -Timeline.nextChronoSign; % flip next chrono -t = GetSecs; % time before output -Timeline.sessions.chrono.outputSingleScan(Timeline.nextChronoSign > 0); % send next flip -Timeline.lastClockSentSysTime = (t + GetSecs)/2; % record mean before/after time - -%% Store new samples into the timeline array -prevSampleCount = Timeline.rawDAQSampleCount; -newSampleCount = prevSampleCount + size(event.Data, 1); - -%If necessary, grow input array by doubling its size -while newSampleCount > size(Timeline.rawDAQData, 1) - disp('Reached capacity of DAQ data array, growing'); - Timeline.rawDAQData = [Timeline.rawDAQData ; zeros(size(Timeline.rawDAQData))]; -end - -%Now slice the data into the array -Timeline.rawDAQData((prevSampleCount + 1):newSampleCount,:) = event.Data; -Timeline.rawDAQSampleCount = newSampleCount; - -%Update continuity timestamp for next check -Timeline.lastTimestamp = event.TimeStamps(end); - -end \ No newline at end of file diff --git a/+tl/ptbSecsToTimeline.m b/+tl/ptbSecsToTimeline.m deleted file mode 100644 index b75c4157..00000000 --- a/+tl/ptbSecsToTimeline.m +++ /dev/null @@ -1,17 +0,0 @@ -function secs = ptbSecsToTimeline(secs) -%TL.PTBSECSTOTIMELINE Convert from Pyschtoolbox to Timeline time -% secs = TL.PTBSECSTOTIMELINE(secs) takes a timestamp 'secs' obtained -% from Pyschtoolbox's functions and converts to Timeline-relative time. -% See also TL.TIME(). -% -% Part of Rigbox - -% 2014-01 CB created - -global Timeline % Eek!! 'Timeline' is a global variable. - -assert(Timeline.isRunning, 'Timeline is not running.'); -secs = secs - Timeline.currSysTimeTimelineOffset; - -end - diff --git a/+tl/record.m b/+tl/record.m deleted file mode 100644 index 3ce8db22..00000000 --- a/+tl/record.m +++ /dev/null @@ -1,53 +0,0 @@ -function [] = record(name, event, time) -%TL.RECORD Records an event in Timeline -% TL.RECORD(name, event, [time]) records an event in the Timeline -% struct in fields prefixed with 'name', with data in 'event'. Optionally -% specify 'time', otherwise the time of call will be used (relative to -% Timeline acquisition). -% -% Part of Rigbox - -% 2014-01 CB created - -global Timeline % Eek!! 'Timeline' is a global variable. - -if nargin < 3 - % default to time now (using Timeline clock) - time = tl.time; -end - -initLength = 100; % default initial length of event data arrays - -timesFieldName = [name 'Times']; -countFieldName = [name 'Count']; -eventFieldName = [name 'Events']; - -%% create fields in Timeline struct if not already -if ~isfield(Timeline, timesFieldName) - Timeline.(timesFieldName) = zeros(initLength,1); -end -if ~isfield(Timeline, countFieldName) - Timeline.(countFieldName) = 0; -end -if ~isfield(Timeline, eventFieldName) - Timeline.(eventFieldName) = cell(initLength, 1); -end - -%% increment the event count -newCount = Timeline.(countFieldName) + 1; - -%% grow arrays if necessary -eventsLength = length(Timeline.(eventFieldName)); -if newCount > eventsLength - Timeline.(eventFieldName){2*eventsLength} = []; - Timeline.(timesFieldName) = [Timeline.(timesFieldName) ; ... - zeros(size(Timeline.(timesFieldName)))]; -end - -%% store the event at the appropriate index -Timeline.(timesFieldName)(newCount) = time; -Timeline.(eventFieldName){newCount} = event; -Timeline.(countFieldName) = newCount; - -end - diff --git a/+tl/running.m b/+tl/running.m deleted file mode 100644 index b2b6916e..00000000 --- a/+tl/running.m +++ /dev/null @@ -1,21 +0,0 @@ -function b = running() -%TL.RUNNING Reports whether Timeline is currently running -% b = TL.RUNNING() returns true if Timeline is currently running, false -% otherwise. -% -% Part of Rigbox - -% 2014-01 CB created - -global Timeline - -b = false; - -if isfield(Timeline, 'isRunning') - if Timeline.isRunning - b = true; - end -end - -end - diff --git a/+tl/start.m b/+tl/start.m deleted file mode 100644 index 2f4411b1..00000000 --- a/+tl/start.m +++ /dev/null @@ -1,177 +0,0 @@ -function start(expRef, disregardInputs, savePaths) -%TL.START Starts Timeline data acquisition -% TL.START(expRef, [disregardInputs], [savePaths]) explanation goes here -% -% Part of Rigbox - -% 2014-01 CB created - -%% Parameter initialisation -global Timeline % Eek!! 'Timeline' is a global variable. - -% expected experiment time so data structure is initialised to sensible size -maxExpectedDuration = 2*60*60; %secs - -if nargin < 3 - % default is to use default Timeline save paths for given experiment ref - savePaths = dat.expFilePath(expRef, 'timeline'); -end - -if nargin < 2 || isempty(disregardInputs) - disregardInputs = {}; -end - -%if 'disregardInputs' is a single string, wrap it in a cell -disregardInputs = ensureCell(disregardInputs); - -%% Check if it's already running, and if so, stop it -if tl.running - disp('Timeline already running, stopping first'); - tl.stop(); -end - -%% Initialise timeline struct -Timeline = struct(); -Timeline.expRef = expRef; -Timeline.savePaths = savePaths; -Timeline.isRunning = false; - -%% Setup DAQ sessions with appropriate channels -[hw, inputOptions, useInputs] = tl.config(); % get Timeline hardware configuration info - -sessions.main = daq.createSession(hw.daqVendor); - -% session for sending output pulses, which are acquired by the main -% session and used to compare daq with system time -sessions.chrono = daq.createSession(hw.daqVendor); -sessions.chrono.addDigitalChannel(hw.daqDevice, hw.chronoOutDaqChannelID, 'OutputOnly'); - -% session for outputing a high signal during acquisition -sessions.acqLive = daq.createSession(hw.daqVendor); -sessions.acqLive.addDigitalChannel(hw.daqDevice, hw.acqLiveDaqChannelID, 'OutputOnly'); -sessions.acqLive.outputSingleScan(false); % ensure acq live is false - -% session for output of timed pulses -if hw.useClockOutput - sessions.clockOut = daq.createSession(hw.daqVendor); - sessions.clockOut.IsContinuous = true; - clocked = sessions.clockOut.addCounterOutputChannel(... - hw.daqDevice, hw.clockOutputChannelID, 'PulseGeneration'); - clocked.Frequency = hw.clockOutputFrequency; - clocked.DutyCycle = hw.clockOutputDutyCycle; - if isfield(hw, 'clockOutputInitialDelay') - clocked.InitialDelay = hw.clockOutputInitialDelay; - end -end - -%now remove disregarded inputs from the list -[~, idx] = intersect(useInputs, disregardInputs); -assert(numel(idx) == numel(disregardInputs), 'Not all disregarded inputs were recognised'); -useInputs(idx) = []; -inputNames = {inputOptions.name}; - -% keep track of where each daq input will be saved into the data array -nextInputArrayColumn = 1; - -% configure all the inputs we are using -hw.inputs = struct('name', {}, 'arrayColumn', {}, 'daqChannelID', {},... - 'measurement', {}, 'terminalConfig', {}); -for i = 1:numel(useInputs) - in = inputOptions(strcmp(inputNames, useInputs(i))); - switch lower(in.measurement) - case 'voltage' - ch = sessions.main.addAnalogInputChannel(... - hw.daqDevice, in.daqChannelID, in.measurement); - if ~isempty(in.terminalConfig) - ch.TerminalConfig = in.terminalConfig; - end - case 'edgecount' - sessions.main.addCounterInputChannel(hw.daqDevice, in.daqChannelID, in.measurement); - case 'position' - ch = sessions.main.addCounterInputChannel(... - hw.daqDevice, in.daqChannelID, in.measurement); - % we assume quadrature encoding (X4) for position measurement - ch.EncoderType = 'X4'; - otherwise - error('Unknown measurement type ''%s''', in.measurement); - end - in.arrayColumn = nextInputArrayColumn; - hw.inputs(i) = in; - nextInputArrayColumn = nextInputArrayColumn + 1; -end -% save column index into data array that will contain chrono input -hw.arrayChronoColumn = pick(hw.inputs(elementByName(hw.inputs, 'chrono')), 'arrayColumn'); - -%% Send a test pulse low, then high to clocking channel & check we read it back -sessions.chrono.outputSingleScan(false); -x1 = sessions.main.inputSingleScan; -sessions.chrono.outputSingleScan(true); -x2 = sessions.main.inputSingleScan; -assert(x1(hw.arrayChronoColumn) < 2.5 && x2(hw.arrayChronoColumn) > 2.5,... - 'The clocking pulse test could not be read back'); - -%% Configure DAQ acquisition -Timeline.dataListener = sessions.main.addlistener('DataAvailable', @tl.processAcquiredData); -sessions.main.Rate = 1/hw.samplingInterval; -sessions.main.IsContinuous = true; -if ~isempty(hw.daqSamplesPerNotify) - sessions.main.NotifyWhenDataAvailableExceeds = hw.daqSamplesPerNotify; -else - sessions.main.NotifyWhenDataAvailableExceeds = sessions.main.Rate; -end - -%% Configure timeline struct -Timeline.hw = hw; -Timeline.sessions = sessions; - -% initialise daq data array -nSamples = sessions.main.Rate*maxExpectedDuration; -channelDirs = io.daqSessionChannelDirections(sessions.main); -nInputChannels = sum(strcmp(channelDirs, 'Input')); - -dataType = 'double'; % default data type for the acquired data array -if isfield(hw, 'dataType') - dataType = hw.dataType; -end - -Timeline.rawDAQData = zeros(nSamples, nInputChannels, dataType); -Timeline.rawDAQSampleCount = 0; - -%% Start the DAQ acquiring -Timeline.startDateTime = now; -Timeline.startDateTimeStr = datestr(Timeline.startDateTime); -Timeline.nextChronoSign = 1; -sessions.chrono.outputSingleScan(false); -%lastTimestamp is the timestamp of the last acquisition sample, which is -%saved to ensure continuity of acquisition. Here it is initialised as if a -%previous acquisition had been made in negative time, since the first -%acquisition timestamp will be zero -Timeline.lastTimestamp = -Timeline.hw.samplingInterval; -sessions.main.startBackground(); - -%% Output clocking pulse and wait for first acquisition to complete -% output first clocking high pulse -t = GetSecs; -sessions.chrono.outputSingleScan(Timeline.nextChronoSign > 0); -Timeline.lastClockSentSysTime = (t + GetSecs)/2; - -% wait for first acquisition processing to begin -while ~Timeline.isRunning - pause(5e-3); -end - -if isfield(hw, 'acqLiveStartDelay') - pause(hw.acqLiveStartDelay); -end - -% set acquisition live signal to true -sessions.acqLive.outputSingleScan(true); -if isfield(sessions, 'clockOut') - % start session to send timing output pulses - sessions.clockOut.startBackground(); -end - -fprintf('Timeline started successfully for ''%s''.\n', expRef); - -end - diff --git a/+tl/stop.m b/+tl/stop.m deleted file mode 100644 index 74be9c44..00000000 --- a/+tl/stop.m +++ /dev/null @@ -1,62 +0,0 @@ -function stop() -%TL.STOP Stops Timeline data acquisition -% TL.STOP() Detailed explanation goes here -% -% Part of Rigbox - -% 2014-01 CB created - -global Timeline % Eek!! 'Timeline' is a global variable - -%% Ensure timeline is actually running -if ~tl.running - warning('Nothing to do, Timeline is not running!'); - return -end - -% kill acquisition output signals -Timeline.sessions.acqLive.outputSingleScan(false); % live -> false -if isfield(Timeline.sessions, 'clockOut') - % stop sending timing output pulses - Timeline.sessions.clockOut.stop(); -end - -% pause to ensure all systems can stop -if isfield(Timeline.hw, 'stopDelay') - stopDelay = Timeline.hw.stopDelay; -else - stopDelay = 2; -end -pause(stopDelay); %temporary delay hack - -% stop actual DAQ aquisition -Timeline.sessions.main.stop(); - -% wait before deleting the listener to ensure most recent samples are -% collected -pause(1.5); -delete(Timeline.dataListener); % now delete the data listener - -% turn off the timeline running flag -Timeline.isRunning = false; - -% release hardware resources -cellfun(@(s) s.release(), struct2cell(Timeline.sessions)); - -% for saving the Timeline struct we remove sessions fields -Timeline = rmfield(Timeline, {'sessions' 'dataListener'}); -% only keep the used part of the daq input array -sampleCount = Timeline.rawDAQSampleCount; -Timeline.rawDAQData((sampleCount + 1):end,:) = []; - -% generate timestamps in seconds for the samples -Timeline.rawDAQTimestamps = ... - Timeline.hw.samplingInterval*(0:Timeline.rawDAQSampleCount - 1); - -% save Timeline to all paths -superSave(Timeline.savePaths, struct('Timeline', Timeline)); - -fprintf('Timeline for ''%s'' stopped and saved successfully.\n', Timeline.expRef); - -end - diff --git a/+tl/time.m b/+tl/time.m deleted file mode 100644 index f04fc412..00000000 --- a/+tl/time.m +++ /dev/null @@ -1,30 +0,0 @@ -function secs = time(strict) -%TL.TIME Time relative to Timeline acquisition -% secs = TL.TIME([strict]) Returns the time in seconds relative to -% Timeline data acquistion. 'strict' is optional (defaults to true), and -% if true, this function will fail if Timeline is not running. If false, -% it will just return the time using Psychtoolbox GetSecs if it's not -% running. See also TL.PTBSECSTOTIMELINE(). -% -% Part of Rigbox - -% 2014-01 CB created - -global Timeline % Eek!! 'Timeline' is a global variable. - -if nargin < 1 - strict = true; -end - -if tl.running - secs = GetSecs - Timeline.currSysTimeTimelineOffset; -elseif strict - error('Tried to use Timeline clock when Timeline is not running'); -else - % Timeline not running, but not being 'strict' so just return the system - % time as if it were the Timeline clock - secs = GetSecs; -end - -end - diff --git a/addRigboxPaths.m b/addRigboxPaths.m index 301feee0..2fa54a8b 100644 --- a/addRigboxPaths.m +++ b/addRigboxPaths.m @@ -38,10 +38,7 @@ function addRigboxPaths(savePaths) cortexLabAddonsPath,... % add the Rigging cortexlab add-ons rigboxPath,... % add Rigbox itself cbToolsPath,... % add cb-tools root dir - fullfile(cbToolsPath, 'burgbox'),... % Burgbox - fullfile(cbToolsPath, 'jsonlab'),... % jsonlab for JSON encoding - fullfile(cbToolsPath, 'urlread2')... % urlread2 for http requests - ); + fullfile(cbToolsPath, 'burgbox')); % Burgbox % guiLayoutPath,... % add GUI Layout toolbox % fullfile(guiLayoutPath, 'layout'),... % fullfile(guiLayoutPath, 'Patch'),... diff --git a/cb-tools/SuperWebSocket/Config/log4net.config b/cb-tools/SuperWebSocket/Config/log4net.config deleted file mode 100644 index efa786b7..00000000 --- a/cb-tools/SuperWebSocket/Config/log4net.config +++ /dev/null @@ -1,69 +0,0 @@ -ïğż - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cb-tools/SuperWebSocket/Config/log4net.unix.config b/cb-tools/SuperWebSocket/Config/log4net.unix.config deleted file mode 100644 index d6f35702..00000000 --- a/cb-tools/SuperWebSocket/Config/log4net.unix.config +++ /dev/null @@ -1,69 +0,0 @@ -ïğż - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cb-tools/SuperWebSocket/InstallService.bat b/cb-tools/SuperWebSocket/InstallService.bat deleted file mode 100644 index 50530656..00000000 --- a/cb-tools/SuperWebSocket/InstallService.bat +++ /dev/null @@ -1,2 +0,0 @@ -SuperSocket.SocketService.exe -i -pause \ No newline at end of file diff --git a/cb-tools/SuperWebSocket/Newtonsoft.Json.dll b/cb-tools/SuperWebSocket/Newtonsoft.Json.dll deleted file mode 100644 index 67b9d351..00000000 Binary files a/cb-tools/SuperWebSocket/Newtonsoft.Json.dll and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.Common.dll b/cb-tools/SuperWebSocket/SuperSocket.Common.dll deleted file mode 100644 index cb07bd26..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.Common.dll and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.Common.pdb b/cb-tools/SuperWebSocket/SuperSocket.Common.pdb deleted file mode 100644 index 33b1455a..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.Common.pdb and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.Common.xml b/cb-tools/SuperWebSocket/SuperSocket.Common.xml deleted file mode 100644 index 884adfb8..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.Common.xml +++ /dev/null @@ -1,1224 +0,0 @@ - - - - SuperSocket.Common - - - - - Gets the array. - - - - - Gets the count. - - - - - Gets the offset. - - - - - ArraySegmentList - - - - - - Initializes a new instance of the class. - - - - - Determines the index of a specific item in the . - - The object to locate in the . - - The index of if found in the list; otherwise, -1. - - - - - NotSupported - - - - - NotSupported - - - - - NotSupported - - - - - NotSupported - - - - - NotSupported - - - - - Copies to. - - The array. - Index of the array. - - - - NotSupported - - - - - NotSupported - - - - - NotSupported - - - - - Removes the segment at. - - The index. - - - - Adds the segment to the list. - - The array. - The offset. - The length. - - - - Adds the segment to the list. - - The array. - The offset. - The length. - if set to true [to be copied]. - - - - Clears all the segements. - - - - - Read all data in this list to the array data. - - - - - - Read the data in specific range to the array data. - - The start index. - The length. - - - - - Trims the end. - - Size of the trim. - - - - Searches the last segment. - - The state. - - - - - Copies to. - - To. - - - - - Copies to. - - To. - Index of the SRC. - To index. - The length. - - - - - Gets or sets the element at the specified index. - - - The element at the specified index. - - - is not a valid index in the . - - - - The property is set and the is read-only. - - - - - Gets the number of elements contained in the . - - - The number of elements contained in the . - - - - - Gets a value indicating whether the is read-only. - - true if the is read-only; otherwise, false. - - - - - Gets the segment count. - - - - - ArraySegmentList - - - - - Decodes bytes to string by the specified encoding. - - The encoding. - - - - - Decodes bytes to string by the specified encoding. - - The encoding. - The offset. - The length. - - - - - Decodes data by the mask. - - The mask. - The offset. - The length. - - - - Assembly Util Class - - - - - Creates the instance from type name. - - - The type. - - - - - Creates the instance from type name and parameters. - - - The type. - The parameters. - - - - - Gets the implement types from assembly. - - The type of the base type. - The assembly. - - - - - Gets the implemented objects by interface. - - The type of the base interface. - The assembly. - - - - - Gets the implemented objects by interface. - - The type of the base interface. - The assembly. - Type of the target. - - - - - Clone object in binary format. - - - The target. - - - - - Copies the properties of one object to another object. - - The source. - The target. - - - - Gets the assemblies from string. - - The assembly def. - - - - - Gets the assemblies from strings. - - The assemblies. - - - - - Binary util class - - - - - Search target from source. - - - The source. - The target. - The pos. - The length. - - - - - Searches the mark from source. - - - The source. - The mark. - - - - - Searches the mark from source. - - - The source. - The offset. - The length. - The mark. - - - - - Searches the mark from source. - - - The source. - The offset. - The length. - The mark. - The matched. - - - - - Searches the mark from source. - - - The source. - The offset. - The length. - State of the search. - - - - - Startses the with. - - - The source. - The mark. - - - - - Startses the with. - - - The source. - The offset. - The length. - The mark. - - - - - Endses the with. - - - The source. - The mark. - - - - - Endses the with. - - - The source. - The offset. - The length. - The mark. - - - - - Clones the elements in the specific range. - - - The source. - The offset. - The length. - - - - - This class creates a single large buffer which can be divided up and assigned to SocketAsyncEventArgs objects for use - with each socket I/O operation. This enables bufffers to be easily reused and gaurds against fragmenting heap memory. - - The operations exposed on the BufferManager class are not thread safe. - - - - - Initializes a new instance of the class. - - The total bytes. - Size of the buffer. - - - - Allocates buffer space used by the buffer pool - - - - - Assigns a buffer from the buffer pool to the specified SocketAsyncEventArgs object - - true if the buffer was successfully set, else false - - - - Removes the buffer from a SocketAsyncEventArg object. This frees the buffer back to the - buffer pool - - - - - ConfigurationElementBase - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - if set to true [name required]. - - - - Reads XML from the configuration file. - - The that reads from the configuration file. - true to serialize only the collection key properties; otherwise, false. - The element to read is locked.- or -An attribute of the current node is not recognized.- or -The lock status of the current node cannot be determined. - - - - Gets a value indicating whether an unknown attribute is encountered during deserialization. - - The name of the unrecognized attribute. - The value of the unrecognized attribute. - - true when an unknown attribute is encountered while deserializing; otherwise, false. - - - - - Gets a value indicating whether an unknown element is encountered during deserialization. - - The name of the unknown subelement. - The being used for deserialization. - - true when an unknown element is encountered while deserializing; otherwise, false. - - The element identified by is locked.- or -One or more of the element's attributes is locked.- or - is unrecognized, or the element has an unrecognized attribute.- or -The element has a Boolean attribute with an invalid value.- or -An attempt was made to deserialize a property more than once.- or -An attempt was made to deserialize a property that is not a valid member of the element.- or -The element cannot contain a CDATA or text element. - - - - Gets the name. - - - - - Gets the options. - - - - - Gets the option elements. - - - - - Configuration extension class - - - - - Gets the value from namevalue collection by key. - - The collection. - The key. - - - - - Gets the value from namevalue collection by key. - - The collection. - The key. - The default value. - - - - - Deserializes the specified configuration section. - - The type of the element. - The section. - The reader. - - - - Gets the child config. - - The type of the config. - The child elements. - Name of the child config. - - - - - Gets the config source path. - - The config. - - - - - Extension class for IDictionary - - - - - Gets the value by key. - - - The dictionary. - The key. - - - - - Gets the value by key and default value. - - - The dictionary. - The key. - The default value. - - - - - EventArgs for error and exception - - - - - Initializes a new instance of the class. - - The message. - - - - Initializes a new instance of the class. - - The exception. - - - - Gets the exception. - - - - - GenericConfigurationElementCollectionBase - - The type of the config element. - The type of the config interface. - - - - When overridden in a derived class, creates a new . - - - A new . - - - - - Gets the element key for a specified configuration element when overridden in a derived class. - - The to return the key for. - - An that acts as the key for the specified . - - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - - - - Gets or sets a property, attribute, or child element of this configuration element. - - The specified property, attribute, or child element - - - - GenericConfigurationElementCollection - - The type of the config element. - The type of the config interface. - - - - Gets the element key. - - The element. - - - - - This class is designed for detect platform attribute in runtime - - - - - Gets a value indicating whether [support socket IO control by code enum]. - - - true if [support socket IO control by code enum]; otherwise, false. - - - - - Gets a value indicating whether this instance is mono. - - - true if this instance is mono; otherwise, false. - - - - - SearchMarkState - - - - - - Initializes a new instance of the class. - - The mark. - - - - Gets the mark. - - - - - Gets or sets whether matched already. - - - The matched. - - - - - SendingQueue - - - - - Initializes a new instance of the class. - - The global queue. - The offset. - The capacity. - - - - Enqueues the specified item. - - The item. - The track ID. - - - - - Enqueues the specified items. - - The items. - The track ID. - - - - - Stops the enqueue, and then wait all current excueting enqueu threads exit. - - - - - Starts to allow enqueue. - - - - - Determines the index of a specific item in the . - - The object to locate in the . - - The index of if found in the list; otherwise, -1. - - - - - - Inserts an item to the at the specified index. - - The zero-based index at which should be inserted. - The object to insert into the . - - - - - Removes the item at the specified index. - - The zero-based index of the item to remove. - - - - - Adds an item to the . - - The object to add to the . - - - - - Removes all items from the . - - - - - - Determines whether the contains a specific value. - - The object to locate in the . - - true if is found in the ; otherwise, false. - - - - - - Copies to. - - The array. - Index of the array. - - - - Removes the first occurrence of a specific object from the . - - The object to remove from the . - - true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - - - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - - - - - Returns an enumerator that iterates through a collection. - - - An object that can be used to iterate through the collection. - - - - - - Gets the track ID. - - - The track ID. - - - - - Gets the global queue. - - - The global queue. - - - - - Gets the offset. - - - The offset. - - - - - Gets the capacity. - - - The capacity. - - - - - Gets the number of elements contained in the . - - The number of elements contained in the . - - - - Gets or sets the position. - - - The position. - - - - - Gets or sets the element at the specified index. - - The index. - - - - - - Gets a value indicating whether the is read-only. - - true if the is read-only; otherwise, false. - - - - SendingQueueSourceCreator - - - - - ISmartPoolSourceCreator - - - - - - Creates the specified size. - - The size. - The pool items. - - - - - Initializes a new instance of the class. - - Size of the sending queue. - - - - Creates the specified size. - - The size. - The pool items. - - - - - The pool information class - - - - - Gets the min size of the pool. - - - The min size of the pool. - - - - - Gets the max size of the pool. - - - The max size of the pool. - - - - - Gets the avialable items count. - - - The avialable items count. - - - - - Gets the total items count, include items in the pool and outside the pool. - - - The total items count. - - - - - The basic interface of smart pool - - - - - - Initializes the specified min pool size. - - The min size of the pool. - The max size of the pool. - The source creator. - - - - - Pushes the specified item into the pool. - - The item. - - - - Tries to get one item from the pool. - - The item. - - - - - ISmartPoolSource - - - - - Gets the count. - - - The count. - - - - - SmartPoolSource - - - - - Initializes a new instance of the class. - - The source. - The items count. - - - - Gets the source. - - - The source. - - - - - Gets the count. - - - The count. - - - - - The smart pool - - - - - - Initializes the specified min and max pool size. - - The min size of the pool. - The max size of the pool. - The source creator. - - - - Pushes the specified item into the pool. - - The item. - - - - Tries to get one item from the pool. - - The item. - - - - - - Gets the size of the min pool. - - - The size of the min pool. - - - - - Gets the size of the max pool. - - - The size of the max pool. - - - - - Gets the avialable items count. - - - The avialable items count. - - - - - Gets the total items count, include items in the pool and outside the pool. - - - The total items count. - - - - - Socket extension class - - - - - Close the socket safely. - - The socket. - - - - Sends the data. - - The client. - The data. - - - - Sends the data. - - The client. - The data. - The offset. - The length. - - - - String extension class - - - String extension - - - - - Convert string to int32. - - The source. - - - - - Convert string to int32. - - The source. - The default value. - - - - - Convert string to long. - - The source. - - - - - Convert string to long. - - The source. - The default value. - - - - - Convert string to short. - - The source. - - - - - Convert string to short. - - The source. - The default value. - - - - - Convert string to decimal. - - The source. - - - - - Convert string to decimal. - - The source. - The default value. - - - - - Convert string to date time. - - The source. - - - - - Convert string to date time. - - The source. - The default value. - - - - - Convert string to boolean. - - The source. - - - - - Convert string tp boolean. - - The source. - if set to true [default value]. - - - - - Tries parse string to enum. - - the enum type - The value. - if set to true [ignore case]. - The enum value. - - - - - Thread pool extension class - - - - - Resets the thread pool. - - The max working threads. - The max completion port threads. - The min working threads. - The min completion port threads. - - - - diff --git a/cb-tools/SuperWebSocket/SuperSocket.Facility.XML b/cb-tools/SuperWebSocket/SuperSocket.Facility.XML deleted file mode 100644 index 77c6fc99..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.Facility.XML +++ /dev/null @@ -1,467 +0,0 @@ - - - - SuperSocket.Facility - - - - - Flash policy AppServer - - - - - PolicyServer base class - - - - - Initializes a new instance of the class. - - - - - Setups the specified root config. - - The root config. - The config. - - - - - Setups the policy response. - - The policy file data. - - - - - Gets the policy file response. - - The client end point. - - - - - Processes the request. - - The session. - The data. - - - - Gets the policy response. - - - - - Initializes a new instance of the class. - - - - - Setups the policy response. - - The policy file data. - - - - - PolicyReceiveFilter - - - - - FixedSizeReceiveFilter - - The type of the request info. - - - - Null RequestInfo - - - - - Initializes a new instance of the class. - - The size. - - - - Filters the specified session. - - The read buffer. - The offset. - The length. - if set to true [to be copied]. - The rest. - - - - - Filters the buffer after the server receive the enough size of data. - - The buffer. - The offset. - The length. - if set to true [to be copied]. - - - - - Resets this instance. - - - - - Gets the size of the fixed size Receive filter. - - - - - Gets the size of the rest buffer. - - - The size of the rest buffer. - - - - - Gets the next Receive filter. - - - - - Gets the offset delta. - - - - - Gets the filter state. - - - The filter state. - - - - - Initializes a new instance of the class. - - The size. - - - - Filters the buffer after the server receive the enough size of data. - - The buffer. - The offset. - The length. - if set to true [to be copied]. - - - - - Initializes a new instance of the class. - - Size of the fix request. - - - - Creates the filter. - - The app server. - The app session. - The remote end point. - - - - - Gets the size of the fix request. - - - The size of the fix request. - - - - - PolicySession - - - - - Silverlight policy AppServer - - - - - Initializes a new instance of the class. - - - - - Processes the request. - - The session. - The data. - - - - ReceiveFilter for the protocol that each request has bengin and end mark - - The type of the request info. - - - - Null request info - - - - - Initializes a new instance of the class. - - The begin mark. - The end mark. - - - - Filters the specified session. - - The read buffer. - The offset. - The length. - if set to true [to be copied]. - The rest. - - - - - Processes the matched request. - - The read buffer. - The offset. - The length. - - - - - Resets this instance. - - - - - This Receive filter is designed for this kind protocol: - each request has fixed count part which splited by a char(byte) - for instance, request is defined like this "#12122#23343#4545456565#343435446#", - because this request is splited into many parts by 5 '#', we can create a Receive filter by CountSpliterRequestFilter((byte)'#', 5) - - The type of the request info. - - - - Null request info instance - - - - - Initializes a new instance of the class. - - The spliter. - The spliter count. - - - - Filters the specified session. - - The read buffer. - The offset. - The length. - if set to true [to be copied]. - The rest. - - - - - Processes the matched request. - - The read buffer. - The offset. - The length. - - - - - Resets this instance. - - - - - Gets the size of the rest buffer. - - - The size of the rest buffer. - - - - - Gets the next Receive filter. - - - - - Gets the offset delta relative original receiving offset which will be used for next round receiving. - - - - - Gets the filter state. - - - The filter state. - - - - - This Receive filter is designed for this kind protocol: - each request has fixed count part which splited by a char(byte) - for instance, request is defined like this "#12122#23343#4545456565#343435446#", - because this request is splited into many parts by 5 '#', we can create a Receive filter by CountSpliterRequestFilter((byte)'#', 5) - - - - - Initializes a new instance of the class. - - The spliter. - The spliter count. - - - - Initializes a new instance of the class. - - The spliter. - The spliter count. - The encoding. - - - - Initializes a new instance of the class. - - The spliter. - The spliter count. - The encoding. - Index of the key. - - - - Processes the matched request. - - The read buffer. - The offset. - The length. - - - - - ReceiveFilterFactory for CountSpliterReceiveFilter - - The type of the Receive filter. - The type of the request info. - - - - Creates the filter. - - The app server. - The app session. - The remote end point. - - - - - ReceiveFilterFactory for CountSpliterReceiveFilter - - The type of the Receive filter. - - - - receiveFilterFactory for CountSpliterRequestFilter - - - - - Initializes a new instance of the class. - - The spliter. - The count. - - - - Creates the filter. - - The app server. - The app session. - The remote end point. - - - - - FixedHeaderReceiveFilter, - it is the Receive filter base for the protocol which define fixed length header and the header contains the request body length, - you can implement your own Receive filter for this kind protocol easily by inheriting this class - - The type of the request info. - - - - Initializes a new instance of the class. - - Size of the header. - - - - Filters the specified session. - - The read buffer. - The offset. - The length. - if set to true [to be copied]. - The rest. - - - - - Processes the fix size request. - - The buffer. - The offset. - The length. - if set to true [to be copied]. - - - - - Gets the body length from header. - - The header. - The offset. - The length. - - - - - Resolves the request data. - - The header. - The body buffer. - The offset. - The length. - - - - - Resets this instance. - - - - diff --git a/cb-tools/SuperWebSocket/SuperSocket.Facility.dll b/cb-tools/SuperWebSocket/SuperSocket.Facility.dll deleted file mode 100644 index 159a9d98..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.Facility.dll and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.Facility.pdb b/cb-tools/SuperWebSocket/SuperSocket.Facility.pdb deleted file mode 100644 index 5193d970..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.Facility.pdb and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.dll b/cb-tools/SuperWebSocket/SuperSocket.SocketBase.dll deleted file mode 100644 index ca403ad0..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.dll and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.pdb b/cb-tools/SuperWebSocket/SuperSocket.SocketBase.pdb deleted file mode 100644 index f877f709..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.pdb and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.xml b/cb-tools/SuperWebSocket/SuperSocket.SocketBase.xml deleted file mode 100644 index 61d2d077..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.SocketBase.xml +++ /dev/null @@ -1,4974 +0,0 @@ - - - - SuperSocket.SocketBase - - - - - Async extension class - - - - - Runs the specified task. - - The log provider. - The task. - - - - - Runs the specified task. - - The log provider. - The task. - The task option. - - - - - Runs the specified task. - - The log provider. - The task. - The exception handler. - - - - - Runs the specified task. - - The log provider. - The task. - The task option. - The exception handler. - - - - - Runs the specified task. - - The log provider. - The task. - The state. - - - - - Runs the specified task. - - The log provider. - The task. - The state. - The task option. - - - - - Runs the specified task. - - The log provider. - The task. - The state. - The exception handler. - - - - - Runs the specified task. - - The log provider. - The task. - The state. - The task option. - The exception handler. - - - - - Command Executing Context - - - - - Initializes a new instance of the class. - - The session. - The request info. - The command. - - - - Gets the session. - - - - - Gets the request info. - - - - - Gets the current command. - - - - - Gets or sets a value indicating whether this command executing is cancelled. - - - true if cancel; otherwise, false. - - - - - Command filter attribute - - - - - Called when [command executing]. - - The command context. - - - - Called when [command executed]. - - The command context. - - - - Gets or sets the execution order. - - - The order. - - - - - CommandLoader base class - - - - - Command loader's interface - - - - - Initializes the command loader - - The type of the command. - The root config. - The app server. - - - - - Tries to load commands. - - The commands. - - - - - Occurs when [updated]. - - - - - Occurs when [error]. - - - - - Initializes the command loader - - The type of the command. - The root config. - The app server. - - - - - Tries to load commands. - - The commands. - - - - - Called when [updated]. - - The commands. - - - - Called when [error]. - - The message. - - - - Called when [error]. - - The e. - - - - Occurs when [updated]. - - - - - Occurs when [error]. - - - - - CommandUpdateEventArgs - - - - - - Initializes a new instance of the class. - - The commands. - - - - Gets the commands updated. - - - - - Command assembly config - - - - - The basic interface for command assembly config - - - - - Gets the assembly name. - - - The assembly. - - - - - Gets or sets the assembly name. - - - The assembly. - - - - - Configuration source interface - - - - - The root configuration interface - - - - - Gets the child config. - - The type of the config. - Name of the child config. - - - - - Gets the max working threads. - - - - - Gets the min working threads. - - - - - Gets the max completion port threads. - - - - - Gets the min completion port threads. - - - - - Gets a value indicating whether [disable performance data collector]. - - - true if [disable performance data collector]; otherwise, false. - - - - - Gets the performance data collect interval, in seconds. - - - - - Gets the log factory name. - - - The log factory. - - - - - Gets the isolation mode. - - - - - Gets the option elements. - - - - - Gets the servers definitions. - - - - - Gets the appServer types definition. - - - - - Gets the connection filters definition. - - - - - Gets the log factories definition. - - - - - Gets the Receive filter factories definition. - - - - - Gets the command loaders definition. - - - - - TypeProvider's interface - - - - - Gets the name. - - - - - Gets the type. - - - - - Poco configuration source - - - - - Root configuration model - - - - - Initializes a new instance of the class. - - The root config. - - - - Initializes a new instance of the class. - - - - - Gets the child config. - - The type of the config. - Name of the child config. - - - - - Gets/Sets the max working threads. - - - - - Gets/sets the min working threads. - - - - - Gets/sets the max completion port threads. - - - - - Gets/sets the min completion port threads. - - - - - Gets/sets the performance data collect interval, in seconds. - - - - - Gets/sets a value indicating whether [disable performance data collector]. - - - true if [disable performance data collector]; otherwise, false. - - - - - Gets/sets the isolation mode. - - - - - Gets/sets the log factory name. - - - The log factory. - - - - - Gets/sets the option elements. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The source. - - - - Gets the servers definitions. - - - - - Gets/sets the server types definition. - - - - - Gets/sets the connection filters definition. - - - - - Gets/sets the log factories definition. - - - - - Gets/sets the Receive filter factories definition. - - - - - Gets/sets the command loaders definition. - - - - - Type provider configuration - - - - - Gets the name. - - - - - Gets the type. - - - - - Type provider colletion configuration - - - - - When overridden in a derived class, creates a new . - - - A new . - - - - - Gets the element key for a specified configuration element when overridden in a derived class. - - The to return the key for. - - An that acts as the key for the specified . - - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - - - - TypeProviderConfig - - - - - Gets the name. - - - - - Gets the type. - - - - - Display attribute - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The name. - - - - Gets the name. - - - - - Gets or sets the short name. - - - The short name. - - - - - Gets or sets the format. - - - The format. - - - - - Gets or sets the order. - - - The order. - - - - - Gets or sets a value indicating whether [output in perf log]. - - - true if [output in perf log]; otherwise, false. - - - - - Extensions class for SocketBase project - - - - - Gets the app server instance in the bootstrap by name, ignore case - - The bootstrap. - The name of the appserver instance. - - - - - - The bootstrap start result - - - - - No appserver has been set in the bootstrap, so nothing was started - - - - - All appserver instances were started successfully - - - - - Some appserver instances were started successfully, but some of them failed - - - - - All appserver instances failed to start - - - - - SuperSocket bootstrap - - - - - Initializes the bootstrap with the configuration - - - - - - Initializes the bootstrap with a listen endpoint replacement dictionary - - The listen end point replacement. - - - - - Initializes the bootstrap with the configuration - - The server config resolver. - - - - - Initializes the bootstrap with the configuration - - The log factory. - - - - - Initializes the bootstrap with the configuration - - The server config resolver. - The log factory. - - - - - Starts this bootstrap. - - - - - - Stops this bootstrap. - - - - - Gets all the app servers running in this bootstrap - - - - - Gets the config. - - - - - Gets the startup config file. - - - - - The interface for who provides logger - - - - - Gets the logger assosiated with this object. - - - - - AppServer instance running isolation mode - - - - - No isolation - - - - - Isolation by AppDomain - - - - - An item can be started and stopped - - - - - Setups with the specified root config. - - The bootstrap. - The socket server instance config. - The factories. - - - - - Starts this server instance. - - return true if start successfull, else false - - - - Stops this server instance. - - - - - Collects the server summary. - - The node summary. - - - - - Gets the name. - - - - - Gets the current state of the work item. - - - The state. - - - - - Gets the total session count. - - - - - Gets the state of the server. - - - The state of the server. - - - - - Console Log - - - - - Log interface - - - - - Logs the debug message. - - The message. - - - - Logs the debug message. - - The message. - The exception. - - - - Logs the debug message. - - The format. - The arg0. - - - - Logs the debug message. - - The format. - The args. - - - - Logs the debug message. - - The provider. - The format. - The args. - - - - Logs the debug message. - - The format. - The arg0. - The arg1. - - - - Logs the debug message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the error message. - - The message. - - - - Logs the error message. - - The message. - The exception. - - - - Logs the error message. - - The format. - The arg0. - - - - Logs the error message. - - The format. - The args. - - - - Logs the error message. - - The provider. - The format. - The args. - - - - Logs the error message. - - The format. - The arg0. - The arg1. - - - - Logs the error message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the fatal error message. - - The message. - - - - Logs the fatal error message. - - The message. - The exception. - - - - Logs the fatal error message. - - The format. - The arg0. - - - - Logs the fatal error message. - - The format. - The args. - - - - Logs the fatal error message. - - The provider. - The format. - The args. - - - - Logs the fatal error message. - - The format. - The arg0. - The arg1. - - - - Logs the fatal error message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the info message. - - The message. - - - - Logs the info message. - - The message. - The exception. - - - - Logs the info message. - - The format. - The arg0. - - - - Logs the info message. - - The format. - The args. - - - - Logs the info message. - - The provider. - The format. - The args. - - - - Logs the info message. - - The format. - The arg0. - The arg1. - - - - Logs the info message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the warning message. - - The message. - - - - Logs the warning message. - - The message. - The exception. - - - - Logs the warning message. - - The format. - The arg0. - - - - Logs the warning message. - - The format. - The args. - - - - Logs the warning message. - - The provider. - The format. - The args. - - - - Logs the warning message. - - The format. - The arg0. - The arg1. - - - - Logs the warning message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Gets a value indicating whether this instance is debug enabled. - - - true if this instance is debug enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is error enabled. - - - true if this instance is error enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is fatal enabled. - - - true if this instance is fatal enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is info enabled. - - - true if this instance is info enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is warn enabled. - - - true if this instance is warn enabled; otherwise, false. - - - - - Initializes a new instance of the class. - - The name. - - - - Logs the debug message. - - The message. - - - - Logs the debug message. - - The message. - The exception. - - - - Logs the debug message. - - The format. - The arg0. - - - - Logs the debug message. - - The format. - The args. - - - - Logs the debug message. - - The provider. - The format. - The args. - - - - Logs the debug message. - - The format. - The arg0. - The arg1. - - - - Logs the debug message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the error message. - - The message. - - - - Logs the error message. - - The message. - The exception. - - - - Logs the error message. - - The format. - The arg0. - - - - Logs the error message. - - The format. - The args. - - - - Logs the error message. - - The provider. - The format. - The args. - - - - Logs the error message. - - The format. - The arg0. - The arg1. - - - - Logs the error message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the fatal error message. - - The message. - - - - Logs the fatal error message. - - The message. - The exception. - - - - Logs the fatal error message. - - The format. - The arg0. - - - - Logs the fatal error message. - - The format. - The args. - - - - Logs the fatal error message. - - The provider. - The format. - The args. - - - - Logs the fatal error message. - - The format. - The arg0. - The arg1. - - - - Logs the fatal error message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the info message. - - The message. - - - - Logs the info message. - - The message. - The exception. - - - - Logs the info message. - - The format. - The arg0. - - - - Logs the info message. - - The format. - The args. - - - - Logs the info message. - - The provider. - The format. - The args. - - - - Logs the info message. - - The format. - The arg0. - The arg1. - - - - Logs the info message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the warning message. - - The message. - - - - Logs the warning message. - - The message. - The exception. - - - - Logs the warning message. - - The format. - The arg0. - - - - Logs the warning message. - - The format. - The args. - - - - Logs the warning message. - - The provider. - The format. - The args. - - - - Logs the warning message. - - The format. - The arg0. - The arg1. - - - - Logs the warning message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Gets a value indicating whether this instance is debug enabled. - - - true if this instance is debug enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is error enabled. - - - true if this instance is error enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is fatal enabled. - - - true if this instance is fatal enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is info enabled. - - - true if this instance is info enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is warn enabled. - - - true if this instance is warn enabled; otherwise, false. - - - - - Console log factory - - - - - LogFactory Interface - - - - - Gets the log by name. - - The name. - - - - - Gets the log by name. - - The name. - - - - - Log4NetLog - - - - - Initializes a new instance of the class. - - The log. - - - - Logs the debug message. - - The message. - - - - Logs the debug message. - - The message. - The exception. - - - - Logs the debug message. - - The format. - The arg0. - - - - Logs the debug message. - - The format. - The args. - - - - Logs the debug message. - - The provider. - The format. - The args. - - - - Logs the debug message. - - The format. - The arg0. - The arg1. - - - - Logs the debug message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the error message. - - The message. - - - - Logs the error message. - - The message. - The exception. - - - - Logs the error message. - - The format. - The arg0. - - - - Logs the error message. - - The format. - The args. - - - - Logs the error message. - - The provider. - The format. - The args. - - - - Logs the error message. - - The format. - The arg0. - The arg1. - - - - Logs the error message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the fatal error message. - - The message. - - - - Logs the fatal error message. - - The message. - The exception. - - - - Logs the fatal error message. - - The format. - The arg0. - - - - Logs the fatal error message. - - The format. - The args. - - - - Logs the fatal error message. - - The provider. - The format. - The args. - - - - Logs the fatal error message. - - The format. - The arg0. - The arg1. - - - - Logs the fatal error message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the info message. - - The message. - - - - Logs the info message. - - The message. - The exception. - - - - Logs the info message. - - The format. - The arg0. - - - - Logs the info message. - - The format. - The args. - - - - Logs the info message. - - The provider. - The format. - The args. - - - - Logs the info message. - - The format. - The arg0. - The arg1. - - - - Logs the info message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Logs the warning message. - - The message. - - - - Logs the warning message. - - The message. - The exception. - - - - Logs the warning message. - - The format. - The arg0. - - - - Logs the warning message. - - The format. - The args. - - - - Logs the warning message. - - The provider. - The format. - The args. - - - - Logs the warning message. - - The format. - The arg0. - The arg1. - - - - Logs the warning message. - - The format. - The arg0. - The arg1. - The arg2. - - - - Gets a value indicating whether this instance is debug enabled. - - - true if this instance is debug enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is error enabled. - - - true if this instance is error enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is fatal enabled. - - - true if this instance is fatal enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is info enabled. - - - true if this instance is info enabled; otherwise, false. - - - - - Gets a value indicating whether this instance is warn enabled. - - - true if this instance is warn enabled; otherwise, false. - - - - - Log4NetLogFactory - - - - - LogFactory Base class - - - - - Initializes a new instance of the class. - - The config file. - - - - Gets the log by name. - - The name. - - - - - Gets the config file file path. - - - - Gets a value indicating whether the server instance is running in isolation mode and the multiple server instances share the same logging configuration. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The log4net config. - - - - Gets the log by name. - - The name. - - - - - GlobalPerformanceData class - - - - - Gets or sets the cpu usage. - - - The cpu usage. - - - - - Gets or sets the working set. - - - The working set. - - - - - Gets or sets the total thread count. - - - The total thread count. - - - - - Gets or sets the available working threads. - - - The available working threads. - - - - - Gets or sets the available completion port threads. - - - The available completion port threads. - - - - - Gets or sets the max working threads. - - - The max working threads. - - - - - Gets or sets the max completion port threads. - - - The max completion port threads. - - - - - CommandLine RequestFilter Factory - - - - - Terminator ReceiveFilter Factory - - - - - Receive filter factory interface - - The type of the request info. - - - - Receive filter factory interface - - - - - Creates the Receive filter. - - The app server. - The app session. - The remote end point. - - the new created request filer assosiated with this socketSession - - - - - Initializes a new instance of the class. - - The terminator. - - - - Initializes a new instance of the class. - - The terminator. - The encoding. - - - - Initializes a new instance of the class. - - The terminator. - The encoding. - The line parser. - - - - Creates the Receive filter. - - The app server. - The app session. - The remote end point. - - the new created request filer assosiated with this socketSession - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The encoding. - - - - Initializes a new instance of the class. - - The encoding. - The request info parser. - - - - DefaultreceiveFilterFactory - - The type of the Receive filter. - The type of the request info. - - - - Creates the Receive filter. - - The app server. - The app session. - The remote end point. - - the new created request filer assosiated with this socketSession - - - - - Filter state enum - - - - - Normal state - - - - - Error state - - - - - The interface for a Receive filter to adapt receiving buffer offset - - - - - Gets the offset delta. - - - - - Receive filter interface - - The type of the request info. - - - - Filters received data of the specific session into request info. - - The read buffer. - The offset of the current received data in this read buffer. - The length of the current received data. - if set to true [to be copied]. - The rest, the length of the data which hasn't been parsed. - - - - - Resets this instance to initial state. - - - - - Gets the size of the rest buffer. - - - The size of the rest buffer. - - - - - Gets the next Receive filter. - - - - - Gets the filter state. - - - The filter state. - - - - - Provide the initializing interface for ReceiveFilter - - - - - Initializes the ReceiveFilter with the specified appServer and appSession - - The app server. - The session. - - - - Receive filter base class - - The type of the request info. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The previous Receive filter. - - - - Initializes the specified previous Receive filter. - - The previous Receive filter. - - - - Filters received data of the specific session into request info. - - The read buffer. - The offset of the current received data in this read buffer. - The length of the current received data. - if set to true [to be copied]. - The rest, the length of the data which hasn't been parsed. - - - - - Gets the rest buffer. - - - - - - Adds the array segment. - - The buffer. - The offset. - The length. - if set to true [to be copied]. - - - - Clears the buffer segments. - - - - - Resets this instance to initial state. - - - - - Gets the buffer segments which can help you parse your request info conviniently. - - - - - Gets the size of the rest buffer. - - - The size of the rest buffer. - - - - - Gets or sets the next Receive filter. - - - The next Receive filter. - - - - - Gets the filter state. - - - The state. - - - - - Terminator Receive filter - - The type of the request info. - - - - Null RequestInfo - - - - - Initializes a new instance of the class. - - The terminator. - - - - Filters received data of the specific session into request info. - - The read buffer. - The offset of the current received data in this read buffer. - The length of the current received data. - if set to true [to be copied]. - The rest, the length of the data which hasn't been parsed. - return the parsed TRequestInfo - - - - Resets this instance. - - - - - Resolves the specified data to TRequestInfo. - - The data. - The offset. - The length. - - - - - Gets the session assosiated with the Receive filter. - - - - - TerminatorRequestFilter - - - - - Initializes a new instance of the class. - - The terminator. - The encoding. - - - - Initializes a new instance of the class. - - The terminator. - The encoding. - The request parser. - - - - Resolves the specified data to StringRequestInfo. - - The data. - The offset. - The length. - - - - - Export Factory - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The instance. - - - - Initializes a new instance of the class. - - Name of the type. - - - - Ensures the instance's existance. - - - - - Creates the export type instance. - - - - - - - Gets or sets the type. - - - The type. - - - - - Provider factory infomation - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The key. - The name. - The instance. - - - - Initializes a new instance of the class. - - The key. - The name. - Name of the type. - - - - Initializes a new instance of the class. - - The key. - The name. - The type. - - - - Gets the key. - - - - - Gets or sets the name. - - - The name. - - - - - Gets or sets the export factory. - - - The export factory. - - - - - ProviderKey - - - - - Gets or sets the name. - - - The name. - - - - - Gets or sets the type. - - - The type. - - - - - Gets the service. - - - - - Gets the socket server factory. - - - - - Gets the connection filter. - - - - - Gets the log factory. - - - - - Gets the Receive filter factory. - - - - - Gets the command loader. - - - - - Request handler - - The type of the app session. - The type of the request info. - The session. - The request info. - - - - The listener configuration interface - - - - - Gets the ip of listener - - - - - Gets the port of listener - - - - - Gets the backlog. - - - - - Gets the security option, None/Default/Tls/Ssl/... - - - - - Listener configuration model - - - - - Initializes a new instance of the class. - - - - - Gets the ip of listener - - - - - Gets the port of listener - - - - - Gets the backlog. - - - - - Gets/sets the security option, None/Default/Tls/Ssl/... - - - - - Listener inforamtion - - - - - Gets or sets the listen endpoint. - - - The end point. - - - - - Gets or sets the listen backlog. - - - The back log. - - - - - Gets or sets the security protocol. - - - The security. - - - - - Binary type request information - - - - - RequestInfo basic class - - The type of the request body. - - - - Request information interface - - The type of the request body. - - - - Request information interface - - - - - Gets the key of this request. - - - - - Gets the body of this request. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The key. - The body. - - - - Initializes the specified key. - - The key. - The body. - - - - Gets the key of this request. - - - - - Gets the body. - - - - - Initializes a new instance of the class. - - The key. - The body. - - - - Command base class - - The type of the app session. - The type of the request info. - - - - Command basic interface - - The type of the app session. - The type of the request info. - - - - Command basic interface - - - - - Gets the name. - - - - - Executes the command. - - The session. - The request info. - - - - Executes the command. - - The session. - The request info. - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Gets the name. - - - - - Command update action enum - - - - - Add command - - - - - Remove command - - - - - Update command - - - - - Command update information - - - - - - Gets or sets the update action. - - - The update action. - - - - - Gets or sets the target command. - - - The command. - - - - - Mockup command - - The type of the app session. - The type of the request info. - - - - Initializes a new instance of the class. - - The name. - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - Request information interface - - The type of the request header. - The type of the request body. - - - - Gets the header of the request. - - - - - RequestInfo with header - - The type of the request header. - The type of the request body. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The key. - The header. - The body. - - - - Initializes the specified key. - - The key. - The header. - The body. - - - - Gets the header. - - - - - String type request information - - - - - Initializes a new instance of the class. - - The key. - The body. - The parameters. - - - - Gets the first param. - - - - - - Gets the parameters. - - - - - Gets the at the specified index. - - - - - A command loader which loads commands from assembly by reflection - - - - - Initializes the command loader - - The type of the command. - The root config. - The app server. - - - - - Tries to load commands. - - The commands. - - - - - UdpRequestInfo, it is designed for passing in business session ID to udp request info - - - - - Initializes a new instance of the class. - - The key. - The session ID. - - - - Gets the key of this request. - - - - - Gets the session ID. - - - - - Certificate config model class - - - - - Certificate configuration interface - - - - - Gets the file path. - - - - - Gets the password. - - - - - Gets the the store where certificate locates. - - - The name of the store. - - - - - Gets the thumbprint. - - - - - Gets/sets the file path. - - - - - Gets/sets the password. - - - - - Gets/sets the the store where certificate locates. - - - The name of the store. - - - - - Gets/sets the thumbprint. - - - - - Server instance configuation interface - - - - - Gets the child config. - - The type of the config. - Name of the child config. - - - - - Gets the name of the server type this appServer want to use. - - - The name of the server type. - - - - - Gets the type definition of the appserver. - - - The type of the server. - - - - - Gets the Receive filter factory. - - - - - Gets the ip. - - - - - Gets the port. - - - - - Gets the options. - - - - - Gets the option elements. - - - - - Gets a value indicating whether this is disabled. - - - true if disabled; otherwise, false. - - - - - Gets the name. - - - - - Gets the mode. - - - - - Gets the send time out. - - - - - Gets the max connection number. - - - - - Gets the size of the receive buffer. - - - The size of the receive buffer. - - - - - Gets the size of the send buffer. - - - The size of the send buffer. - - - - - Gets a value indicating whether sending is in synchronous mode. - - - true if [sync send]; otherwise, false. - - - - - Gets a value indicating whether log command in log file. - - true if log command; otherwise, false. - - - - Gets a value indicating whether clear idle session. - - true if clear idle session; otherwise, false. - - - - Gets the clear idle session interval, in seconds. - - The clear idle session interval. - - - - Gets the idle session timeout time length, in seconds. - - The idle session time out. - - - - Gets X509Certificate configuration. - - X509Certificate configuration. - - - - Gets the security protocol, X509 certificate. - - - - - Gets the length of the max request. - - - The length of the max request. - - - - - Gets a value indicating whether [disable session snapshot]. - - - true if [disable session snapshot]; otherwise, false. - - - - - Gets the interval to taking snapshot for all live sessions. - - - - - Gets the connection filters used by this server instance. - - - The connection filter's name list, seperated by comma - - - - - Gets the command loader, multiple values should be separated by comma. - - - - - Gets the start keep alive time, in seconds - - - - - Gets the keep alive interval, in seconds. - - - - - Gets the backlog size of socket listening. - - - - - Gets the startup order of the server instance. - - - - - Gets the listeners' configuration. - - - - - Gets the log factory name. - - - - - Gets the size of the sending queue. - - - The size of the sending queue. - - - - - Gets a value indicating whether [log basic session activity like connected and disconnected]. - - - true if [log basic session activity]; otherwise, false. - - - - - Gets a value indicating whether [log all socket exception]. - - - true if [log all socket exception]; otherwise, false. - - - - - Gets the command assemblies configuration. - - - The command assemblies. - - - - - Server configruation model - - - - - Default ReceiveBufferSize - - - - - Default MaxConnectionNumber - - - - - Default sending queue size - - - - - Default MaxRequestLength - - - - - Initializes a new instance of the class. - - The server config. - - - - Initializes a new instance of the class. - - - - - Gets the child config. - - The type of the config. - Name of the child config. - - - - - Gets/sets the name of the server type of this appServer want to use. - - - The name of the server type. - - - - - Gets/sets the type definition of the appserver. - - - The type of the server. - - - - - Gets/sets the Receive filter factory. - - - - - Gets/sets the ip. - - - - - Gets/sets the port. - - - - - Gets/sets the options. - - - - - Gets the option elements. - - - - - Gets/sets a value indicating whether this is disabled. - - - true if disabled; otherwise, false. - - - - - Gets the name. - - - - - Gets/sets the mode. - - - - - Gets/sets the send time out. - - - - - Gets the max connection number. - - - - - Gets the size of the receive buffer. - - - The size of the receive buffer. - - - - - Gets the size of the send buffer. - - - The size of the send buffer. - - - - - Gets a value indicating whether sending is in synchronous mode. - - - true if [sync send]; otherwise, false. - - - - - Gets/sets a value indicating whether log command in log file. - - - true if log command; otherwise, false. - - - - - Gets/sets a value indicating whether clear idle session. - - - true if clear idle session; otherwise, false. - - - - - Gets/sets the clear idle session interval, in seconds. - - - The clear idle session interval. - - - - - Gets/sets the idle session timeout time length, in seconds. - - - The idle session time out. - - - - - Gets/sets X509Certificate configuration. - - - X509Certificate configuration. - - - - - Gets/sets the security protocol, X509 certificate. - - - - - Gets/sets the length of the max request. - - - The length of the max request. - - - - - Gets/sets a value indicating whether [disable session snapshot]. - - - true if [disable session snapshot]; otherwise, false. - - - - - Gets/sets the interval to taking snapshot for all live sessions. - - - - - Gets/sets the connection filters used by this server instance. - - - The connection filter's name list, seperated by comma - - - - - Gets the command loader, multiple values should be separated by comma. - - - - - Gets/sets the start keep alive time, in seconds - - - - - Gets/sets the keep alive interval, in seconds. - - - - - Gets the backlog size of socket listening. - - - - - Gets/sets the startup order of the server instance. - - - - - Gets and sets the listeners' configuration. - - - - - Gets/sets the log factory name. - - - - - Gets/sets the size of the sending queue. - - - The size of the sending queue. - - - - - Gets a value indicating whether [log basic session activity like connected and disconnected]. - - - true if [log basic session activity]; otherwise, false. - - - - - Gets/sets a value indicating whether [log all socket exception]. - - - true if [log all socket exception]; otherwise, false. - - - - - Gets the command assemblies configuration. - - - The command assemblies. - - - - - The interface for AppServer - - - - - Creates the app session. - - The socket session. - - - - - Gets the app session by ID. - - The session ID. - - - - - Resets the session's security protocol. - - The session. - The security protocol. - - - - Gets the started time. - - - The started time. - - - - - Gets or sets the listeners. - - - The listeners. - - - - - Gets the Receive filter factory. - - - - - Gets the server's config. - - - - - Gets the certificate of current server. - - - - - Gets the transfer layer security protocol. - - - - - Gets the log factory. - - - - - The raw data processor - - The type of the app session. - - - - Gets or sets the raw binary data received event handler. - TAppSession: session - byte[]: receive buffer - int: receive buffer offset - int: receive lenght - bool: whether process the received data further - - - - - The interface for AppServer - - The type of the app session. - - - - Gets the matched sessions from sessions snapshot. - - The prediction critera. - - - - - Gets all sessions in sessions snapshot. - - - - - - Gets/sets the new session connected event handler. - - - - - Gets/sets the session closed event handler. - - - - - The interface for AppServer - - The type of the app session. - The type of the request info. - - - - Occurs when [request comming]. - - - - - The interface for handler of session request - - The type of the request info. - - - - Executes the command. - - The session. - The request info. - - - - SocketServer Accessor interface - - - - - Gets the socket server. - - - The socket server. - - - - - The basic interface for appSession - - - - - The basic session interface - - - - - Gets the session ID. - - - - - Gets the remote endpoint. - - - - - Closes this session. - - - - - Closes the session by the specified reason. - - The close reason. - - - - Processes the request. - - The read buffer. - The offset. - The length. - if set to true [to be copied]. - return offset delta of next receiving buffer - - - - Starts the session. - - - - - Gets the app server. - - - - - Gets the socket session of the AppSession. - - - - - Gets the items. - - - - - Gets the config of the server. - - - - - Gets the local listening endpoint. - - - - - Gets or sets the last active time of the session. - - - The last active time. - - - - - Gets the start time of the session. - - - - - Gets a value indicating whether this is connected. - - - true if connected; otherwise, false. - - - - - Gets or sets the charset which is used for transfering text message. - - The charset. - - - - Gets or sets the previous command. - - - The prev command. - - - - - Gets or sets the current executing command. - - - The current command. - - - - - Gets the logger assosiated with this session. - - - - - The interface for appSession - - The type of the app session. - The type of the request info. - - - - Initializes the specified session. - - The server. - The socket session. - - - - The basic interface of connection filter - - - - - Initializes the connection filter - - The name. - The app server. - - - - - Whether allows the connect according the remote endpoint - - The remote address. - - - - - Gets the name of the filter. - - - - - It is the basic interface of SocketServer, - SocketServer is the abstract server who really listen the comming sockets directly. - - - - - Starts this instance. - - - - - - Resets the session's security protocol. - - The session. - The security protocol. - - - - Stops this instance. - - - - - Gets a value indicating whether this instance is running. - - - true if this instance is running; otherwise, false. - - - - - Gets the information of the sending queue pool. - - - The sending queue pool. - - - - - The interface for socket server factory - - - - - Creates the socket server instance. - - The type of the request info. - The app server. - The listeners. - The config. - - - - - CloseReason enum - - - - - The socket is closed for unknown reason - - - - - Close for server shutdown - - - - - The client close the socket - - - - - The server side close the socket - - - - - Application error - - - - - The socket is closed for a socket error - - - - - The socket is closed by server for timeout - - - - - Protocol error - - - - - SuperSocket internal error - - - - - The interface for socket session - - - - - Initializes the specified app session. - - The app session. - - - - Starts this instance. - - - - - Closes the socket session for the specified reason. - - The reason. - - - - Tries to send array segment. - - The segments. - - - - Tries to send array segment. - - The segment. - - - - Applies the secure protocol. - - - - - Gets the client socket. - - - - - Gets the local listening endpoint. - - - - - Gets or sets the secure protocol. - - - The secure protocol. - - - - - Occurs when [closed]. - - - - - Gets the app session assosiated with this socket session. - - - - - Gets the original receive buffer offset. - - - The original receive buffer offset. - - - - - Logger extension class - - - - - Logs the error - - The logger. - The session. - The title. - The e. - - - - Logs the error - - The logger. - The session. - The message. - - - - Logs the information - - The logger. - The session. - The message. - - - - Logs the debug message - - The logger. - The session. - The message. - - - - Logs the performance message - - The app server. - The message. - - - - Basic request info parser, which parse request info by separating - - - - - The interface for request info parser - - - - - Parses the request info from the source string. - - The source. - - - - - The default singlegton instance - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The spliter between command name and command parameters. - The parameter spliter. - - - - Parses the request info. - - The source. - - - - - Server's state enum class - - - - - Not initialized - - - - - In initializing - - - - - Has been initialized, but not started - - - - - In starting - - - - - In running - - - - - In stopping - - - - - Server State - - - - - Gets or sets the name. - - - The name. - - - - - Gets or sets the collected time. - - - The collected time. - - - - - Gets or sets the started time. - - - The started time. - - - - - Gets or sets a value indicating whether this instance is running. - - - true if this instance is running; otherwise, false. - - - - - Gets or sets the total count of the connections. - - - The total count of the connections. - - - - - Gets or sets the maximum allowed connection number. - - - The max connection number. - - - - - Gets or sets the total handled requests count. - - - The total handled requests count. - - - - - Gets or sets the request handling speed, per second. - - - The request handling speed. - - - - - Gets or sets the listeners. - - - The listeners. - - - - - Gets or sets the avialable sending queue items. - - - The avialable sending queue items. - - - - - Gets or sets the total sending queue items. - - - The total sending queue items. - - - - - Used for session level event handler - - the type of the target session - the target session - - - - Used for session level event handler - - the type of the target session - the target session - the target session - the event parameter - - - - Socket server running mode - - - - - Tcp mode - - - - - Udp mode - - - - - AppServer class - - - - - AppServer class - - The type of the app session. - - - - AppServer basic class - - The type of the app session. - The type of the request info. - - - - AppServer base class - - The type of the app session. - The type of the request info. - - - - Null appSession instance - - - - - the current state's code - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The Receive filter factory. - - - - Gets the filter attributes. - - The type. - - - - - Setups the command into command dictionary - - The discovered commands. - - - - - Setups the specified root config. - - The root config. - The config. - - - - - Setups with the specified port. - - The port. - return setup result - - - - Setups with the specified config. - - The server config. - The socket server factory. - The receive filter factory. - The log factory. - The connection filters. - The command loaders. - - - - - Setups the specified root config, this method used for programming setup - - The root config. - The server config. - The socket server factory. - The Receive filter factory. - The log factory. - The connection filters. - The command loaders. - - - - - Setups with the specified ip and port. - - The ip. - The port. - The socket server factory. - The Receive filter factory. - The log factory. - The connection filters. - The command loaders. - return setup result - - - - Setups the specified root config. - - The bootstrap. - The socket server instance config. - The factories. - - - - - Creates the logger for the AppServer. - - Name of the logger. - - - - - Setups the security option of socket communications. - - The config of the server instance. - - - - - Gets the certificate from server configuguration. - - The certificate config. - - - - - Setups the socket server.instance - - - - - - Setups the listeners base on server configuration - - The config. - - - - - Starts this server instance. - - - return true if start successfull, else false - - - - - Called when [startup]. - - - - - Called when [stopped]. - - - - - Stops this server instance. - - - - - Gets command by command name. - - Name of the command. - - - - - Called when [raw data received]. - - The session. - The buffer. - The offset. - The length. - - - - Executes the command. - - The session. - The request info. - - - - Executes the command for the session. - - The session. - The request info. - - - - Executes the command. - - The session. - The request info. - - - - Executes the connection filters. - - The remote address. - - - - - Creates the app session. - - The socket session. - - - - - Registers the session into session container. - - The session ID. - The app session. - - - - - Called when [new session connected]. - - The session. - - - - Resets the session's security protocol. - - The session. - The security protocol. - - - - Called when [socket session closed]. - - The socket session. - The reason. - - - - Called when [session closed]. - - The appSession. - The reason. - - - - Gets the app session by ID. - - The session ID. - - - - - Gets the app session by ID. - - - - - - - Gets the matched sessions from sessions snapshot. - - The prediction critera. - - - - Gets all sessions in sessions snapshot. - - - - - Updates the summary of the server. - - The server summary. - - - - Called when [summary data collected], you can override this method to get collected performance data - - The node summary. - The server summary. - - - - Releases unmanaged and - optionally - managed resources - - - - - Gets the server's config. - - - - - Gets the current state of the work item. - - - The state. - - - - - Gets the certificate of current server. - - - - - Gets or sets the receive filter factory. - - - The receive filter factory. - - - - - Gets the Receive filter factory. - - - - - Gets the basic transfer layer security protocol. - - - - - Gets the root config. - - - - - Gets the logger assosiated with this object. - - - - - Gets the bootstrap of this appServer instance. - - - - - Gets the total handled requests number. - - - - - Gets or sets the listeners inforamtion. - - - The listeners. - - - - - Gets the started time of this server instance. - - - The started time. - - - - - Gets or sets the log factory. - - - The log factory. - - - - - Gets the name of the server instance. - - - - - Gets the socket server. - - - The socket server. - - - - - Gets or sets the raw binary data received event handler. - TAppSession: session - byte[]: receive buffer - int: receive buffer offset - int: receive lenght - bool: whether process the received data further - - - - - Occurs when a full request item received. - - - - - Gets or sets the server's connection filter - - - The server's connection filters - - - - - The action which will be executed after a new session connect - - - - - Gets/sets the session closed event handler. - - - - - Gets the total session count. - - - - - Gets the type of the server state. The type must inherit from ServerState - - - The type of the server state. - - - - - Gets the state of the server. - - - The state data of the server. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The protocol. - - - - Starts this AppServer instance. - - - - - - Registers the session into the session container. - - The session ID. - The app session. - - - - - Gets the app session by ID. - - The session ID. - - - - - Called when [socket session closed]. - - The session. - The reason. - - - - Clears the idle session. - - The state. - - - - Gets the matched sessions from sessions snapshot. - - The prediction critera. - - - - - Gets all sessions in sessions snapshot. - - - - - - Stops this instance. - - - - - Gets the total session count. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The Receive filter factory. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The Receive filter factory. - - - - AppSession base class - - The type of the app session. - The type of the request info. - - - - Initializes a new instance of the class. - - - - - Initializes the specified app session by AppServer and SocketSession. - - The app server. - The socket session. - - - - Starts the session. - - - - - Called when [init]. - - - - - Called when [session started]. - - - - - Called when [session closed]. - - The reason. - - - - Handles the exceptional error, it only handles application error. - - The exception. - - - - Handles the unknown request. - - The request info. - - - - Closes the session by the specified reason. - - The close reason. - - - - Closes this session. - - - - - Try to send the message to client. - - The message which will be sent. - Indicate whether the message was pushed into the sending queue - - - - Sends the message to client. - - The message which will be sent. - - - - Sends the response. - - The message which will be sent. - Indicate whether the message was pushed into the sending queue - - - - Try to send the data to client. - - The data which will be sent. - The offset. - The length. - Indicate whether the message was pushed into the sending queue - - - - Sends the data to client. - - The data which will be sent. - The offset. - The length. - - - - Sends the response. - - The data which will be sent. - The offset. - The length. - - - - Try to send the data segment to client. - - The segment which will be sent. - Indicate whether the message was pushed into the sending queue - - - - Sends the data segment to client. - - The segment which will be sent. - - - - Sends the response. - - The segment which will be sent. - Indicate whether the message was pushed into the sending queue; if it returns false, the sending queue may be full or the socket is not connected - - - - Try to send the data segments to clinet. - - The segments. - Indicate whether the message was pushed into the sending queue; if it returns false, the sending queue may be full or the socket is not connected - - - - Sends the data segments to clinet. - - The segments. - - - - Sends the response. - - The segments. - Indicate whether the message was pushed into the sending queue - - - - Sends the response. - - The message which will be sent. - The parameter values. - - - - Sends the response. - - The message which will be sent. - The parameter values. - Indicate whether the message was pushed into the sending queue - - - - Sets the next Receive filter which will be used when next data block received - - The next receive filter. - - - - Filters the request. - - The read buffer. - The offset. - The length. - if set to true [to be copied]. - The rest, the size of the data which has not been processed - return offset delta of next receiving buffer. - - - - - Processes the request data. - - The read buffer. - The offset. - The length. - if set to true [to be copied]. - - return offset delta of next receiving buffer - - - - - Gets the app server instance assosiated with the session. - - - - - Gets the app server instance assosiated with the session. - - - - - Gets or sets the charset which is used for transfering text message. - - - The charset. - - - - - Gets the items dictionary, only support 10 items maximum - - - - - Gets a value indicating whether this is connected. - - - true if connected; otherwise, false. - - - - - Gets or sets the previous command. - - - The prev command. - - - - - Gets or sets the current executing command. - - - The current command. - - - - - Gets or sets the secure protocol of transportation layer. - - - The secure protocol. - - - - - Gets the local listening endpoint. - - - - - Gets the remote endpoint of client. - - - - - Gets the logger. - - - - - Gets or sets the last active time of the session. - - - The last active time. - - - - - Gets the start time of the session. - - - - - Gets the session ID. - - - - - Gets the socket session of the AppSession. - - - - - Gets the config of the server. - - - - - AppServer basic class for whose request infoe type is StringRequestInfo - - The type of the app session. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - if set to true [append new line for response]. - - - - Handles the unknown request. - - The request info. - - - - Processes the sending message. - - The raw message. - - - - - Sends the specified message. - - The message. - - - - - Sends the response. - - The message. - Indicate whether the message was pushed into the sending queue - - - - Sends the response. - - The message. - The param values. - Indicate whether the message was pushed into the sending queue - - - - Sends the response. - - The message. - The param values. - Indicate whether the message was pushed into the sending queue - - - - AppServer basic class for whose request infoe type is StringRequestInfo - - - - - A command type for whose request info type is StringRequestInfo - - The type of the app session. - - - - A command type for whose request info type is StringRequestInfo - - - - diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.XML b/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.XML deleted file mode 100644 index 95df68de..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.XML +++ /dev/null @@ -1,1045 +0,0 @@ - - - - SuperSocket.SocketEngine - - - - - AppDomainAppServer - - - - - Initializes a new instance of the class. - - Name of the service type. - - - - Initializes a new instance of the class. - - Type of the service. - - - - Setups the specified root config. - - The bootstrap. - The socket server instance config. - The factories. - - - - - Starts this server instance. - - - return true if start successfull, else false - - - - - Stops this server instance. - - - - - Gets the name of the server instance. - - - - - Gets the current state of the work item. - - - The state. - - - - - Gets the total session count. - - - - - Gets the state data of the server. - - - The state of the server. - - - - - AppDomainBootstrap - - - - - Initializes a new instance of the class. - - - - - Initializes the bootstrap with the configuration - - - - - - Initializes the bootstrap with the configuration and config resolver. - - The server config resolver. - - - - - Initializes the bootstrap with the configuration and config resolver. - - The log factory. - - - - - Initializes the bootstrap with a listen endpoint replacement dictionary - - The listen end point replacement. - - - - - Initializes the bootstrap with the configuration - - The server config resolver. - The log factory. - - - - - Starts this bootstrap. - - - - - - Stops this bootstrap. - - - - - Gets all the app servers running in this bootstrap - - - - - Gets the config. - - - - - Gets the startup config file. - - - - - Validates the type of the provider, needn't validate in default mode, because it will be validate later when initializing. - - Name of the type. - - - - - Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - - - - - SuperSocket default bootstrap - - - - - Indicates whether the bootstrap is initialized - - - - - Global configuration - - - - - Global log - - - - - Initializes a new instance of the class. - - The app servers. - - - - Initializes a new instance of the class. - - The root config. - The app servers. - - - - Initializes a new instance of the class. - - The root config. - The app servers. - The log factory. - - - - Initializes a new instance of the class. - - The config. - - - - Initializes a new instance of the class. - - The config. - The startup config file. - - - - Creates the work item instance. - - Name of the service type. - - - - - Gets the work item factory info loader. - - The config. - The log factory. - - - - - Initializes the bootstrap with a listen endpoint replacement dictionary - - The listen end point replacement. - - - - - Initializes the bootstrap with the configuration, config resolver and log factory. - - The server config resolver. - The log factory. - - - - - Initializes the bootstrap with the configuration and config resolver. - - The server config resolver. - - - - - Initializes the bootstrap with the configuration - - The log factory. - - - - - Initializes the bootstrap with the configuration - - - - - - Starts this bootstrap. - - - - - - Stops this bootstrap. - - - - - Gets the log factory. - - - - - Gets all the app servers running in this bootstrap - - - - - Gets the config. - - - - - Gets the startup config file. - - - - - AssemblyImport, used for importing assembly to the current AppDomain - - - - - Initializes a new instance of the class. - - - - - Bootstrap Factory - - - - - Creates the bootstrap. - - The config. - - - - - Creates the bootstrap from app configuration's socketServer section. - - - - - - Creates the bootstrap. - - Name of the config section. - - - - - Creates the bootstrap from configuration file. - - The configuration file. - - - - - Command assembly configuration element - - - - - Gets the assembly name. - - - The assembly. - - - - - Command assembly configuation collection - - - - - Socket Session, all application session should base on this class - - - - - Logs the error, skip the ignored exception - - The exception. - The caller. - The caller file path. - The caller line number. - - - - Logs the error, skip the ignored exception - - The message. - The exception. - The caller. - The caller file path. - The caller line number. - - - - Logs the socket error, skip the ignored error - - The socket error code. - The caller. - The caller file path. - The caller line number. - - - - Starts this session. - - - - - Says the welcome information when a client connectted. - - - - - Called when [close]. - - - - - Tries to send array segment. - - The segments. - - - - - Tries to send array segment. - - The segment. - - - - - Sends in async mode. - - The queue. - - - - Sends in sync mode. - - The queue. - - - - Gets or sets the session ID. - - The session ID. - - - - Gets or sets the config. - - - The config. - - - - - Occurs when [closed]. - - - - - Gets or sets the client. - - The client. - - - - Gets the local end point. - - The local end point. - - - - Gets the remote end point. - - The remote end point. - - - - Gets or sets the secure protocol. - - The secure protocol. - - - - Initializes a new instance of the class. - - Name of the service type. - - - - Setups the specified root config. - - The bootstrap. - The socket server instance config. - The providers. - - - - - Starts this server instance. - - - return true if start successfull, else false - - - - - Stops this server instance. - - - - - Gets the name of the server instance. - - - - - Gets the current state of the work item. - - - The state. - - - - - Gets the total session count. - - - - - The interface for socket listener - - - - - Starts to listen - - The server config. - - - - - Stops listening - - - - - Gets the info of listener - - - - - Gets the end point the listener is working on - - - - - Occurs when new client accepted. - - - - - Occurs when error got. - - - - - Occurs when [stopped]. - - - - - Starts to listen - - The server config. - - - - - Occurs when [stopped]. - - - - - Tcp socket listener in async mode - - - - - Starts to listen - - The server config. - - - - - Gets the sending queue manager. - - - The sending queue manager. - - - - - Starts this session communication. - - - - - Certificate configuration - - - - - Gets the certificate file path. - - - - - Gets the password. - - - - - Gets the the store where certificate locates. - - - The name of the store. - - - - - Gets the thumbprint. - - - - - Listener configuration - - - - - Gets the ip of listener - - - - - Gets the port of listener - - - - - Gets the backlog. - - - - - Gets the security option, None/Default/Tls/Ssl/... - - - - - Listener configuration collection - - - - - Server configuration - - - - - Gets the child config. - - The type of the config. - Name of the child config. - - - - - Gets a value indicating whether an unknown attribute is encountered during deserialization. - To keep compatible with old configuration - - The name of the unrecognized attribute. - The value of the unrecognized attribute. - - true when an unknown attribute is encountered while deserializing; otherwise, false. - - - - - Gets the name of the server type this appServer want to use. - - - The name of the server type. - - - - - Gets the type definition of the appserver. - - - The type of the server. - - - - - Gets the Receive filter factory. - - - - - Gets the ip. - - - - - Gets the port. - - - - - Gets the mode. - - - - - Gets a value indicating whether this is disabled. - - - true if disabled; otherwise, false. - - - - - Gets the send time out. - - - - - Gets the max connection number. - - - - - Gets the size of the receive buffer. - - - The size of the receive buffer. - - - - - Gets the size of the send buffer. - - - The size of the send buffer. - - - - - Gets a value indicating whether sending is in synchronous mode. - - - true if [sync send]; otherwise, false. - - - - - Gets a value indicating whether log command in log file. - - true if log command; otherwise, false. - - - - Gets a value indicating whether [log basic session activity like connected and disconnected]. - - - true if [log basic session activity]; otherwise, false. - - - - - Gets a value indicating whether [log all socket exception]. - - - true if [log all socket exception]; otherwise, false. - - - - - Gets a value indicating whether clear idle session. - - true if clear idle session; otherwise, false. - - - - Gets the clear idle session interval, in seconds. - - The clear idle session interval. - - - - Gets the idle session timeout time length, in seconds. - - The idle session time out. - - - - Gets the certificate config. - - The certificate config. - - - - Gets X509Certificate configuration. - - - X509Certificate configuration. - - - - - Gets the security protocol, X509 certificate. - - - - - Gets the max allowed length of request. - - - The max allowed length of request. - - - - - Gets a value indicating whether [disable session snapshot] - - - - - Gets the interval to taking snapshot for all live sessions. - - - - - Gets the connection filters used by this server instance. - - - The connection filters's name list, seperated by comma - - - - - Gets the command loader, multiple values should be separated by comma. - - - - - Gets the start keep alive time, in seconds - - - - - Gets the keep alive interval, in seconds. - - - - - Gets the backlog size of socket listening. - - - - - Gets the startup order of the server instance. - - - - - Gets/sets the size of the sending queue. - - - The size of the sending queue. - - - - - Gets the logfactory name of the server instance. - - - - - Gets the listeners' configuration. - - - - - Gets the listeners' configuration. - - - - - Gets the command assemblies configuration. - - - The command assemblies. - - - - - Server configuration collection - - - - - SuperSocket's root configuration node - - - - - Gets a value indicating whether an unknown element is encountered during deserialization. - To keep compatible with old configuration - - The name of the unknown subelement. - The being used for deserialization. - - true when an unknown element is encountered while deserializing; otherwise, false. - - The element identified by is locked.- or -One or more of the element's attributes is locked.- or - is unrecognized, or the element has an unrecognized attribute.- or -The element has a Boolean attribute with an invalid value.- or -An attempt was made to deserialize a property more than once.- or -An attempt was made to deserialize a property that is not a valid member of the element.- or -The element cannot contain a CDATA or text element. - - - - Gets the child config. - - The type of the config. - Name of the child config. - - - - - Gets all the server configurations - - - - - Gets the service configurations - - - - - Gets all the connection filter configurations. - - - - - Gets the defined log factory types. - - - - - Gets the logfactory name of the bootstrap. - - - - - Gets the command loaders definition. - - - - - Gets the max working threads. - - - - - Gets the min working threads. - - - - - Gets the max completion port threads. - - - - - Gets the min completion port threads. - - - - - Gets the performance data collect interval, in seconds. - - - - - Gets a value indicating whether [disable performance data collector]. - - - true if [disable performance data collector]; otherwise, false. - - - - - Gets the isolation mode. - - - - - Gets the logfactory name of the bootstrap. - - - - - Gets the option elements. - - - - - Default socket server factory - - - - - Creates the socket server. - - The type of the request info. - The app server. - The listeners. - The config. - - - - - Starts to listen - - The server config. - - - - - Initializes a new instance of the class. - - The app server. - The listeners. - - - - Called when [new client accepted]. - - The listener. - The client. - The state. - - - - Updates the remote end point of the client. - - The remote end point. - - - diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.dll b/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.dll deleted file mode 100644 index ded732da..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.dll and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.pdb b/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.pdb deleted file mode 100644 index 619ef6be..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.SocketEngine.pdb and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe b/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe deleted file mode 100644 index b3a7c7d7..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe.config b/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe.config deleted file mode 100644 index 4ce9e122..00000000 --- a/cb-tools/SuperWebSocket/SuperSocket.SocketService.exe.config +++ /dev/null @@ -1,24 +0,0 @@ - - - -
- - - - - - - - - - - - - - - - - - - - diff --git a/cb-tools/SuperWebSocket/SuperSocket.SocketService.pdb b/cb-tools/SuperWebSocket/SuperSocket.SocketService.pdb deleted file mode 100644 index 30066049..00000000 Binary files a/cb-tools/SuperWebSocket/SuperSocket.SocketService.pdb and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperWebSocket.XML b/cb-tools/SuperWebSocket/SuperWebSocket.XML deleted file mode 100644 index cdd8817c..00000000 --- a/cb-tools/SuperWebSocket/SuperWebSocket.XML +++ /dev/null @@ -1,1762 +0,0 @@ - - - - SuperWebSocket - - - - - The command handling binary data - - The type of the web socket session. - - - - FragmentCommand - - The type of the web socket session. - - - - Initializes a new instance of the class. - - - - - Checks the frame. - - The frame. - - - - - Checks the control frame. - - The frame. - - - - - Gets data from websocket frames. - - The frames. - - - - - Gets text string from websocket frames. - - The frames. - - - - - Gets data from a websocket frame. - - The frame. - - - - - Gets text string from a websocket frame. - - The frame. - - - - - Gets the UTF8 encoding which has been set ExceptionFallback. - - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - The command handling close fragment - - The type of the web socket session. - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - The command handling continuation fragment - - The type of the web socket session. - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - The command handle handshake request - - The type of the web socket session. - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - The command handling Ping - - The type of the web socket session. - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - The command to handling text message in plain text of hybi00 - - The type of the web socket session. - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - The command handling Pong - - The type of the web socket session. - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - The command handling Text fragment - - The type of the web socket session. - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - Command configuration - - - - - Gets a value indicating whether an unknown attribute is encountered during deserialization. - - The name of the unrecognized attribute. - The value of the unrecognized attribute. - - true when an unknown attribute is encountered while deserializing; otherwise, false. - - - - - Gets the options. - - - - - Command configuration collection - - - - - When overridden in a derived class, creates a new . - - - A new . - - - - - Gets the element key for a specified configuration element when overridden in a derived class. - - The to return the key for. - - An that acts as the key for the specified . - - - - - Gets the enumerator. - - - - - - Gets or sets a property, attribute, or child element of this configuration element. - - The specified property, attribute, or child element - - - - SubProtocol configuration - - - - - Initializes a new instance of the class. - - - - - Gets the type. - - - - - Gets the commands. - - - - - SubProtocol configuation collection - - - - - When overridden in a derived class, creates a new . - - - A new . - - - - - Gets the element key for a specified configuration element when overridden in a derived class. - - The to return the key for. - - An that acts as the key for the specified . - - - - - Gets the enumerator. - - - - - - Gets the type of the . - - The of this collection. - - - - Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. - - The name of the collection; otherwise, an empty string. The default is an empty string. - - - - Extension class - - - - - Appends in the format with CrCf as suffix. - - The builder. - The format. - The arg. - - - - Appends in the format with CrCf as suffix. - - The builder. - The format. - The args. - - - - Appends with CrCf as suffix. - - The builder. - The content. - - - - Appends with CrCf as suffix. - - The builder. - - - - The converter interface for converting binary data to text message - - - - - Returns a that represents this instance. - - The data. - The offset. - The length. - - A that represents this instance. - - - - - Json websocket session - - - - - Json websocket session - - The type of the web socket session. - - - - WebSocket AppSession class - - The type of the web socket session. - - - - WebSocketSession basic interface - - - - - Sends the raw binary response. - - The data. - The offset. - The length. - - - - Gets the available sub protocol. - - The protocol. - - - - - Gets or sets the method. - - - The method. - - - - - Gets the host. - - - - - Gets or sets the path. - - - The path. - - - - - Gets or sets the HTTP version. - - - The HTTP version. - - - - - Gets the sec web socket version. - - - - - Gets the origin. - - - - - Gets the URI scheme. - - - - - Gets a value indicating whether this is handshaked. - - - true if handshaked; otherwise, false. - - - - - Gets the app server. - - - - - Gets or sets the protocol processor. - - - The protocol processor. - - - - - Called when [init]. - - - - - Sets the cookie. - - - - - Sends the response. - - The message. - - - - Sends the response. - - The data. - The offset. - The length. - - - - Sends the response. - - The segment. - - - - Sends the raw binary data. - - The data. - The offset. - The length. - - - - Sends the response. - - The message. - - - - - Sends the response. - - The data. - The offset. - The length. - - - - - Sends the response. - - The segment. - - - - - Closes the with handshake. - - The reason text. - - - - Closes the with handshake. - - The status code. - The reason text. - - - - Sends the close handshake response. - - The status code. - - - - Closes the specified reason. - - The reason. - - - - Handles the unknown command. - - The request info. - - - - Handles the unknown request. - - The request info. - - - - Gets or sets the method. - - - The method. - - - - - Gets or sets the path. - - - The path. - - - - - Gets or sets the HTTP version. - - - The HTTP version. - - - - - Gets the host. - - - - - Gets the origin. - - - - - Gets the upgrade. - - - - - Gets the connection. - - - - - Gets the sec web socket version. - - - - - Gets the sec web socket protocol. - - - - - Gets the current token. - - - - - Gets the app server. - - - - - Gets the URI scheme, ws or wss - - - - - Gets the sub protocol. - - - - - Gets a value indicating whether this is handshaked. - - - true if handshaked; otherwise, false. - - - - - Gets a value indicating whether the session [in closing]. - - - true if [in closing]; otherwise, false. - - - - - Gets the cookies. - - - - - Gets or sets the protocol processor. - - - The protocol processor. - - - - - Sends the json message. - - The name. - The content. - - - - Close status code for Hybi10 - - - - - Close status code interface - - - - - Gets the code for extension not match. - - - - - Gets the code for going away. - - - - - Gets the code for invalid UT f8. - - - - - Gets the code for normal closure. - - - - - Gets the code for not acceptable data. - - - - - Gets the code for protocol error. - - - - - Gets the code for TLS handshake failure. - - - - - Gets the code for too large frame. - - - - - Gets the code for unexpected condition. - - - - - Gets the code for violate policy. - - - - - Gets the code for no status code. - - - - - Initializes a new instance of the class. - - - - - Gets the code for normal closure. - - - - - Gets the code for going away. - - - - - Gets the code for protocol error. - - - - - Gets the code for not acceptable data. - - - - - Gets the code for too large frame. - - - - - Gets the code for invalid UT f8. - - - - - Gets the code for violate policy. - - - - - Gets the code for extension not match. - - - - - Gets the code for unexpected condition. - - - - - Gets the code for TLS handshake failure. - - - - - Gets the code for no status code. - - - - - Close status code for rfc6455 - - - - - Initializes a new instance of the class. - - - - - Gets the code for normal closure. - - - - - Gets the code for going away. - - - - - Gets the code for protocol error. - - - - - Gets the code for not acceptable data. - - - - - Gets the code for too large frame. - - - - - Gets the code for invalid UT f8. - - - - - Gets the code for violate policy. - - - - - Gets the code for extension not match. - - - - - Gets the code for unexpected condition. - - - - - Gets the code for TLS handshake failure. - - - - - Gets the code for no status code. - - - - - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 - - - - - Protocol processor interface - - - - - Handshakes the specified session. - - The session. - The previous filter. - The data frame reader. - - - - - Sends the message. - - The session. - The message. - - - - Sends the data. - - The session. - The data. - The offset. - The length. - - - - Sends the close handshake. - - The session. - The status code. - The close reason. - - - - Sends the pong. - - The session. - The pong. - - - - Sends the ping. - - The session. - The ping. - - - - Determines whether [is valid close code] [the specified code]. - - The code. - - true if [is valid close code] [the specified code]; otherwise, false. - - - - - Gets a value indicating whether this instance can send binary data. - - - true if this instance can send binary data; otherwise, false. - - - - - Gets the close status clode. - - - - - Gets or sets the next processor. - - - The next processor. - - - - - Gets the version of current protocol. - - - - - http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 - - - - - Handshake request - - - - - WebSocketFragment request info - - - - - Gets the key of this request. - - - - - http://tools.ietf.org/html/rfc6455#section-4.4 - - - - - Plain text fragment - - - - - Initializes a new instance of the class. - - The message. - - - - Gets the message. - - - - - Gets the key of this request. - - - - - http://tools.ietf.org/html/rfc6455 - - - - - WebSocketReceiveFilter basis - - - - - The length of Sec3Key - - - - - Initializes a new instance of the class. - - The session. - - - - Initializes a new instance of the class. - - The previous receive filter. - - - - Handshakes the specified protocol processor. - - The protocol processor. - The session. - - - - - Gets the handshake request info. - - - - - Resets this instance. - - - - - Async json sub command - - The type of the json command info. - - - - Async json sub command - - The type of the web socket session. - The type of the json command info. - - - - Json SubCommand base - - The type of the web socket session. - The type of the json command info. - - - - SubCommand base - - The type of the web socket session. - - - - SubCommand interface - - The type of the web socket session. - - - - Executes the command. - - The session. - The request info. - - - - The basic interface of sub command filter loader - - - - - Loads the sub command filters. - - The global filters. - - - - Executes the command. - - The session. - The request info. - - - - Gets the name. - - - - - Initializes a new instance of the class. - - - - - Executes the command. - - The session. - The request info. - - - - Executes the json command. - - The session. - The command info. - - - - Gets the json message. - - The session. - The name. - The token. - The content. - - - - - Initializes a new instance of the class. - - - - - Executes the json command. - - The session. - The command info. - - - - Executes the async json command. - - The session. - The token. - The command info. - - - - Sends the json message. - - The session. - The token. - The content. - - - - Sends the json message. - - The session. - The name. - The token. - The content. - - - - Basic sub command parser - - - - - Parses the request info. - - The source. - - - - - Default basic sub protocol implementation - - - - - Default basic sub protocol implementation - - - - - SubProtocol basis - - The type of the web socket session. - - - - SubProtocol interface - - The type of the web socket session. - - - - Initializes with the specified config. - - The app server. - The protocol config. - The logger. - - - - - Tries the get command. - - The name. - The command. - - - - - Gets the name. - - - - - Gets the sub request parser. - - - - - Initializes a new instance of the class. - - The name. - - - - Initializes with the specified config. - - The app server. - The protocol config. - The logger. - - - - - Tries the get command. - - The name. - The command. - - - - - Gets the name. - - - - - Gets or sets the sub request parser. - - - The sub request parser. - - - - - Default basic sub protocol name - - - - - Initializes a new instance of the class with the calling aseembly as command assembly - - - - - Initializes a new instance of the class with the calling aseembly as command assembly - - The sub protocol name. - - - - Initializes a new instance of the class with command assemblies - - The command assemblies. - - - - Initializes a new instance of the class with single command assembly. - - The command assembly. - - - - Initializes a new instance of the class with name and single command assembly. - - The sub protocol name. - The command assembly. - - - - Initializes a new instance of the class with name and command assemblies. - - The sub protocol name. - The command assemblies. - - - - Initializes a new instance of the class. - - The name. - The command assemblies. - The request info parser. - - - - Initializes with the specified config. - - The app server. - The protocol config. - The logger. - - - - - Tries get command from the sub protocol's command inventory. - - The name. - The command. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The sub protocol name. - - - - Initializes a new instance of the class. - - The command assembly. - - - - Initializes a new instance of the class. - - The command assemblies. - - - - Initializes a new instance of the class. - - The sub protocol name. - The command assembly. - - - - Initializes a new instance of the class. - - The sub protocol name. - The command assemblies. - - - - Initializes a new instance of the class. - - The name. - The command assemblies. - The request info parser. - - - - The basic interface of SubRequestInfo - - - - - Gets the token. - - - The token. - - - - - JsonSubCommand - - The type of the json command info. - - - - JsonSubCommand - - The type of the web socket session. - The type of the json command info. - - - - Gets the json message. - - The session. - The content. - - - - - Gets the json message. - - The session. - The name. - The content. - - - - - Sends the json message. - - The session. - The content. - - - - Sends the json message. - - The session. - The name. - The content. - - - - SubCommand base - - - - - SubCommandFilter Attribute - - - - - Gets or sets the sub protocol. - - - The sub protocol. - - - - - SubProtocol RequestInfo type - - - - - Initializes a new instance of the class. - - The key. - The token. - The data. - - - - Gets the token of this request, used for callback - - - - - Text encoding binary data converter - - - - - Initializes a new instance of the class. - - The encoding. - - - - Returns a that represents this instance. - - The data. - The offset. - The length. - - A that represents this instance. - - - - - Gets the encoding. - - - The encoding. - - - - - WebSocket protocol - - - - - Initializes a new instance of the class. - - - - - Creates the filter. - - The app server. - The app session. - The remote end point. - - - - - Blah - - - - - Blah - - - - - Blah - - - - - Blah - - - - - Blah - - - - - Blah - - - - - Blah - - - - - WebSocket server interface - - - - - Validates the handshake request. - - The session. - The origin. - the validation result - - - - Gets the web socket protocol processor. - - - - - WebSocket AppServer - - - - - WebSocket AppServer - - The type of the web socket session. - - - - Initializes a new instance of the class. - - The sub protocols. - - - - Initializes a new instance of the class. - - The sub protocol. - - - - Initializes a new instance of the class. - - - - - The openning handshake timeout, in seconds - - - - - The closing handshake timeout, in seconds - - - - - The interval of checking handshake pending queue, in seconds - - - - - Gets the sub protocol by sub protocol name. - - The name. - - - - - Validates the handshake request. - - The session. - The origin in the handshake request. - - - - - Setups with the specified root config. - - The root config. - The config. - - - - - Called when [startup]. - - - - - Called when [new session connected]. - - The session. - - - - Blah - - - - - Setups the commands. - - The discovered commands. - - - - - Executes the command. - - The session. - The request info. - - - - Serialize the target object by JSON - - The target. - - - - - Deserialize the JSON string to target type object. - - The json. - The type. - - - - - Gets or sets the binary data converter. - - - The binary data converter. - - - - - Gets the request filter factory. - - - - - Occurs when [new request received]. - - - - - - Occurs when [new message received]. - - - - - Blah - - - - - Occurs when [new data received]. - - - - - Initializes a new instance of the class. - - The sub protocols. - - - - Initializes a new instance of the class. - - The sub protocol. - - - - Initializes a new instance of the class. - - - - - WebSocket AppSession - - - - - Gets the app server. - - - - diff --git a/cb-tools/SuperWebSocket/SuperWebSocket.dll b/cb-tools/SuperWebSocket/SuperWebSocket.dll deleted file mode 100644 index 57235983..00000000 Binary files a/cb-tools/SuperWebSocket/SuperWebSocket.dll and /dev/null differ diff --git a/cb-tools/SuperWebSocket/SuperWebSocket.pdb b/cb-tools/SuperWebSocket/SuperWebSocket.pdb deleted file mode 100644 index c5e3b5c4..00000000 Binary files a/cb-tools/SuperWebSocket/SuperWebSocket.pdb and /dev/null differ diff --git a/cb-tools/SuperWebSocket/UninstallService.bat b/cb-tools/SuperWebSocket/UninstallService.bat deleted file mode 100644 index 9948b806..00000000 --- a/cb-tools/SuperWebSocket/UninstallService.bat +++ /dev/null @@ -1,2 +0,0 @@ -SuperSocket.SocketService.exe -u -pause \ No newline at end of file diff --git a/cb-tools/SuperWebSocket/WebSocket4Net.dll b/cb-tools/SuperWebSocket/WebSocket4Net.dll deleted file mode 100644 index 9ca44f13..00000000 Binary files a/cb-tools/SuperWebSocket/WebSocket4Net.dll and /dev/null differ diff --git a/cb-tools/SuperWebSocket/log4net.dll b/cb-tools/SuperWebSocket/log4net.dll deleted file mode 100644 index c3ced354..00000000 Binary files a/cb-tools/SuperWebSocket/log4net.dll and /dev/null differ diff --git a/cb-tools/burgbox/+io/TCPCommunicator.m b/cb-tools/burgbox/+io/TCPCommunicator.m index dce2ffa4..839f042d 100644 --- a/cb-tools/burgbox/+io/TCPCommunicator.m +++ b/cb-tools/burgbox/+io/TCPCommunicator.m @@ -149,7 +149,7 @@ function open(obj) case 'request' obj.requestConnection(obj.pRequestHost, obj.pRequestPort, obj.pConnectionTimeout); otherwise - error('Invalide connection mode ''%s''', obj.pConnectionMode); + error('Invalid connection mode ''%s''', obj.pConnectionMode); end end diff --git a/cb-tools/burgbox/+plt/binoErrorbar.m b/cb-tools/burgbox/+plt/binoErrorbar.m deleted file mode 100644 index 8f49b2b8..00000000 --- a/cb-tools/burgbox/+plt/binoErrorbar.m +++ /dev/null @@ -1,24 +0,0 @@ -function [h, p, n] = binoErrorbar(axh, x, trialsByX, varargin) -%PLT.BINOERRORBAR Plot parametric binomial data with 95% CI errorbars -% H = PLT.BINOERRORBAR(axh, x, trialsByX, varargin) -% -% Part of Burgbox - -% 2013-10 CB created - -[phat, pci] = mapToCell(@(t) binofit(sum(t(:)), numel(t)), trialsByX); -% convert to percentages -phat = 100*cell2mat(phat'); -pci = 100*cell2mat(pci'); -if nargout > 2 - n = cellfun(@numel, trialsByX(:)); -end -p = phat/100; -if numel(x) > 0 - h = plt.errorbar(axh, x, phat, pci(:,1), pci(:,2), varargin{:}); -else - h = []; -end - -end - diff --git a/cb-tools/burgbox/+plt/errorbar.m b/cb-tools/burgbox/+plt/errorbar.m deleted file mode 100644 index 85eb9b61..00000000 --- a/cb-tools/burgbox/+plt/errorbar.m +++ /dev/null @@ -1,23 +0,0 @@ -function h = errorbar(axh, x, y, yL, yU, varargin) -%PLT.ERRORBAR Like MATLAB's errorbar but specify actual EB co-ordinates -% H = PLT.ERRORBAR(AXH, X, Y, YL, YU, ...) -% -% Differences to MATLAB's errorbar, this: -% * takes the actual y coordinates of the errorbar top and bottom, rather -% than the offset from y coordinates -% * always takes the axes as the first parameter -% * if there is no data to plot, then it won't throw a wobbly, it just -% wont plot anything -% -% Part of Burgbox - -% 2013-10 CB created - -if numel(x) > 0 - h = errorbar(x, y, y - yL, y - yU, varargin{:}, 'Parent', axh); -else - h = []; -end - -end - diff --git a/cb-tools/burgbox/+plt/hshade.m b/cb-tools/burgbox/+plt/hshade.m deleted file mode 100644 index 216e064a..00000000 --- a/cb-tools/burgbox/+plt/hshade.m +++ /dev/null @@ -1,37 +0,0 @@ -function [fillhandle, msg] = hshade(ax, xpoints, upper, lower, color, edge, alpha) -%USAGE: [fillhandle, msg] = fill(xpoints, upper, lower, color, edge, add, transparency) -%This function will fill a region with a color between the two vectors provided -%using the Matlab fill command. -% -%fillhandle is the returned handle to the filled region in the plot. -%xpoints= The horizontal data points (ie frequencies). Note length(Upper) -% must equal Length(lower)and must equal length(xpoints)! -%upper = the upper curve values (data can be less than lower) -%lower = the lower curve values (data can be more than upper) -%color = the color of the filled area -%edge = the color around the edge of the filled area -%add = a flag to add to the current plot or make a new one. -%transparency is a value ranging from 1 for opaque to 0 for invisible for -%the filled color only. -% -%John A. Bockstege November 2006; -%Example: -% a=rand(1,20);%Vector of random data -% b=a+2*rand(1,20);%2nd vector of data points; -% x=1:20;%horizontal vector -% [ph,msg]=jbfill(x,a,b,rand(1,3),rand(1,3),0,rand(1,1)) -% grid on -% legend('Datr') -if nargin<7;alpha=.5;end %default is to have a transparency of .5 -if nargin<6;edge='k';end %dfault edge color is black -if nargin<5;color='b';end %default color is blue - -if length(upper)==length(lower) && length(lower)==length(xpoints) - msg=''; - filled=[upper,fliplr(lower)]; - xpoints=[xpoints,fliplr(xpoints)]; - fillhandle = fill(xpoints, filled, color, 'Parent', ax);%plot the data - set(fillhandle, 'EdgeColor', edge, 'FaceAlpha', alpha, 'EdgeAlpha', alpha);%set edge color -else - msg='Error: Must use the same number of points in each vector'; -end diff --git a/cb-tools/burgbox/+plt/vshade.m b/cb-tools/burgbox/+plt/vshade.m deleted file mode 100644 index c4abb2b1..00000000 --- a/cb-tools/burgbox/+plt/vshade.m +++ /dev/null @@ -1,37 +0,0 @@ -function [fillhandle, msg] = vshade(ax, ypoints, left, right, color, edge, alpha) -%USAGE: [fillhandle, msg] = fill(xpoints, upper, lower, color, edge, add, transparency) -%This function will fill a region with a color between the two vectors provided -%using the Matlab fill command. -% -%fillhandle is the returned handle to the filled region in the plot. -%xpoints= The horizontal data points (ie frequencies). Note length(Upper) -% must equal Length(lower)and must equal length(xpoints)! -%upper = the upper curve values (data can be less than lower) -%lower = the lower curve values (data can be more than upper) -%color = the color of the filled area -%edge = the color around the edge of the filled area -%add = a flag to add to the current plot or make a new one. -%transparency is a value ranging from 1 for opaque to 0 for invisible for -%the filled color only. -% -%John A. Bockstege November 2006; -%Example: -% a=rand(1,20);%Vector of random data -% b=a+2*rand(1,20);%2nd vector of data points; -% x=1:20;%horizontal vector -% [ph,msg]=jbfill(x,a,b,rand(1,3),rand(1,3),0,rand(1,1)) -% grid on -% legend('Datr') -if nargin<7;alpha=.5;end %default is to have a transparency of .5 -if nargin<6;edge='k';end %dfault edge color is black -if nargin<5;color='b';end %default color is blue - -if length(left)==length(right) && length(right)==length(ypoints) - msg=''; - filled=[left,fliplr(right)]; - ypoints=[ypoints,fliplr(ypoints)]; - fillhandle=fill(filled,ypoints,color, 'Parent', ax);%plot the data - set(fillhandle,'EdgeColor',edge,'FaceAlpha',alpha,'EdgeAlpha',alpha);%set edge color -else - msg='Error: Must use the same number of points in each vector'; -end diff --git a/cb-tools/burgbox/mat2DStrTo1D.m b/cb-tools/burgbox/mat2DStrTo1D.m index e8789e0b..ab79761a 100644 --- a/cb-tools/burgbox/mat2DStrTo1D.m +++ b/cb-tools/burgbox/mat2DStrTo1D.m @@ -9,7 +9,7 @@ % 2013-09 CB created if iscellstr(str) - str = mapToCellArray(@matStr2Lines, str); + str = mapToCell(@mat2DStrTo1D, str); else str = strJoin(deblank(num2cell(str, 2)), '\n'); end diff --git a/cb-tools/jsonlab/AUTHORS.txt b/cb-tools/jsonlab/AUTHORS.txt deleted file mode 100644 index 80446a89..00000000 --- a/cb-tools/jsonlab/AUTHORS.txt +++ /dev/null @@ -1,36 +0,0 @@ -The author of "jsonlab" toolbox is Qianqian Fang. Qianqian -is currently an Assistant Professor at Massachusetts General Hospital, -Harvard Medical School. - -Address: Martinos Center for Biomedical Imaging, - Massachusetts General Hospital, - Harvard Medical School - Bldg 149, 13th St, Charlestown, MA 02129, USA -URL: http://nmr.mgh.harvard.edu/~fangq/ -Email: or - - -The script loadjson.m was built upon previous works by - -- Nedialko Krouchev: http://www.mathworks.com/matlabcentral/fileexchange/25713 - date: 2009/11/02 -- Fran§ois Glineur: http://www.mathworks.com/matlabcentral/fileexchange/23393 - date: 2009/03/22 -- Joel Feenstra: http://www.mathworks.com/matlabcentral/fileexchange/20565 - date: 2008/07/03 - - -This toolbox contains patches submitted by the following contributors: - -- Blake Johnson - part of revision 341 - -- Niclas Borlin - various fixes in revision 394, including - - loadjson crashes for all-zero sparse matrix. - - loadjson crashes for empty sparse matrix. - - Non-zero size of 0-by-N and N-by-0 empty matrices is lost after savejson/loadjson. - - loadjson crashes for sparse real column vector. - - loadjson crashes for sparse complex column vector. - - Data is corrupted by savejson for sparse real row vector. - - savejson crashes for sparse complex row vector. diff --git a/cb-tools/jsonlab/ChangeLog.txt b/cb-tools/jsonlab/ChangeLog.txt deleted file mode 100644 index f409e767..00000000 --- a/cb-tools/jsonlab/ChangeLog.txt +++ /dev/null @@ -1,47 +0,0 @@ -============================================================================ - - JSONlab - a toolbox to encode/decode JSON/UBJSON files in MATLAB/Octave - ----------------------------------------------------------------------------- - -JSONlab ChangeLog (key features marked by *): - -== JSONlab 0.9.8 (codename: Optimus - alpha), FangQ == - 2013/08/23 *Universal Binary JSON (UBJSON) support, including both saveubjson and loadubjson - -== JSONlab 0.9.1 (codename: Rodimus, update 1), FangQ == - 2012/12/18 *handling of various empty and sparse matrices (fixes submitted by Niclas Borlin) - -== JSONlab 0.9.0 (codename: Rodimus), FangQ == - - 2012/06/17 *new format for an invalid leading char, unpacking hex code in savejson - 2012/06/01 support JSONP in savejson - 2012/05/25 fix the empty cell bug (reported by Cyril Davin) - 2012/04/05 savejson can save to a file (suggested by Patrick Rapin) - -== JSONlab 0.8.1 (codename: Sentiel, Update 1), FangQ == - - 2012/02/28 loadjson quotation mark escape bug, see http://bit.ly/yyk1nS - 2012/01/25 patch to handle root-less objects, contributed by Blake Johnson - -== JSONlab 0.8.0 (codename: Sentiel), FangQ == - - 2012/01/13 *speed up loadjson by 20 fold when parsing large data arrays in matlab - 2012/01/11 remove row bracket if an array has 1 element, suggested by Mykel Kochenderfer - 2011/12/22 *accept sequence of 'param',value input in savejson and loadjson - 2011/11/18 fix struct array bug reported by Mykel Kochenderfer - -== JSONlab 0.5.1 (codename: Nexus Update 1), FangQ == - - 2011/10/21 fix a bug in loadjson, previous code does not use any of the acceleration - 2011/10/20 loadjson supports JSON collections - concatenated JSON objects - -== JSONlab 0.5.0 (codename: Nexus), FangQ == - - 2011/10/16 package and release jsonlab 0.5.0 - 2011/10/15 *add json demo and regression test, support cpx numbers, fix double quote bug - 2011/10/11 *speed up readjson dramatically, interpret _Array* tags, show data in root level - 2011/10/10 create jsonlab project, start jsonlab website, add online documentation - 2011/10/07 *speed up savejson by 25x using sprintf instead of mat2str, add options support - 2011/10/06 *savejson works for structs, cells and arrays - 2011/09/09 derive loadjson from JSON parser from MATLAB Central, draft savejson.m diff --git a/cb-tools/jsonlab/LICENSE_BSD.txt b/cb-tools/jsonlab/LICENSE_BSD.txt deleted file mode 100644 index cc5fb4d6..00000000 --- a/cb-tools/jsonlab/LICENSE_BSD.txt +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2011 Qianqian Fang . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are -permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of - conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list - of conditions and the following disclaimer in the documentation and/or other materials - provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS -OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those of the -authors and should not be interpreted as representing official policies, either expressed -or implied, of the copyright holders. diff --git a/cb-tools/jsonlab/LICENSE_GPLv3.txt b/cb-tools/jsonlab/LICENSE_GPLv3.txt deleted file mode 100644 index 94a9ed02..00000000 --- a/cb-tools/jsonlab/LICENSE_GPLv3.txt +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/cb-tools/jsonlab/README.txt b/cb-tools/jsonlab/README.txt deleted file mode 100644 index 83616b81..00000000 --- a/cb-tools/jsonlab/README.txt +++ /dev/null @@ -1,335 +0,0 @@ -=============================================================================== -= JSONlab = -= An open-source MATLAB/Octave JSON encoder and decoder = -=============================================================================== - -*Copyright (c) 2011-2013 Qianqian Fang -*License: BSD or GNU General Public License version 3 (GPL v3), see License*.txt -*Version: 0.9.8 (Optimus - alpha) - -------------------------------------------------------------------------------- - -Table of Content: - -I. Introduction -II. Installation -III.Using JSONlab -IV. Known Issues and TODOs -V. Contribution and feedback - -------------------------------------------------------------------------------- - -I. Introduction - -JSON ([http://www.json.org/ JavaScript Object Notation]) is a highly portable, -human-readable and "[http://en.wikipedia.org/wiki/JSON fat-free]" text format -to represent complex and hierarchical data. It is as powerful as -[http://en.wikipedia.org/wiki/XML XML], but less verbose. JSON format is widely -used for data-exchange in applications, and is essential for the wild success -of [http://en.wikipedia.org/wiki/Ajax_(programming) Ajax] and -[http://en.wikipedia.org/wiki/Web_2.0 Web2.0]. - -UBJSON (Universal Binary JSON) is a binary JSON format, specifically -optimized for compact file size and better performance while keeping -the semantics as simple as the text-based JSON format. Using the UBJSON -format allows to wrap complex binary data in a flexible and extensible -structure, making it possible to process complex and large dataset -without accuracy loss due to text conversions. - -We envision that both JSON and its binary version will serve as part of -the mainstream data-exchange formats for scientific research in the future. -It will provide the flexibility and generality achieved by other popular -general-purpose file specifications, such as -[http://www.hdfgroup.org/HDF5/whatishdf5.html HDF5], with significantly -reduced complexity and enhanced performance. - -JSONlab is a free and open-source implementation of a JSON/UBJSON encoder -and a decoder in the native MATLAB language. It can be used to convert a MATLAB -data structure (array, struct, cell, struct array and cell array) into -JSON/UBJSON formatted strings, or to decode a JSON/UBJSON file into MATLAB -data structure. JSONlab supports both MATLAB and -[http://www.gnu.org/software/octave/ GNU Octave] (a free MATLAB clone). - -------------------------------------------------------------------------------- - -II. Installation - -The installation of JSONlab is no different than any other simple -MATLAB toolbox. You only need to download/unzip the JSONlab package -to a folder, and add the folder's path to MATLAB/Octave's path list -by using the following command: - - addpath('/path/to/jsonlab'); - -If you want to add this path permanently, you need to type "pathtool", -browse to the jsonlab root folder and add to the list, then click "Save". -Then, run "rehash" in MATLAB, and type "which loadjson", if you see an -output, that means JSONlab is installed for MATLAB/Octave. - -------------------------------------------------------------------------------- - -III.Using JSONlab - -JSONlab provides two functions, loadjson.m -- a MATLAB->JSON decoder, -and savejson.m -- a MATLAB->JSON encoder, for the text-based JSON, and -two equivallent functions -- loadubjson and saveubjson for the binary -JSON. The detailed help info for the four functions can be found below: - -=== loadjson.m === -
-  data=loadjson(fname,opt)
-     or
-  data=loadjson(fname,'param1',value1,'param2',value2,...)
- 
-  parse a JSON (JavaScript Object Notation) file or string
- 
-  authors:Qianqian Fang (fangq nmr.mgh.harvard.edu)
-             date: 2011/09/09
-          Nedialko Krouchev: http://www.mathworks.com/matlabcentral/fileexchange/25713
-             date: 2009/11/02
-          François Glineur: http://www.mathworks.com/matlabcentral/fileexchange/23393
-             date: 2009/03/22
-          Joel Feenstra:
-          http://www.mathworks.com/matlabcentral/fileexchange/20565
-             date: 2008/07/03
- 
-  $Id: loadjson.m 394 2012-12-18 17:58:11Z fangq $
- 
-  input:
-       fname: input file name, if fname contains "{}" or "[]", fname
-              will be interpreted as a JSON string
-       opt: a struct to store parsing options, opt can be replaced by 
-            a list of ('param',value) pairs. The param string is equivallent
-            to a field in opt.
- 
-  output:
-       dat: a cell array, where {...} blocks are converted into cell arrays,
-            and [...] are converted to arrays
-
- -=== savejson.m === - -
-  json=savejson(rootname,obj,filename)
-     or
-  json=savejson(rootname,obj,opt)
-  json=savejson(rootname,obj,'param1',value1,'param2',value2,...)
- 
-  convert a MATLAB object (cell, struct or array) into a JSON (JavaScript
-  Object Notation) string
- 
-  author: Qianqian Fang (fangq nmr.mgh.harvard.edu)
-             created on 2011/09/09
- 
-  $Id: savejson.m 394 2012-12-18 17:58:11Z fangq $
- 
-  input:
-       rootname: name of the root-object, if set to '', will use variable name
-       obj: a MATLAB object (array, cell, cell array, struct, struct array)
-       filename: a string for the file name to save the output JSON data
-       opt: a struct for additional options, use [] if all use default
-         opt can have the following fields (first in [.|.] is the default)
- 
-         opt.FileName [''|string]: a file name to save the output JSON data
-         opt.FloatFormat ['%.10g'|string]: format to show each numeric element
-                          of a 1D/2D array;
-         opt.ArrayIndent [1|0]: if 1, output explicit data array with
-                          precedent indentation; if 0, no indentation
-         opt.ArrayToStruct[0|1]: when set to 0, savejson outputs 1D/2D
-                          array in JSON array format; if sets to 1, an
-                          array will be shown as a struct with fields
-                          "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for
-                          sparse arrays, the non-zero elements will be
-                          saved to _ArrayData_ field in triplet-format i.e.
-                          (ix,iy,val) and "_ArrayIsSparse_" will be added
-                          with a value of 1; for a complex array, the 
-                          _ArrayData_ array will include two columns 
-                          (4 for sparse) to record the real and imaginary 
-                          parts, and also "_ArrayIsComplex_":1 is added. 
-         opt.ParseLogical [0|1]: if this is set to 1, logical array elem
-                          will use true/false rather than 1/0.
-         opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single
-                          numerical element will be shown without a square
-                          bracket, unless it is the root object; if 0, square
-                          brackets are forced for any numerical arrays.
-         opt.ForceRootName [0|1]: when set to 1 and rootname is empty, savejson
-                          will use the name of the passed obj variable as the 
-                          root object name; if obj is an expression and 
-                          does not have a name, 'root' will be used; if this 
-                          is set to 0 and rootname is empty, the root level 
-                          will be merged down to the lower level.
-         opt.Inf ['"$1_Inf_"'|string]: a customized regular expression pattern
-                          to represent +/-Inf. The matched pattern is '([-+]*)Inf'
-                          and $1 represents the sign. For those who want to use
-                          1e999 to represent Inf, they can set opt.Inf to '$11e999'
-         opt.NaN ['"_NaN_"'|string]: a customized regular expression pattern
-                          to represent NaN
-         opt.JSONP [''|string]: to generate a JSONP output (JSON with padding),
-                          for example, if opt.JSON='foo', the JSON data is
-                          wrapped inside a function call as 'foo(...);'
-         opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson 
-                          back to the string form
-         opt can be replaced by a list of ('param',value) pairs. The param 
-         string is equivallent to a field in opt.
-  output:
-       json: a string in the JSON format (see http://json.org)
- 
-  examples:
-       a=struct('node',[1  9  10; 2 1 1.2], 'elem',[9 1;1 2;2 3],...
-            'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ');
-       savejson('mesh',a)
-       savejson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g')
-
- -=== loadubjson.m === - -
-  data=loadubjson(fname,opt)
-     or
-  data=loadubjson(fname,'param1',value1,'param2',value2,...)
- 
-  parse a JSON (JavaScript Object Notation) file or string
- 
-  authors:Qianqian Fang (fangq nmr.mgh.harvard.edu)
-             date: 2013/08/01
- 
-  $Id: loadubjson.m 410 2013-08-24 03:33:18Z fangq $
- 
-  input:
-       fname: input file name, if fname contains "{}" or "[]", fname
-              will be interpreted as a UBJSON string
-       opt: a struct to store parsing options, opt can be replaced by 
-            a list of ('param',value) pairs. The param string is equivallent
-            to a field in opt.
- 
-  output:
-       dat: a cell array, where {...} blocks are converted into cell arrays,
-            and [...] are converted to arrays
-
- -=== saveubjson.m === - -
-  json=saveubjson(rootname,obj,filename)
-     or
-  json=saveubjson(rootname,obj,opt)
-  json=saveubjson(rootname,obj,'param1',value1,'param2',value2,...)
- 
-  convert a MATLAB object (cell, struct or array) into a Universal 
-  Binary JSON (UBJSON) binary string
- 
-  author: Qianqian Fang (fangq nmr.mgh.harvard.edu)
-             created on 2013/08/17
- 
-  $Id: saveubjson.m 410 2013-08-24 03:33:18Z fangq $
- 
-  input:
-       rootname: name of the root-object, if set to '', will use variable name
-       obj: a MATLAB object (array, cell, cell array, struct, struct array)
-       filename: a string for the file name to save the output JSON data
-       opt: a struct for additional options, use [] if all use default
-         opt can have the following fields (first in [.|.] is the default)
- 
-         opt.FileName [''|string]: a file name to save the output JSON data
-         opt.ArrayToStruct[0|1]: when set to 0, saveubjson outputs 1D/2D
-                          array in JSON array format; if sets to 1, an
-                          array will be shown as a struct with fields
-                          "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for
-                          sparse arrays, the non-zero elements will be
-                          saved to _ArrayData_ field in triplet-format i.e.
-                          (ix,iy,val) and "_ArrayIsSparse_" will be added
-                          with a value of 1; for a complex array, the 
-                          _ArrayData_ array will include two columns 
-                          (4 for sparse) to record the real and imaginary 
-                          parts, and also "_ArrayIsComplex_":1 is added. 
-         opt.ParseLogical [1|0]: if this is set to 1, logical array elem
-                          will use true/false rather than 1/0.
-         opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single
-                          numerical element will be shown without a square
-                          bracket, unless it is the root object; if 0, square
-                          brackets are forced for any numerical arrays.
-         opt.ForceRootName [0|1]: when set to 1 and rootname is empty, saveubjson
-                          will use the name of the passed obj variable as the 
-                          root object name; if obj is an expression and 
-                          does not have a name, 'root' will be used; if this 
-                          is set to 0 and rootname is empty, the root level 
-                          will be merged down to the lower level.
-         opt.JSONP [''|string]: to generate a JSONP output (JSON with padding),
-                          for example, if opt.JSON='foo', the JSON data is
-                          wrapped inside a function call as 'foo(...);'
-         opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson 
-                          back to the string form
-         opt can be replaced by a list of ('param',value) pairs. The param 
-         string is equivallent to a field in opt.
-  output:
-       json: a string in the JSON format (see http://json.org)
- 
-  examples:
-       a=struct('node',[1  9  10; 2 1 1.2], 'elem',[9 1;1 2;2 3],...
-            'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ');
-       saveubjson('mesh',a)
-       saveubjson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g')
-
- - -=== examples === - -Under the "examples" folder, you can find several scripts to demonstrate the -basic utilities of JSONlab. Running the "demo_jsonlab_basic.m" script, you -will see the conversions from MATLAB data structure to JSON text and backward. -In "jsonlab_selftest.m", we load complex JSON files downloaded from the Internet -and validate the loadjson/savejson functions for regression testing purposes. -Similarly, a "demo_ubjson_basic.m" script is provided to test the saveubjson -and loadubjson pairs for various matlab data structures. - -Please run these examples and understand how JSONlab works before you use -it to process your data. - -------------------------------------------------------------------------------- - -IV. Known Issues and TODOs - -JSONlab has several known limitations. We are striving to make it more general -and robust. Hopefully in a few future releases, the limitations become less. - -Here are the known issues: - -# Any high-dimensional cell-array will be converted to a 1D array; -# When processing names containing multi-byte characters, Octave and MATLAB \ -can give different field-names; you can use feature('DefaultCharacterSet','latin1') \ -in MATLAB to get consistant results -# Can not handle classes. -# saveubjson has not yet supported arbitrary data ([H] in the UBJSON specification) -# saveubjson now converts a logical array into a uint8 ([U]) array for now -# an unofficial N-D array count syntax is implemented in saveubjson. We are \ -actively communicating with the UBJSON spec maintainer to investigate the \ -possibility of making it upstream - -------------------------------------------------------------------------------- - -V. Contribution and feedback - -JSONlab is an open-source project. This means you can not only use it and modify -it as you wish, but also you can contribute your changes back to JSONlab so -that everyone else can enjoy the improvement. For anyone who want to contribute, -please download JSONlab source code from it's subversion repository by using the -following command: - - svn co https://iso2mesh.svn.sourceforge.net/svnroot/iso2mesh/trunk/jsonlab jsonlab - -You can make changes to the files as needed. Once you are satisfied with your -changes, and ready to share it with others, please cd the root directory of -JSONlab, and type - - svn diff > yourname_featurename.patch - -You then email the .patch file to JSONlab's maintainer, Qianqian Fang, at -the email address shown in the beginning of this file. Qianqian will review -the changes and commit it to the subversion if they are satisfactory. - -We appreciate any suggestions and feedbacks from you. Please use iso2mesh's -mailing list to report any questions you may have with JSONlab: - -http://groups.google.com/group/iso2mesh-users?hl=en&pli=1 - -(Subscription to the mailing list is needed in order to post messages). diff --git a/cb-tools/jsonlab/examples/demo_jsonlab_basic.m b/cb-tools/jsonlab/examples/demo_jsonlab_basic.m deleted file mode 100644 index d094a64e..00000000 --- a/cb-tools/jsonlab/examples/demo_jsonlab_basic.m +++ /dev/null @@ -1,161 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Demonstration of Basic Utilities of JSONlab -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -rngstate = rand ('state'); -randseed=hex2dec('623F9A9E'); -clear data2json json2data - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a simple scalar value \n') -fprintf(1,'%%=================================================\n\n') - -data2json=pi -savejson('',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex number\n') -fprintf(1,'%%=================================================\n\n') - -clear i; -data2json=1+2*i -savejson('',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=magic(6); -data2json=data2json(:,1:3)+data2json(:,4:6)*i -savejson('',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% MATLAB special constants\n') -fprintf(1,'%%=================================================\n\n') - -data2json=[NaN Inf -Inf] -savejson('specials',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a real sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sprand(10,10,0.1) -savejson('sparse',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-data2json*i -savejson('complex_sparse',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an all-zero sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse(2,3); -savejson('all_zero_sparse',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([]); -savejson('empty_sparse',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty 0-by-0 real matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=[]; -savejson('empty_0by0_real',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty 0-by-3 real matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=zeros(0,3); -savejson('empty_0by3_real',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse real column vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([0,3,0,1,4]'); -savejson('sparse_column_vector',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse complex column vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-1i*data2json; -savejson('complex_sparse_column_vector',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse real row vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([0,3,0,1,4]); -savejson('sparse_row_vector',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse complex row vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-1i*data2json; -savejson('complex_sparse_row_vector',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a structure\n') -fprintf(1,'%%=================================================\n\n') - -data2json=struct('name','Think Different','year',1997,'magic',magic(3),... - 'misfits',[Inf,NaN],'embedded',struct('left',true,'right',false)) -savejson('astruct',data2json,struct('ParseLogical',1)) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a structure array\n') -fprintf(1,'%%=================================================\n\n') - -data2json=struct('name','Nexus Prime','rank',9); -data2json(2)=struct('name','Sentinel Prime','rank',9); -data2json(3)=struct('name','Optimus Prime','rank',9); -savejson('Supreme Commander',data2json) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a cell array\n') -fprintf(1,'%%=================================================\n\n') - -data2json=cell(3,1); -data2json{1}=struct('buzz',1.1,'rex',1.2,'bo',1.3,'hamm',2.0,'slink',2.1,'potato',2.2,... - 'woody',3.0,'sarge',3.1,'etch',4.0,'lenny',5.0,'squeeze',6.0,'wheezy',7.0); -data2json{2}=struct('Ubuntu',['Kubuntu';'Xubuntu';'Lubuntu']); -data2json{3}=[10.04,10.10,11.04,11.10] -savejson('debian',data2json,struct('FloatFormat','%.2f')) -json2data=loadjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% invalid field-name handling\n') -fprintf(1,'%%=================================================\n\n') - -json2data=loadjson('{"ValidName":1, "_InvalidName":2, ":Field:":3, "éĦıç›":"çğċŻ†"}') - -rand ('state',rngstate); - diff --git a/cb-tools/jsonlab/examples/demo_ubjson_basic.m b/cb-tools/jsonlab/examples/demo_ubjson_basic.m deleted file mode 100644 index fd5096c3..00000000 --- a/cb-tools/jsonlab/examples/demo_ubjson_basic.m +++ /dev/null @@ -1,161 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Demonstration of Basic Utilities of JSONlab -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -rngstate = rand ('state'); -randseed=hex2dec('623F9A9E'); -clear data2json json2data - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a simple scalar value \n') -fprintf(1,'%%=================================================\n\n') - -data2json=pi -saveubjson('',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex number\n') -fprintf(1,'%%=================================================\n\n') - -clear i; -data2json=1+2*i -saveubjson('',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=magic(6); -data2json=data2json(:,1:3)+data2json(:,4:6)*i -saveubjson('',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% MATLAB special constants\n') -fprintf(1,'%%=================================================\n\n') - -data2json=[NaN Inf -Inf] -saveubjson('specials',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a real sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sprand(10,10,0.1) -saveubjson('sparse',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a complex sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-data2json*i -saveubjson('complex_sparse',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an all-zero sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse(2,3); -saveubjson('all_zero_sparse',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty sparse matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([]); -saveubjson('empty_sparse',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty 0-by-0 real matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=[]; -saveubjson('empty_0by0_real',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% an empty 0-by-3 real matrix\n') -fprintf(1,'%%=================================================\n\n') - -data2json=zeros(0,3); -saveubjson('empty_0by3_real',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse real column vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([0,3,0,1,4]'); -saveubjson('sparse_column_vector',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse complex column vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-1i*data2json; -saveubjson('complex_sparse_column_vector',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse real row vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=sparse([0,3,0,1,4]); -saveubjson('sparse_row_vector',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a sparse complex row vector\n') -fprintf(1,'%%=================================================\n\n') - -data2json=data2json-1i*data2json; -saveubjson('complex_sparse_row_vector',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a structure\n') -fprintf(1,'%%=================================================\n\n') - -data2json=struct('name','Think Different','year',1997,'magic',magic(3),... - 'misfits',[Inf,NaN],'embedded',struct('left',true,'right',false)) -saveubjson('astruct',data2json,struct('ParseLogical',1)) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a structure array\n') -fprintf(1,'%%=================================================\n\n') - -data2json=struct('name','Nexus Prime','rank',9); -data2json(2)=struct('name','Sentinel Prime','rank',9); -data2json(3)=struct('name','Optimus Prime','rank',9); -saveubjson('Supreme Commander',data2json) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% a cell array\n') -fprintf(1,'%%=================================================\n\n') - -data2json=cell(3,1); -data2json{1}=struct('buzz',1.1,'rex',1.2,'bo',1.3,'hamm',2.0,'slink',2.1,'potato',2.2,... - 'woody',3.0,'sarge',3.1,'etch',4.0,'lenny',5.0,'squeeze',6.0,'wheezy',7.0); -data2json{2}=struct('Ubuntu',['Kubuntu';'Xubuntu';'Lubuntu']); -data2json{3}=[10.04,10.10,11.04,11.10] -saveubjson('debian',data2json,struct('FloatFormat','%.2f')) -json2data=loadubjson(ans) - -fprintf(1,'\n%%=================================================\n') -fprintf(1,'%% invalid field-name handling\n') -fprintf(1,'%%=================================================\n\n') - -json2data=loadubjson(saveubjson('',loadjson('{"ValidName":1, "_InvalidName":2, ":Field:":3, "éĦıç›":"çğċŻ†"}'))) - -rand ('state',rngstate); - diff --git a/cb-tools/jsonlab/examples/example1.json b/cb-tools/jsonlab/examples/example1.json deleted file mode 100644 index be0993ef..00000000 --- a/cb-tools/jsonlab/examples/example1.json +++ /dev/null @@ -1,23 +0,0 @@ - { - "firstName": "John", - "lastName": "Smith", - "age": 25, - "address": - { - "streetAddress": "21 2nd Street", - "city": "New York", - "state": "NY", - "postalCode": "10021" - }, - "phoneNumber": - [ - { - "type": "home", - "number": "212 555-1234" - }, - { - "type": "fax", - "number": "646 555-4567" - } - ] - } diff --git a/cb-tools/jsonlab/examples/example2.json b/cb-tools/jsonlab/examples/example2.json deleted file mode 100644 index eacfbf5e..00000000 --- a/cb-tools/jsonlab/examples/example2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "glossary": { - "title": "example glossary", - "GlossDiv": { - "title": "S", - "GlossList": { - "GlossEntry": { - "ID": "SGML", - "SortAs": "SGML", - "GlossTerm": "Standard Generalized Markup Language", - "Acronym": "SGML", - "Abbrev": "ISO 8879:1986", - "GlossDef": { - "para": "A meta-markup language, used to create markup languages such as DocBook.", - "GlossSeeAlso": ["GML", "XML"] - }, - "GlossSee": "markup" - } - } - } - } -} diff --git a/cb-tools/jsonlab/examples/example3.json b/cb-tools/jsonlab/examples/example3.json deleted file mode 100644 index b7ca9411..00000000 --- a/cb-tools/jsonlab/examples/example3.json +++ /dev/null @@ -1,11 +0,0 @@ -{"menu": { - "id": "file", - "value": "_&File", - "popup": { - "menuitem": [ - {"value": "_&New", "onclick": "CreateNewDoc(\"\"\")"}, - {"value": "_&Open", "onclick": "OpenDoc()"}, - {"value": "_&Close", "onclick": "CloseDoc()"} - ] - } -}} diff --git a/cb-tools/jsonlab/examples/example4.json b/cb-tools/jsonlab/examples/example4.json deleted file mode 100644 index 66deeafb..00000000 --- a/cb-tools/jsonlab/examples/example4.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "sample" : { - "rho" : 1 - } - }, - { - "sample" : { - "rho" : 2 - } - }, - [ - { - "_ArrayType_" : "double", - "_ArraySize_" : [1,2], - "_ArrayData_" : [1,0] - }, - { - "_ArrayType_" : "double", - "_ArraySize_" : [1,2], - "_ArrayData_" : [1,1] - }, - { - "_ArrayType_" : "double", - "_ArraySize_" : [1,2], - "_ArrayData_" : [1,2] - } - ], - [ - "Paper", - "Scissors", - "Stone" - ] -] diff --git a/cb-tools/jsonlab/examples/jsonlab_basictest.matlab b/cb-tools/jsonlab/examples/jsonlab_basictest.matlab deleted file mode 100644 index ff8380a1..00000000 --- a/cb-tools/jsonlab/examples/jsonlab_basictest.matlab +++ /dev/null @@ -1,361 +0,0 @@ - - < M A T L A B > - Copyright 1984-2007 The MathWorks, Inc. - Version 7.4.0.287 (R2007a) - January 29, 2007 - - - To get started, type one of these: helpwin, helpdesk, or demo. - For product information, visit www.mathworks.com. - ->> >> >> >> >> >> >> >> >> -%================================================= ->> % a simple scalar value ->> %================================================= - ->> >> -data2json = - - 3.1416 - ->> -ans = - -[3.141592654] - - ->> -json2data = - - 3.1416 - ->> >> -%================================================= ->> % a complex number ->> %================================================= - ->> >> >> -data2json = - - 1.0000 + 2.0000i - ->> -ans = - -{ - "_ArrayType_": "double", - "_ArraySize_": [1,1], - "_ArrayIsComplex_": 1, - "_ArrayData_": [1,2] -} - - ->> -json2data = - - 1.0000 + 2.0000i - ->> >> -%================================================= ->> % a complex matrix ->> %================================================= - ->> >> >> -data2json = - - 35.0000 +26.0000i 1.0000 +19.0000i 6.0000 +24.0000i - 3.0000 +21.0000i 32.0000 +23.0000i 7.0000 +25.0000i - 31.0000 +22.0000i 9.0000 +27.0000i 2.0000 +20.0000i - 8.0000 +17.0000i 28.0000 +10.0000i 33.0000 +15.0000i - 30.0000 +12.0000i 5.0000 +14.0000i 34.0000 +16.0000i - 4.0000 +13.0000i 36.0000 +18.0000i 29.0000 +11.0000i - ->> -ans = - -{ - "_ArrayType_": "double", - "_ArraySize_": [6,3], - "_ArrayIsComplex_": 1, - "_ArrayData_": [ - [35,26], - [3,21], - [31,22], - [8,17], - [30,12], - [4,13], - [1,19], - [32,23], - [9,27], - [28,10], - [5,14], - [36,18], - [6,24], - [7,25], - [2,20], - [33,15], - [34,16], - [29,11] - ] -} - - ->> -json2data = - - 35.0000 +26.0000i 1.0000 +19.0000i 6.0000 +24.0000i - 3.0000 +21.0000i 32.0000 +23.0000i 7.0000 +25.0000i - 31.0000 +22.0000i 9.0000 +27.0000i 2.0000 +20.0000i - 8.0000 +17.0000i 28.0000 +10.0000i 33.0000 +15.0000i - 30.0000 +12.0000i 5.0000 +14.0000i 34.0000 +16.0000i - 4.0000 +13.0000i 36.0000 +18.0000i 29.0000 +11.0000i - ->> >> -%================================================= ->> % MATLAB special constants ->> %================================================= - ->> >> -data2json = - - NaN Inf -Inf - ->> -ans = - -{ - "specials": ["_NaN_","_Inf_","-_Inf_"] -} - - ->> -json2data = - - specials: [NaN Inf -Inf] - ->> >> -%================================================= ->> % a real sparse matrix ->> %================================================= - ->> >> -data2json = - - (1,2) 0.6557 - (9,2) 0.7577 - (3,5) 0.8491 - (10,5) 0.7431 - (10,8) 0.3922 - (7,9) 0.6787 - (2,10) 0.0357 - (6,10) 0.9340 - (10,10) 0.6555 - ->> -ans = - -{ - "sparse": { - "_ArrayType_": "double", - "_ArraySize_": [10,10], - "_ArrayIsSparse_": 1, - "_ArrayData_": [ - [1,2,0.6557406992], - [9,2,0.7577401306], - [3,5,0.8491293059], - [10,5,0.7431324681], - [10,8,0.3922270195], - [7,9,0.6787351549], - [2,10,0.03571167857], - [6,10,0.9339932478], - [10,10,0.6554778902] - ] - } -} - - ->> -json2data = - - sparse: [10x10 double] - ->> >> -%================================================= ->> % a complex sparse matrix ->> %================================================= - ->> >> -data2json = - - (1,2) 0.6557 - 0.6557i - (9,2) 0.7577 - 0.7577i - (3,5) 0.8491 - 0.8491i - (10,5) 0.7431 - 0.7431i - (10,8) 0.3922 - 0.3922i - (7,9) 0.6787 - 0.6787i - (2,10) 0.0357 - 0.0357i - (6,10) 0.9340 - 0.9340i - (10,10) 0.6555 - 0.6555i - ->> -ans = - -{ - "complex_sparse": { - "_ArrayType_": "double", - "_ArraySize_": [10,10], - "_ArrayIsComplex_": 1, - "_ArrayIsSparse_": 1, - "_ArrayData_": [ - [1,2,0.6557406992,-0.6557406992], - [9,2,0.7577401306,-0.7577401306], - [3,5,0.8491293059,-0.8491293059], - [10,5,0.7431324681,-0.7431324681], - [10,8,0.3922270195,-0.3922270195], - [7,9,0.6787351549,-0.6787351549], - [2,10,0.03571167857,-0.03571167857], - [6,10,0.9339932478,-0.9339932478], - [10,10,0.6554778902,-0.6554778902] - ] - } -} - - ->> -json2data = - - complex_sparse: [10x10 double] - ->> >> -%================================================= ->> % a structure ->> %================================================= - ->> >> -data2json = - - name: 'Think Different' - year: 1997 - magic: [3x3 double] - misfits: [Inf NaN] - embedded: [1x1 struct] - ->> -ans = - -{ - "astruct": { - "name": "Think Different", - "year": 1997, - "magic": [ - [8,1,6], - [3,5,7], - [4,9,2] - ], - "misfits": ["_Inf_","_NaN_"], - "embedded": { - "left": true, - "right": false - } - } -} - - ->> -json2data = - - astruct: [1x1 struct] - ->> >> -%================================================= ->> % a structure array ->> %================================================= - ->> >> >> >> >> -ans = - -{ - "Supreme Commander": [ - { - "name": "Nexus Prime", - "rank": 9 - }, - { - "name": "Sentinel Prime", - "rank": 9 - }, - { - "name": "Optimus Prime", - "rank": 9 - } - ] -} - - ->> -json2data = - - Supreme_0x20_Commander: [1x3 struct] - ->> >> -%================================================= ->> % a cell array ->> %================================================= - ->> >> >> >> >> -data2json = - - [1x1 struct] - [1x1 struct] - [1x4 double] - ->> -ans = - -{ - "debian": [ - { - "buzz": 1.10, - "rex": 1.20, - "bo": 1.30, - "hamm": 2.00, - "slink": 2.10, - "potato": 2.20, - "woody": 3.00, - "sarge": 3.10, - "etch": 4.00, - "lenny": 5.00, - "squeeze": 6.00, - "wheezy": 7.00 - }, - { - "Ubuntu": [ - "Kubuntu", - "Xubuntu", - "Lubuntu" - ] - }, - [10.04,10.10,11.04,11.10] - ] -} - - ->> -json2data = - - debian: {[1x1 struct] [1x1 struct] [10.0400 10.1000 11.0400 11.1000]} - ->> >> -%================================================= ->> % invalid field-name handling ->> %================================================= - ->> >> -json2data = - - ValidName: 1 - x0x5F_InvalidName: 2 - x0x3A_Field_0x3A_: 3 - x0xE9A1B9__0xE79BAE_: 'çğċŻ†' - ->> >> >> >> \ No newline at end of file diff --git a/cb-tools/jsonlab/examples/jsonlab_selftest.m b/cb-tools/jsonlab/examples/jsonlab_selftest.m deleted file mode 100644 index c29ffbe3..00000000 --- a/cb-tools/jsonlab/examples/jsonlab_selftest.m +++ /dev/null @@ -1,12 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Regression Test Unit of loadjson and savejson -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -for i=1:4 - fname=sprintf('example%d.json',i); - if(exist(fname,'file')==0) break; end - fprintf(1,'===============================================\n>> %s\n',fname); - json=savejson('data',loadjson(fname)); - fprintf(1,'%s\n',json); - data=loadjson(json); -end diff --git a/cb-tools/jsonlab/examples/jsonlab_selftest.matlab b/cb-tools/jsonlab/examples/jsonlab_selftest.matlab deleted file mode 100644 index 84e66437..00000000 --- a/cb-tools/jsonlab/examples/jsonlab_selftest.matlab +++ /dev/null @@ -1,121 +0,0 @@ - - < M A T L A B > - Copyright 1984-2007 The MathWorks, Inc. - Version 7.4.0.287 (R2007a) - January 29, 2007 - - - To get started, type one of these: helpwin, helpdesk, or demo. - For product information, visit www.mathworks.com. - ->> >> >> >> >> =============================================== ->> example1.json -{ - "data": { - "firstName": "John", - "lastName": "Smith", - "age": 25, - "address": { - "streetAddress": "21 2nd Street", - "city": "New York", - "state": "NY", - "postalCode": "10021" - }, - "phoneNumber": [ - { - "type": "home", - "number": "212 555-1234" - }, - { - "type": "fax", - "number": "646 555-4567" - } - ] - } -} - -=============================================== ->> example2.json -{ - "data": { - "glossary": { - "title": "example glossary", - "GlossDiv": { - "title": "S", - "GlossList": { - "GlossEntry": { - "ID": "SGML", - "SortAs": "SGML", - "GlossTerm": "Standard Generalized Markup Language", - "Acronym": "SGML", - "Abbrev": "ISO 8879:1986", - "GlossDef": { - "para": "A meta-markup language, used to create markup languages such as DocBook.", - "GlossSeeAlso": [ - "GML", - "XML" - ] - }, - "GlossSee": "markup" - } - } - } - } - } -} - -=============================================== ->> example3.json -{ - "data": { - "menu": { - "id": "file", - "value": "_&File", - "popup": { - "menuitem": [ - { - "value": "_&New", - "onclick": "CreateNewDoc(\"\"\")" - }, - { - "value": "_&Open", - "onclick": "OpenDoc()" - }, - { - "value": "_&Close", - "onclick": "CloseDoc()" - } - ] - } - } - } -} - -=============================================== ->> example4.json -{ - "data": [ - { - "sample": { - "rho": 1 - } - }, - { - "sample": { - "rho": 2 - } - }, - [ - [1,0], - [1,1], - [1,2] - ], - [ - "Paper", - "Scissors", - "Stone" - ] - ] -} - ->> \ No newline at end of file diff --git a/cb-tools/jsonlab/examples/jsonlab_speedtest.m b/cb-tools/jsonlab/examples/jsonlab_speedtest.m deleted file mode 100644 index 4990fba0..00000000 --- a/cb-tools/jsonlab/examples/jsonlab_speedtest.m +++ /dev/null @@ -1,21 +0,0 @@ -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% Benchmarking processing speed of savejson and loadjson -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -datalen=[1e3 1e4 1e5 1e6]; -len=length(datalen); -tsave=zeros(len,1); -tload=zeros(len,1); -for i=1:len - tic; - json=savejson('data',struct('d1',rand(datalen(i),3),'d2',rand(datalen(i),3)>0.5)); - tsave(i)=toc; - data=loadjson(json); - tload(i)=toc-tsave(i); - fprintf(1,'matrix size: %d\n',datalen(i)); -end - -loglog(datalen,tsave,'o-',datalen,tload,'r*-'); -legend('savejson runtime (s)','loadjson runtime (s)'); -xlabel('array size'); -ylabel('running time (s)'); diff --git a/cb-tools/jsonlab/jsonopt.m b/cb-tools/jsonlab/jsonopt.m deleted file mode 100644 index 4b541d33..00000000 --- a/cb-tools/jsonlab/jsonopt.m +++ /dev/null @@ -1,32 +0,0 @@ -function val=jsonopt(key,default,varargin) -% -% val=jsonopt(key,default,optstruct) -% -% setting options based on a struct. The struct can be produced -% by varargin2struct from a list of 'param','value' pairs -% -% authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) -% -% $Id: loadjson.m 371 2012-06-20 12:43:06Z fangq $ -% -% input: -% key: a string with which one look up a value from a struct -% default: if the key does not exist, return default -% optstruct: a struct where each sub-field is a key -% -% output: -% val: if key exists, val=optstruct.key; otherwise val=default -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -val=default; -if(nargin<=2) return; end -opt=varargin{1}; -if(isstruct(opt) && isfield(opt,key)) - val=getfield(opt,key); -end - diff --git a/cb-tools/jsonlab/loadjson.m b/cb-tools/jsonlab/loadjson.m deleted file mode 100644 index 2c5d77db..00000000 --- a/cb-tools/jsonlab/loadjson.m +++ /dev/null @@ -1,520 +0,0 @@ -function data = loadjson(fname,varargin) -% -% data=loadjson(fname,opt) -% or -% data=loadjson(fname,'param1',value1,'param2',value2,...) -% -% parse a JSON (JavaScript Object Notation) file or string -% -% authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) -% date: 2011/09/09 -% Nedialko Krouchev: http://www.mathworks.com/matlabcentral/fileexchange/25713 -% date: 2009/11/02 -% Fran§ois Glineur: http://www.mathworks.com/matlabcentral/fileexchange/23393 -% date: 2009/03/22 -% Joel Feenstra: -% http://www.mathworks.com/matlabcentral/fileexchange/20565 -% date: 2008/07/03 -% -% $Id: loadjson.m 394 2012-12-18 17:58:11Z fangq $ -% -% input: -% fname: input file name, if fname contains "{}" or "[]", fname -% will be interpreted as a JSON string -% opt: a struct to store parsing options, opt can be replaced by -% a list of ('param',value) pairs. The param string is equivallent -% to a field in opt. -% -% output: -% dat: a cell array, where {...} blocks are converted into cell arrays, -% and [...] are converted to arrays -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -global pos inStr len esc index_esc len_esc isoct arraytoken - -if(regexp(fname,'[\{\}\]\[]','once')) - string=fname; -elseif(exist(fname,'file')) - fid = fopen(fname,'rt'); - string = fscanf(fid,'%c'); - fclose(fid); -else - error('input file does not exist'); -end - -pos = 1; len = length(string); inStr = string; -isoct=false;%this slows things down a lot! exist('OCTAVE_VERSION'); -arraytoken=find(inStr=='[' | inStr==']' | inStr=='"'); -jstr=regexprep(inStr,'\\\\',' '); -escquote=regexp(jstr,'\\"'); -arraytoken=sort([arraytoken escquote]); - -% String delimiters and escape chars identified to improve speed: -esc = find(inStr=='"' | inStr=='\' ); % comparable to: regexp(inStr, '["\\]'); -index_esc = 1; len_esc = length(esc); - -opt=varargin2struct(varargin{:}); -jsoncount=1; -while pos <= len - switch(next_char) - case '{' - data{jsoncount} = parse_object(opt); - case '[' - data{jsoncount} = parse_array(opt); - otherwise - error_pos('Outer level structure must be an object or an array'); - end - jsoncount=jsoncount+1; -end % while - -jsoncount=length(data); -if(jsoncount==1 && iscell(data)) - data=data{1}; -end - -if(~isempty(data)) - if(isstruct(data)) % data can be a struct array - data=jstruct2array(data); - elseif(iscell(data)) - data=jcell2array(data); - end -end - - -%% -function newdata=parse_collection(id,data,obj) - -if(jsoncount>0 && exist('data','var')) - if(~iscell(data)) - newdata=cell(1); - newdata{1}=data; - data=newdata; - end -end - -%% -function newdata=jcell2array(data) -len=length(data); -newdata=data; -for i=1:len - if(isstruct(data{i})) - newdata{i}=jstruct2array(data{i}); - elseif(iscell(data{i})) - newdata{i}=jcell2array(data{i}); - end -end - -%%------------------------------------------------------------------------- -function newdata=jstruct2array(data) -fn=fieldnames(data); -newdata=data; -len=length(data); -for i=1:length(fn) % depth-first - for j=1:len - if(isstruct(getfield(data(j),fn{i}))) - newdata(j)=setfield(newdata(j),fn{i},jstruct2array(getfield(data(j),fn{i}))); - end - end -end -if(~isempty(strmatch('x0x5F_ArrayType_',fn)) && ~isempty(strmatch('x0x5F_ArrayData_',fn))) - newdata=cell(len,1); - for j=1:len - ndata=cast(data(j).x0x5F_ArrayData_,data(j).x0x5F_ArrayType_); - iscpx=0; - if(~isempty(strmatch('x0x5F_ArrayIsComplex_',fn))) - if(data(j).x0x5F_ArrayIsComplex_) - iscpx=1; - end - end - if(~isempty(strmatch('x0x5F_ArrayIsSparse_',fn))) - if(data(j).x0x5F_ArrayIsSparse_) - if(~isempty(strmatch('x0x5F_ArraySize_',fn))) - dim=data(j).x0x5F_ArraySize_; - if(iscpx && size(ndata,2)==4-any(dim==1)) - ndata(:,end-1)=complex(ndata(:,end-1),ndata(:,end)); - end - if isempty(ndata) - % All-zeros sparse - ndata=sparse(dim(1),prod(dim(2:end))); - elseif dim(1)==1 - % Sparse row vector - ndata=sparse(1,ndata(:,1),ndata(:,2),dim(1),prod(dim(2:end))); - elseif dim(2)==1 - % Sparse column vector - ndata=sparse(ndata(:,1),1,ndata(:,2),dim(1),prod(dim(2:end))); - else - % Generic sparse array. - ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3),dim(1),prod(dim(2:end))); - end - else - if(iscpx && size(ndata,2)==4) - ndata(:,3)=complex(ndata(:,3),ndata(:,4)); - end - ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3)); - end - end - elseif(~isempty(strmatch('x0x5F_ArraySize_',fn))) - if(iscpx && size(ndata,2)==2) - ndata=complex(ndata(:,1),ndata(:,2)); - end - ndata=reshape(ndata(:),data(j).x0x5F_ArraySize_); - end - newdata{j}=ndata; - end - if(len==1) - newdata=newdata{1}; - end -end - -%%------------------------------------------------------------------------- -function object = parse_object(varargin) - parse_char('{'); - object = []; - if next_char ~= '}' - while 1 - str = parseStr(varargin{:}); - if isempty(str) - error_pos('Name of value at position %d cannot be empty'); - end - parse_char(':'); - val = parse_value(varargin{:}); - eval( sprintf( 'object.%s = val;', valid_field(str) ) ); - if next_char == '}' - break; - end - parse_char(','); - end - end - parse_char('}'); - -%%------------------------------------------------------------------------- - -function object = parse_array(varargin) % JSON array is written in row-major order -global pos inStr isoct - parse_char('['); - object = cell(0, 1); - dim2=[]; - if next_char ~= ']' - [endpos e1l e1r maxlevel]=matching_bracket(inStr,pos); - arraystr=['[' inStr(pos:endpos)]; - arraystr=regexprep(arraystr,'"_NaN_"','NaN'); - arraystr=regexprep(arraystr,'"([-+]*)_Inf_"','$1Inf'); - arraystr(find(arraystr==sprintf('\n')))=[]; - arraystr(find(arraystr==sprintf('\r')))=[]; - %arraystr=regexprep(arraystr,'\s*,',','); % this is slow,sometimes needed - if(~isempty(e1l) && ~isempty(e1r)) % the array is in 2D or higher D - astr=inStr((e1l+1):(e1r-1)); - astr=regexprep(astr,'"_NaN_"','NaN'); - astr=regexprep(astr,'"([-+]*)_Inf_"','$1Inf'); - astr(find(astr==sprintf('\n')))=[]; - astr(find(astr==sprintf('\r')))=[]; - astr(find(astr==' '))=''; - if(isempty(find(astr=='[', 1))) % array is 2D - dim2=length(sscanf(astr,'%f,',[1 inf])); - end - else % array is 1D - astr=arraystr(2:end-1); - astr(find(astr==' '))=''; - [obj count errmsg nextidx]=sscanf(astr,'%f,',[1,inf]); - if(nextidx>=length(astr)-1) - object=obj; - pos=endpos; - parse_char(']'); - return; - end - end - if(~isempty(dim2)) - astr=arraystr; - astr(find(astr=='['))=''; - astr(find(astr==']'))=''; - astr(find(astr==' '))=''; - [obj count errmsg nextidx]=sscanf(astr,'%f,',inf); - if(nextidx>=length(astr)-1) - object=reshape(obj,dim2,numel(obj)/dim2)'; - pos=endpos; - parse_char(']'); - return; - end - end - arraystr=regexprep(arraystr,'\]\s*,','];'); - try - if(isoct && regexp(arraystr,'"','once')) - error('Octave eval can produce empty cells for JSON-like input'); - end - object=eval(arraystr); - pos=endpos; - catch - while 1 - val = parse_value(varargin{:}); - object{end+1} = val; - if next_char == ']' - break; - end - parse_char(','); - end - end - end - % 2014-02-13 CB added check to not "simplify" cells of strings - if jsonopt('SimplifyCell',0,varargin{:}) == 1 && ~iscellstr(object) - try - oldobj=object; - object=cell2mat(object')'; - if(iscell(oldobj) && isstruct(object) && numel(object)>1 && jsonopt('SimplifyCellArray',1,varargin{:})==0) - object=oldobj; - elseif(size(object,1)>1 && ndims(object)==2) - object=object'; - end - catch - end - end - parse_char(']'); - -%%------------------------------------------------------------------------- - -function parse_char(c) - global pos inStr len - skip_whitespace; - if pos > len || inStr(pos) ~= c - error_pos(sprintf('Expected %c at position %%d', c)); - else - pos = pos + 1; - skip_whitespace; - end - -%%------------------------------------------------------------------------- - -function c = next_char - global pos inStr len - skip_whitespace; - if pos > len - c = []; - else - c = inStr(pos); - end - -%%------------------------------------------------------------------------- - -function skip_whitespace - global pos inStr len - while pos <= len && isspace(inStr(pos)) - pos = pos + 1; - end - -%%------------------------------------------------------------------------- -function str = parseStr(varargin) - global pos inStr len esc index_esc len_esc - % len, ns = length(inStr), keyboard - if inStr(pos) ~= '"' - error_pos('String starting with " expected at position %d'); - else - pos = pos + 1; - end - str = ''; - while pos <= len - while index_esc <= len_esc && esc(index_esc) < pos - index_esc = index_esc + 1; - end - if index_esc > len_esc - str = [str inStr(pos:len)]; - pos = len + 1; - break; - else - str = [str inStr(pos:esc(index_esc)-1)]; - pos = esc(index_esc); - end - nstr = length(str); switch inStr(pos) - case '"' - pos = pos + 1; - if(~isempty(str)) - if(strcmp(str,'_Inf_')) - str=Inf; - elseif(strcmp(str,'-_Inf_')) - str=-Inf; - elseif(strcmp(str,'_NaN_')) - str=NaN; - end - end - return; - case '\' - if pos+1 > len - error_pos('End of file reached right after escape character'); - end - pos = pos + 1; - switch inStr(pos) - case {'"' '\' '/'} - str(nstr+1) = inStr(pos); - pos = pos + 1; - case {'b' 'f' 'n' 'r' 't'} - str(nstr+1) = sprintf(['\' inStr(pos)]); - pos = pos + 1; - case 'u' - if pos+4 > len - error_pos('End of file reached in escaped unicode character'); - end - str(nstr+(1:6)) = inStr(pos-1:pos+4); - pos = pos + 5; - end - otherwise % should never happen - str(nstr+1) = inStr(pos), keyboard - pos = pos + 1; - end - end - error_pos('End of file while expecting end of inStr'); - -%%------------------------------------------------------------------------- - -function num = parse_number(varargin) - global pos inStr len isoct - currstr=inStr(pos:end); - numstr=0; - if(isoct~=0) - numstr=regexp(currstr,'^\s*-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?','end'); - [num, one] = sscanf(currstr, '%f', 1); - delta=numstr+1; - else - [num, one, err, delta] = sscanf(currstr, '%f', 1); - if ~isempty(err) - error_pos('Error reading number at position %d'); - end - end - pos = pos + delta-1; - -%%------------------------------------------------------------------------- - -function val = parse_value(varargin) - global pos inStr len - true = 1; false = 0; - - switch(inStr(pos)) - case '"' - val = parseStr(varargin{:}); - return; - case '[' - val = parse_array(varargin{:}); - return; - case '{' - val = parse_object(varargin{:}); - return; - case {'-','0','1','2','3','4','5','6','7','8','9'} - val = parse_number(varargin{:}); - return; - case 't' - if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'true') - val = true; - pos = pos + 4; - return; - end - case 'f' - if pos+4 <= len && strcmpi(inStr(pos:pos+4), 'false') - val = false; - pos = pos + 5; - return; - end - case 'n' - if pos+3 <= len && strcmpi(inStr(pos:pos+3), 'null') - val = []; - pos = pos + 4; - return; - end - end - error_pos('Value expected at position %d'); -%%------------------------------------------------------------------------- - -function error_pos(msg) - global pos inStr len - poShow = max(min([pos-15 pos-1 pos pos+20],len),1); - if poShow(3) == poShow(2) - poShow(3:4) = poShow(2)+[0 -1]; % display nothing after - end - msg = [sprintf(msg, pos) ': ' ... - inStr(poShow(1):poShow(2)) '' inStr(poShow(3):poShow(4)) ]; - error( ['JSONparser:invalidFormat: ' msg] ); - -%%------------------------------------------------------------------------- - -function str = valid_field(str) -global isoct -% From MATLAB doc: field names must begin with a letter, which may be -% followed by any combination of letters, digits, and underscores. -% Invalid characters will be converted to underscores, and the prefix -% "x0x[Hex code]_" will be added if the first character is not a letter. - pos=regexp(str,'^[^A-Za-z]','once'); - if(~isempty(pos)) - if(~isoct) - str=regexprep(str,'^([^A-Za-z])','x0x${sprintf(''%X'',unicode2native($1))}_','once'); - else - str=sprintf('x0x%X_%s',char(str(1)),str(2:end)); - end - end - if(isempty(regexp(str,'[^0-9A-Za-z_]', 'once' ))) return; end - if(~isoct) - str=regexprep(str,'([^0-9A-Za-z_])','_0x${sprintf(''%X'',unicode2native($1))}_'); - else - pos=regexp(str,'[^0-9A-Za-z_]'); - if(isempty(pos)) return; end - str0=str; - pos0=[0 pos(:)' length(str)]; - str=''; - for i=1:length(pos) - str=[str str0(pos0(i)+1:pos(i)-1) sprintf('_0x%X_',str0(pos(i)))]; - end - if(pos(end)~=length(str)) - str=[str str0(pos0(end-1)+1:pos0(end))]; - end - end - %str(~isletter(str) & ~('0' <= str & str <= '9')) = '_'; - -%%------------------------------------------------------------------------- -function endpos = matching_quote(str,pos) -len=length(str); -while(pos1 && str(pos-1)=='\')) - endpos=pos; - return; - end - end - pos=pos+1; -end -error('unmatched quotation mark'); -%%------------------------------------------------------------------------- -function [endpos e1l e1r maxlevel] = matching_bracket(str,pos) -global arraytoken -level=1; -maxlevel=level; -endpos=0; -bpos=arraytoken(arraytoken>=pos); -tokens=str(bpos); -len=length(tokens); -pos=1; -e1l=[]; -e1r=[]; -while(pos<=len) - c=tokens(pos); - if(c==']') - level=level-1; - if(isempty(e1r)) e1r=bpos(pos); end - if(level==0) - endpos=bpos(pos); - return - end - end - if(c=='[') - if(isempty(e1l)) e1l=bpos(pos); end - level=level+1; - maxlevel=max(maxlevel,level); - end - if(c=='"') - pos=matching_quote(tokens,pos+1); - end - pos=pos+1; -end -if(endpos==0) - error('unmatched "]"'); -end - diff --git a/cb-tools/jsonlab/loadubjson.m b/cb-tools/jsonlab/loadubjson.m deleted file mode 100644 index 5ea53461..00000000 --- a/cb-tools/jsonlab/loadubjson.m +++ /dev/null @@ -1,478 +0,0 @@ -function data = loadubjson(fname,varargin) -% -% data=loadubjson(fname,opt) -% or -% data=loadubjson(fname,'param1',value1,'param2',value2,...) -% -% parse a JSON (JavaScript Object Notation) file or string -% -% authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) -% date: 2013/08/01 -% -% $Id: loadubjson.m 410 2013-08-24 03:33:18Z fangq $ -% -% input: -% fname: input file name, if fname contains "{}" or "[]", fname -% will be interpreted as a UBJSON string -% opt: a struct to store parsing options, opt can be replaced by -% a list of ('param',value) pairs. The param string is equivallent -% to a field in opt. -% -% output: -% dat: a cell array, where {...} blocks are converted into cell arrays, -% and [...] are converted to arrays -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -global pos inStr len esc index_esc len_esc isoct arraytoken - -if(regexp(fname,'[\{\}\]\[]','once')) - string=fname; -elseif(exist(fname,'file')) - fid = fopen(fname,'rt'); - string = fscanf(fid,'%c'); - fclose(fid); -else - error('input file does not exist'); -end - -pos = 1; len = length(string); inStr = string; -isoct=exist('OCTAVE_VERSION'); -arraytoken=find(inStr=='[' | inStr==']' | inStr=='"'); -jstr=regexprep(inStr,'\\\\',' '); -escquote=regexp(jstr,'\\"'); -arraytoken=sort([arraytoken escquote]); - -% String delimiters and escape chars identified to improve speed: -esc = find(inStr=='"' | inStr=='\' ); % comparable to: regexp(inStr, '["\\]'); -index_esc = 1; len_esc = length(esc); - -opt=varargin2struct(varargin{:}); -jsoncount=1; -while pos <= len - switch(next_char) - case '{' - data{jsoncount} = parse_object(opt); - case '[' - data{jsoncount} = parse_array(opt); - otherwise - error_pos('Outer level structure must be an object or an array'); - end - jsoncount=jsoncount+1; -end % while - -jsoncount=length(data); -if(jsoncount==1 && iscell(data)) - data=data{1}; -end - -if(~isempty(data)) - if(isstruct(data)) % data can be a struct array - data=jstruct2array(data); - elseif(iscell(data)) - data=jcell2array(data); - end -end - - -%% -function newdata=parse_collection(id,data,obj) - -if(jsoncount>0 && exist('data','var')) - if(~iscell(data)) - newdata=cell(1); - newdata{1}=data; - data=newdata; - end -end - -%% -function newdata=jcell2array(data) -len=length(data); -newdata=data; -for i=1:len - if(isstruct(data{i})) - newdata{i}=jstruct2array(data{i}); - elseif(iscell(data{i})) - newdata{i}=jcell2array(data{i}); - end -end - -%%------------------------------------------------------------------------- -function newdata=jstruct2array(data) -fn=fieldnames(data); -newdata=data; -len=length(data); -for i=1:length(fn) % depth-first - for j=1:len - if(isstruct(getfield(data(j),fn{i}))) - newdata(j)=setfield(newdata(j),fn{i},jstruct2array(getfield(data(j),fn{i}))); - end - end -end -if(~isempty(strmatch('x0x5F_ArrayType_',fn)) && ~isempty(strmatch('x0x5F_ArrayData_',fn))) - newdata=cell(len,1); - for j=1:len - ndata=cast(data(j).x0x5F_ArrayData_,data(j).x0x5F_ArrayType_); - iscpx=0; - if(~isempty(strmatch('x0x5F_ArrayIsComplex_',fn))) - if(data(j).x0x5F_ArrayIsComplex_) - iscpx=1; - end - end - if(~isempty(strmatch('x0x5F_ArrayIsSparse_',fn))) - if(data(j).x0x5F_ArrayIsSparse_) - if(~isempty(strmatch('x0x5F_ArraySize_',fn))) - dim=double(data(j).x0x5F_ArraySize_); - if(iscpx && size(ndata,2)==4-any(dim==1)) - ndata(:,end-1)=complex(ndata(:,end-1),ndata(:,end)); - end - if isempty(ndata) - % All-zeros sparse - ndata=sparse(dim(1),prod(dim(2:end))); - elseif dim(1)==1 - % Sparse row vector - ndata=sparse(1,ndata(:,1),ndata(:,2),dim(1),prod(dim(2:end))); - elseif dim(2)==1 - % Sparse column vector - ndata=sparse(ndata(:,1),1,ndata(:,2),dim(1),prod(dim(2:end))); - else - % Generic sparse array. - ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3),dim(1),prod(dim(2:end))); - end - else - if(iscpx && size(ndata,2)==4) - ndata(:,3)=complex(ndata(:,3),ndata(:,4)); - end - ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3)); - end - end - elseif(~isempty(strmatch('x0x5F_ArraySize_',fn))) - if(iscpx && size(ndata,2)==2) - ndata=complex(ndata(:,1),ndata(:,2)); - end - ndata=reshape(ndata(:),data(j).x0x5F_ArraySize_); - end - newdata{j}=ndata; - end - if(len==1) - newdata=newdata{1}; - end -end - -%%------------------------------------------------------------------------- -function object = parse_object(varargin) - parse_char('{'); - object = []; - if next_char ~= '}' - while 1 - str = parseStr(varargin{:}); - if isempty(str) - error_pos('Name of value at position %d cannot be empty'); - end - %parse_char(':'); - val = parse_value(varargin{:}); - eval( sprintf( 'object.%s = val;', valid_field(str) ) ); - if next_char == '}' - break; - end - %parse_char(','); - end - end - parse_char('}'); - -%%------------------------------------------------------------------------- -function [cid,len]=elem_info(type) -id=strfind('iUIlLdD',type); -dataclass={'int8','uint8','int16','int32','int64','single','double'}; -bytelen=[1,1,2,4,8,4,8]; -if(id>0) - cid=dataclass{id}; - len=bytelen(id); -else - error_pos('unsupported type at position %d'); -end -%%------------------------------------------------------------------------- - - -function [data adv]=parse_block(type,count,varargin) -global pos inStr isoct -[cid,len]=elem_info(type); -if(isoct) - data=typecast(int8(inStr(pos:pos+len*count-1)),cid); -else - data=typecast(uint8(inStr(pos:pos+len*count-1)),cid); -end -adv=double(len*count); - -%%------------------------------------------------------------------------- - - -function object = parse_array(varargin) % JSON array is written in row-major order -global pos inStr isoct - parse_char('['); - object = cell(0, 1); - dim=[]; - type=''; - count=-1; - if(next_char == '$') - type=inStr(pos+1); - pos=pos+2; - end - if(next_char == '#') - pos=pos+1; - if(next_char=='[') - dim=parse_array(varargin{:}); - count=prod(double(dim)); - else - count=double(parse_number()); - end - end - if(~isempty(type)) - if(count>=0) - [object adv]=parse_block(type,count,varargin{:}); - if(~isempty(dim)) - object=reshape(object,dim); - end - pos=pos+adv; - return; - else - endpos=matching_bracket(inStr,pos); - [cid,len]=elem_info(type); - count=(endpos-pos)/len; - [object adv]=parse_block(type,count,varargin{:}); - pos=pos+adv; - parse_char(']'); - return; - end - end - - if next_char ~= ']' - while 1 - val = parse_value(varargin{:}); - object{end+1} = val; - if next_char == ']' - break; - end - %parse_char(','); - end - end - if(jsonopt('SimplifyCell',0,varargin{:})==1) - try - oldobj=object; - object=cell2mat(object')'; - if(iscell(oldobj) && isstruct(object) && numel(object)>1 && jsonopt('SimplifyCellArray',1,varargin{:})==0) - object=oldobj; - elseif(size(object,1)>1 && ndims(object)==2) - object=object'; - end - catch - end - end - if(count==-1) - parse_char(']'); - end - -%%------------------------------------------------------------------------- - -function parse_char(c) - global pos inStr len - skip_whitespace; - if pos > len || inStr(pos) ~= c - error_pos(sprintf('Expected %c at position %%d', c)); - else - pos = pos + 1; - skip_whitespace; - end - -%%------------------------------------------------------------------------- - -function c = next_char - global pos inStr len - skip_whitespace; - if pos > len - c = []; - else - c = inStr(pos); - end - -%%------------------------------------------------------------------------- - -function skip_whitespace - global pos inStr len - while pos <= len && isspace(inStr(pos)) - pos = pos + 1; - end - -%%------------------------------------------------------------------------- -function str = parseStr(varargin) - global pos inStr esc index_esc len_esc - % len, ns = length(inStr), keyboard - type=inStr(pos); - if type ~= 'S' && type ~= 'C' - error_pos('String starting with S expected at position %d'); - else - pos = pos + 1; - end - if(type == 'C') - str=inStr(pos); - pos=pos+1; - return; - end - bytelen=double(parse_number()); - if(length(inStr)>=pos+bytelen-1) - str=inStr(pos:pos+bytelen-1); - pos=pos+bytelen; - else - error_pos('End of file while expecting end of inStr'); - end - -%%------------------------------------------------------------------------- - -function num = parse_number(varargin) - global pos inStr len isoct - id=strfind('iUIlLdD',inStr(pos)); - if(isempty(id)) - error_pos('expecting a number at position %d'); - end - type={'int8','uint8','int16','int32','int64','single','double'}; - bytelen=[1,1,2,4,8,4,8]; - if(isoct) - num=typecast(int8(inStr(pos+1:pos+bytelen(id))),type{id}); - else - num=typecast(uint8(inStr(pos+1:pos+bytelen(id))),type{id}); - end - pos = pos + bytelen(id)+1; - -%%------------------------------------------------------------------------- - -function val = parse_value(varargin) - global pos inStr len - true = 1; false = 0; - - switch(inStr(pos)) - case {'S','C'} - val = parseStr(varargin{:}); - return; - case '[' - val = parse_array(varargin{:}); - return; - case '{' - val = parse_object(varargin{:}); - return; - case {'i','U','I','l','L','d','D'} - val = parse_number(varargin{:}); - return; - case 'T' - val = true; - pos = pos + 1; - return; - case 'F' - val = false; - pos = pos + 1; - return; - case {'Z','N'} - val = []; - pos = pos + 1; - return; - end - error_pos('Value expected at position %d'); -%%------------------------------------------------------------------------- - -function error_pos(msg) - global pos inStr len - poShow = max(min([pos-15 pos-1 pos pos+20],len),1); - if poShow(3) == poShow(2) - poShow(3:4) = poShow(2)+[0 -1]; % display nothing after - end - msg = [sprintf(msg, pos) ': ' ... - inStr(poShow(1):poShow(2)) '' inStr(poShow(3):poShow(4)) ]; - error( ['JSONparser:invalidFormat: ' msg] ); - -%%------------------------------------------------------------------------- - -function str = valid_field(str) -global isoct -% From MATLAB doc: field names must begin with a letter, which may be -% followed by any combination of letters, digits, and underscores. -% Invalid characters will be converted to underscores, and the prefix -% "x0x[Hex code]_" will be added if the first character is not a letter. - pos=regexp(str,'^[^A-Za-z]','once'); - if(~isempty(pos)) - if(~isoct) - str=regexprep(str,'^([^A-Za-z])','x0x${sprintf(''%X'',unicode2native($1))}_','once'); - else - str=sprintf('x0x%X_%s',char(str(1)),str(2:end)); - end - end - if(isempty(regexp(str,'[^0-9A-Za-z_]', 'once' ))) return; end - if(~isoct) - str=regexprep(str,'([^0-9A-Za-z_])','_0x${sprintf(''%X'',unicode2native($1))}_'); - else - pos=regexp(str,'[^0-9A-Za-z_]'); - if(isempty(pos)) return; end - str0=str; - pos0=[0 pos(:)' length(str)]; - str=''; - for i=1:length(pos) - str=[str str0(pos0(i)+1:pos(i)-1) sprintf('_0x%X_',str0(pos(i)))]; - end - if(pos(end)~=length(str)) - str=[str str0(pos0(end-1)+1:pos0(end))]; - end - end - %str(~isletter(str) & ~('0' <= str & str <= '9')) = '_'; - -%%------------------------------------------------------------------------- -function endpos = matching_quote(str,pos) -len=length(str); -while(pos1 && str(pos-1)=='\')) - endpos=pos; - return; - end - end - pos=pos+1; -end -error('unmatched quotation mark'); -%%------------------------------------------------------------------------- -function [endpos e1l e1r maxlevel] = matching_bracket(str,pos) -global arraytoken -level=1; -maxlevel=level; -endpos=0; -bpos=arraytoken(arraytoken>=pos); -tokens=str(bpos); -len=length(tokens); -pos=1; -e1l=[]; -e1r=[]; -while(pos<=len) - c=tokens(pos); - if(c==']') - level=level-1; - if(isempty(e1r)) e1r=bpos(pos); end - if(level==0) - endpos=bpos(pos); - return - end - end - if(c=='[') - if(isempty(e1l)) e1l=bpos(pos); end - level=level+1; - maxlevel=max(maxlevel,level); - end - if(c=='"') - pos=matching_quote(tokens,pos+1); - end - pos=pos+1; -end -if(endpos==0) - error('unmatched "]"'); -end - diff --git a/cb-tools/jsonlab/mergestruct.m b/cb-tools/jsonlab/mergestruct.m deleted file mode 100644 index ce344ad2..00000000 --- a/cb-tools/jsonlab/mergestruct.m +++ /dev/null @@ -1,33 +0,0 @@ -function s=mergestruct(s1,s2) -% -% s=mergestruct(s1,s2) -% -% merge two struct objects into one -% -% authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) -% date: 2012/12/22 -% -% input: -% s1,s2: a struct object, s1 and s2 can not be arrays -% -% output: -% s: the merged struct object. fields in s1 and s2 will be combined in s. -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -if(~isstruct(s1) || ~isstruct(s2)) - error('input parameters contain non-struct'); -end -if(length(s1)>1 || length(s2)>1) - error('can not merge struct arrays'); -end -fn=fieldnames(s2); -s=s1; -for i=1:length(fn) - s=setfield(s,fn{i},getfield(s2,fn{i})); -end - diff --git a/cb-tools/jsonlab/origsavejson.m b/cb-tools/jsonlab/origsavejson.m deleted file mode 100644 index a4860ee0..00000000 --- a/cb-tools/jsonlab/origsavejson.m +++ /dev/null @@ -1,386 +0,0 @@ -function json=savejson(rootname,obj,varargin) -% -% json=savejson(rootname,obj,filename) -% or -% json=savejson(rootname,obj,opt) -% json=savejson(rootname,obj,'param1',value1,'param2',value2,...) -% -% convert a MATLAB object (cell, struct or array) into a JSON (JavaScript -% Object Notation) string -% -% author: Qianqian Fang (fangq nmr.mgh.harvard.edu) -% created on 2011/09/09 -% -% $Id: savejson.m 394 2012-12-18 17:58:11Z fangq $ -% -% input: -% rootname: name of the root-object, if set to '', will use variable name -% obj: a MATLAB object (array, cell, cell array, struct, struct array) -% filename: a string for the file name to save the output JSON data -% opt: a struct for additional options, use [] if all use default -% opt can have the following fields (first in [.|.] is the default) -% -% opt.FileName [''|string]: a file name to save the output JSON data -% opt.FloatFormat ['%.10g'|string]: format to show each numeric element -% of a 1D/2D array; -% opt.ArrayIndent [1|0]: if 1, output explicit data array with -% precedent indentation; if 0, no indentation -% opt.ArrayToStruct[0|1]: when set to 0, savejson outputs 1D/2D -% array in JSON array format; if sets to 1, an -% array will be shown as a struct with fields -% "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for -% sparse arrays, the non-zero elements will be -% saved to _ArrayData_ field in triplet-format i.e. -% (ix,iy,val) and "_ArrayIsSparse_" will be added -% with a value of 1; for a complex array, the -% _ArrayData_ array will include two columns -% (4 for sparse) to record the real and imaginary -% parts, and also "_ArrayIsComplex_":1 is added. -% opt.ParseLogical [0|1]: if this is set to 1, logical array elem -% will use true/false rather than 1/0. -% opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single -% numerical element will be shown without a square -% bracket, unless it is the root object; if 0, square -% brackets are forced for any numerical arrays. -% opt.ForceRootName [0|1]: when set to 1 and rootname is empty, savejson -% will use the name of the passed obj variable as the -% root object name; if obj is an expression and -% does not have a name, 'root' will be used; if this -% is set to 0 and rootname is empty, the root level -% will be merged down to the lower level. -% opt.Inf ['"$1_Inf_"'|string]: a customized regular expression pattern -% to represent +/-Inf. The matched pattern is '([-+]*)Inf' -% and $1 represents the sign. For those who want to use -% 1e999 to represent Inf, they can set opt.Inf to '$11e999' -% opt.NaN ['"_NaN_"'|string]: a customized regular expression pattern -% to represent NaN -% opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), -% for example, if opt.JSON='foo', the JSON data is -% wrapped inside a function call as 'foo(...);' -% opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson -% back to the string form -% opt can be replaced by a list of ('param',value) pairs. The param -% string is equivallent to a field in opt. -% output: -% json: a string in the JSON format (see http://json.org) -% -% examples: -% a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... -% 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); -% savejson('mesh',a) -% savejson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g') -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -if(nargin==1) - varname=inputname(1); - obj=rootname; - if(isempty(varname)) - varname='root'; - end - rootname=varname; -else - varname=inputname(2); -end -if(length(varargin)==1 && ischar(varargin{1})) - opt=struct('FileName',varargin{1}); -else - opt=varargin2struct(varargin{:}); -end -opt.IsOctave=exist('OCTAVE_VERSION'); -rootisarray=0; -rootlevel=1; -forceroot=jsonopt('ForceRootName',0,opt); -if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0) - rootisarray=1; - rootlevel=0; -else - if(isempty(rootname)) - rootname=varname; - end -end -if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot) - rootname='root'; -end -json=obj2json(rootname,obj,rootlevel,opt); -if(rootisarray) - json=sprintf('%s\n',json); -else - json=sprintf('{\n%s\n}\n',json); -end - -jsonp=jsonopt('JSONP','',opt); -if(~isempty(jsonp)) - json=sprintf('%s(%s);\n',jsonp,json); -end - -% save to a file if FileName is set, suggested by Patrick Rapin -if(~isempty(jsonopt('FileName','',opt))) - fid = fopen(opt.FileName, 'wt'); - fwrite(fid,json,'char'); - fclose(fid); -end - -%%------------------------------------------------------------------------- -function txt=obj2json(name,item,level,varargin) - -if(iscell(item)) - txt=cell2json(name,item,level,varargin{:}); -elseif(isstruct(item)) - txt=struct2json(name,item,level,varargin{:}); -elseif(ischar(item)) - txt=str2json(name,item,level,varargin{:}); -else - txt=mat2json(name,item,level,varargin{:}); -end - -%%------------------------------------------------------------------------- -function txt=cell2json(name,item,level,varargin) -txt=''; -if(~iscell(item)) - error('input is not a cell'); -end - -dim=size(item); -len=numel(item); % let's handle 1D cell first -level -padding1=repmat(sprintf('\t'),1,level-1); -padding0=repmat(sprintf('\t'),1,level); -if(len>1) - if(~isempty(name)) - txt=sprintf('%s"%s": [\n',padding0, checkname(name,varargin{:})); name=''; - else - txt=sprintf('%s[\n',padding0); - end -elseif(len==0) - if(~isempty(name)) - txt=sprintf('%s"%s": null',padding0, checkname(name,varargin{:})); name=''; - else - txt=sprintf('%snull',padding0); - end -end -for i=1:len - txt=sprintf('%s%s%s',txt,padding1,obj2json(name,item{i},level+(len>1),varargin{:})); - if(i1) txt=sprintf('%s\n%s]',txt,padding0); end - -%%------------------------------------------------------------------------- -function txt=struct2json(name,item,level,varargin) -txt=''; -if(~isstruct(item)) - error('input is not a struct'); -end -len=numel(item); -padding1=repmat(sprintf('\t'),1,level-1); -padding0=repmat(sprintf('\t'),1,level); -sep=','; - -if(~isempty(name)) - if(len>1) txt=sprintf('%s"%s": [\n',padding0,checkname(name,varargin{:})); end -else - if(len>1) txt=sprintf('%s[\n',padding0); end -end -for e=1:len - names = fieldnames(item(e)); - if(~isempty(name) && len==1) - txt=sprintf('%s%s"%s": {\n',txt,repmat(sprintf('\t'),1,level+(len>1)), checkname(name,varargin{:})); - else - txt=sprintf('%s%s{\n',txt,repmat(sprintf('\t'),1,level+(len>1))); - end - if(~isempty(names)) - for i=1:length(names) - txt=sprintf('%s%s',txt,obj2json(names{i},getfield(item(e),... - names{i}),level+1+(len>1),varargin{:})); - if(i1))); - if(e==len) sep=''; end - if(e1) txt=sprintf('%s\n%s]',txt,padding0); end - -%%------------------------------------------------------------------------- -function txt=str2json(name,item,level,varargin) -txt=''; -if(~ischar(item)) - error('input is not a string'); -end -item=reshape(item, max(size(item),[1 0])); -len=size(item,1); -sep=sprintf(',\n'); - -padding1=repmat(sprintf('\t'),1,level); -padding0=repmat(sprintf('\t'),1,level+1); - -if(~isempty(name)) - if(len>1) txt=sprintf('%s"%s": [\n',padding1,checkname(name,varargin{:})); end -else - if(len>1) txt=sprintf('%s[\n',padding1); end -end -isoct=jsonopt('IsOctave',0,varargin{:}); -for e=1:len - if(isoct) - val=regexprep(item(e,:),'\\','\\'); - val=regexprep(val,'"','\"'); - val=regexprep(val,'^"','\"'); - else - val=regexprep(item(e,:),'\\','\\\\'); - val=regexprep(val,'"','\\"'); - val=regexprep(val,'^"','\\"'); - end - if(len==1) - obj=['"' checkname(name,varargin{:}) '": ' '"',val,'"']; - if(isempty(name)) obj=['"',val,'"']; end - txt=sprintf('%s%s%s%s',txt,repmat(sprintf('\t'),1,level),obj); - else - txt=sprintf('%s%s%s%s',txt,repmat(sprintf('\t'),1,level+1),['"',val,'"']); - end - if(e==len) sep=''; end - txt=sprintf('%s%s',txt,sep); -end -if(len>1) txt=sprintf('%s\n%s%s',txt,padding1,']'); end - -%%------------------------------------------------------------------------- -function txt=mat2json(name,item,level,varargin) -if(~isnumeric(item) && ~islogical(item)) - error('input is not an array'); -end - -padding1=repmat(sprintf('\t'),1,level); -padding0=repmat(sprintf('\t'),1,level+1); - -if(length(size(item))>2 || issparse(item) || ~isreal(item) || ... - isempty(item) ||jsonopt('ArrayToStruct',0,varargin{:})) - if(isempty(name)) - txt=sprintf('%s{\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... - padding1,padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); - else - txt=sprintf('%s"%s": {\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... - padding1,checkname(name,varargin{:}),padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); - end -else - if(isempty(name)) - txt=sprintf('%s%s',padding1,matdata2json(item,level+1,varargin{:})); - else - if(numel(item)==1 && jsonopt('NoRowBracket',1,varargin{:})==1) - numtxt=regexprep(regexprep(matdata2json(item,level+1,varargin{:}),'^\[',''),']',''); - txt=sprintf('%s"%s": %s',padding1,checkname(name,varargin{:}),numtxt); - else - txt=sprintf('%s"%s": %s',padding1,checkname(name,varargin{:}),matdata2json(item,level+1,varargin{:})); - end - end - return; -end -dataformat='%s%s%s%s%s'; - -if(issparse(item)) - [ix,iy]=find(item); - data=full(item(find(item))); - if(~isreal(item)) - data=[real(data(:)),imag(data(:))]; - if(size(item,1)==1) - % Kludge to have data's 'transposedness' match item's. - % (Necessary for complex row vector handling below.) - data=data'; - end - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); - end - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsSparse_": ','1', sprintf(',\n')); - if(size(item,1)==1) - % Row vector, store only column indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([iy(:),data'],level+2,varargin{:}), sprintf('\n')); - elseif(size(item,2)==1) - % Column vector, store only row indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([ix,data],level+2,varargin{:}), sprintf('\n')); - else - % General case, store row and column indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([ix,iy,data],level+2,varargin{:}), sprintf('\n')); - end -else - if(isreal(item)) - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json(item(:)',level+2,varargin{:}), sprintf('\n')); - else - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([real(item(:)) imag(item(:))],level+2,varargin{:}), sprintf('\n')); - end -end -txt=sprintf('%s%s%s',txt,padding1,'}'); - -%%------------------------------------------------------------------------- -function txt=matdata2json(mat,level,varargin) -if(size(mat,1)==1) - pre=''; - post=''; - level=level-1; -else - pre=sprintf('[\n'); - post=sprintf('\n%s]',repmat(sprintf('\t'),1,level-1)); -end -if(isempty(mat)) - txt='null'; - return; -end -floatformat=jsonopt('FloatFormat','%.10g',varargin{:}); -formatstr=['[' repmat([floatformat ','],1,size(mat,2)-1) [floatformat sprintf('],\n')]]; - -if(nargin>=2 && size(mat,1)>1 && jsonopt('ArrayIndent',1,varargin{:})==1) - formatstr=[repmat(sprintf('\t'),1,level) formatstr]; -end -txt=sprintf(formatstr,mat'); -txt(end-1:end)=[]; -if(islogical(mat) && jsonopt('ParseLogical',0,varargin{:})==1) - txt=regexprep(txt,'1','true'); - txt=regexprep(txt,'0','false'); -end -%txt=regexprep(mat2str(mat),'\s+',','); -%txt=regexprep(txt,';',sprintf('],\n[')); -% if(nargin>=2 && size(mat,1)>1) -% txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']); -% end -txt=[pre txt post]; -if(any(isinf(mat(:)))) - txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',varargin{:})); -end -if(any(isnan(mat(:)))) - txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',varargin{:})); -end - -%%------------------------------------------------------------------------- -function newname=checkname(name,varargin) -isunpack=jsonopt('UnpackHex',1,varargin{:}); -newname=name; -if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once'))) - return -end -if(isunpack) - isoct=jsonopt('IsOctave',0,varargin{:}); - if(~isoct) - newname=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}'); - else - pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start'); - pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end'); - if(isempty(pos)) return; end - str0=name; - pos0=[0 pend(:)' length(name)]; - newname=''; - for i=1:length(pos) - newname=[newname str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))]; - end - if(pos(end)~=length(name)) - newname=[newname str0(pos0(end-1)+1:pos0(end))]; - end - end -end - diff --git a/cb-tools/jsonlab/savejson.m b/cb-tools/jsonlab/savejson.m deleted file mode 100644 index 5fde6935..00000000 --- a/cb-tools/jsonlab/savejson.m +++ /dev/null @@ -1,435 +0,0 @@ -function json=savejson(rootname,obj,varargin) -% -% json=savejson(rootname,obj,filename) -% or -% json=savejson(rootname,obj,opt) -% json=savejson(rootname,obj,'param1',value1,'param2',value2,...) -% -% convert a MATLAB object (cell, struct or array) into a JSON (JavaScript -% Object Notation) string -% -% author: Qianqian Fang (fangq nmr.mgh.harvard.edu) -% created on 2011/09/09 -% -% $Id: savejson.m 394 2012-12-18 17:58:11Z fangq $ -% -% input: -% rootname: name of the root-object, if set to '', will use variable name -% obj: a MATLAB object (array, cell, cell array, struct, struct array) -% filename: a string for the file name to save the output JSON data -% opt: a struct for additional options, use [] if all use default -% opt can have the following fields (first in [.|.] is the default) -% -% opt.FileName [''|string]: a file name to save the output JSON data -% opt.FloatFormat ['%.10g'|string]: format to show each numeric element -% of a 1D/2D array; -% opt.ArrayIndent [1|0]: if 1, output explicit data array with -% precedent indentation; if 0, no indentation -% opt.ArrayToStruct[0|1]: when set to 0, savejson outputs 1D/2D -% array in JSON array format; if sets to 1, an -% array will be shown as a struct with fields -% "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for -% sparse arrays, the non-zero elements will be -% saved to _ArrayData_ field in triplet-format i.e. -% (ix,iy,val) and "_ArrayIsSparse_" will be added -% with a value of 1; for a complex array, the -% _ArrayData_ array will include two columns -% (4 for sparse) to record the real and imaginary -% parts, and also "_ArrayIsComplex_":1 is added. -% opt.ParseLogical [0|1]: if this is set to 1, logical array elem -% will use true/false rather than 1/0. -% opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single -% numerical element will be shown without a square -% bracket, unless it is the root object; if 0, square -% brackets are forced for any numerical arrays. -% opt.ForceRootName [0|1]: when set to 1 and rootname is empty, savejson -% will use the name of the passed obj variable as the -% root object name; if obj is an expression and -% does not have a name, 'root' will be used; if this -% is set to 0 and rootname is empty, the root level -% will be merged down to the lower level. -% opt.Inf ['"$1_Inf_"'|string]: a customized regular expression pattern -% to represent +/-Inf. The matched pattern is '([-+]*)Inf' -% and $1 represents the sign. For those who want to use -% 1e999 to represent Inf, they can set opt.Inf to '$11e999' -% opt.NaN ['"_NaN_"'|string]: a customized regular expression pattern -% to represent NaN -% opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), -% for example, if opt.JSON='foo', the JSON data is -% wrapped inside a function call as 'foo(...);' -% opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson -% back to the string form -% opt can be replaced by a list of ('param',value) pairs. The param -% string is equivallent to a field in opt. -% output: -% json: a string in the JSON format (see http://json.org) -% -% examples: -% a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... -% 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); -% savejson('mesh',a) -% savejson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g') -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -if(nargin==1) - varname=inputname(1); - obj=rootname; - if(isempty(varname)) - varname='root'; - end - rootname=varname; -else - varname=inputname(2); -end -if(length(varargin)==1 && ischar(varargin{1})) - opt=struct('FileName',varargin{1}); -else - opt=varargin2struct(varargin{:}); -end -opt.IsOctave=exist('OCTAVE_VERSION', 'builtin'); -rootisarray=0; -rootlevel=1; -forceroot=jsonopt('ForceRootName',0,opt); - -tab = sprintf('\t'); -newline = sprintf('\n'); -floatformat=jsonopt('FloatFormat','%.10g',opt); -arrayIndent=jsonopt('ArrayIndent',1,opt)==1; -parseLogical=jsonopt('ParseLogical',0,opt)==1; -noRowBracket=jsonopt('NoRowBracket',1,opt)==1; -arrayToStruct=jsonopt('ArrayToStruct',0,opt); -unpackHex=jsonopt('UnpackHex',1,opt); -jsonp=jsonopt('JSONP','',opt); - -% speedup? -maxlevel = 25; % maximum object nesting degree to save padding for -pad = cell(maxlevel+1,1); % idx 1 is zero level padding -for l = 1:maxlevel - pad{l+1} = [pad{l} tab]; -end - -if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0) - rootisarray=1; - rootlevel=0; -else - if(isempty(rootname)) - rootname=varname; - end -end -if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot) - rootname='root'; -end - -json=obj2json(checkname(rootname),obj,rootlevel); -if(rootisarray) - json=sprintf('%s\n',json); -else - json=sprintf('{\n%s\n}\n',json); -end -if(~isempty(jsonp)) - json=sprintf('%s(%s);\n',jsonp,json); -end -% save to a file if FileName is set, suggested by Patrick Rapin -if ~isempty(jsonopt('FileName','',opt)) - fid = fopen(opt.FileName, 'wt'); - fwrite(fid,json,'char'); - fclose(fid); -end - - %%------------------------------------------------------------------------- - function txt=obj2json(name,item,level) - - if(iscell(item)) - txt=cell2json(name,item,level,opt); - elseif(isstruct(item)) - txt=struct2json(name,item,level); - elseif(ischar(item)) - txt=str2json(name,item,level,opt); - else - txt=mat2json(name,item,level); - end - end - - %%------------------------------------------------------------------------- - function txt=cell2json(name,item,level,varargin) - %% Warning i need some work, and checking that loadjson mirrors this well - txt=''; - if(~iscell(item)) - error('input is not a cell'); - end - - len=numel(item); % let's handle 1D cell first -% padding1=pad{level};%repmat(sprintf('\t'),1,level-1); ??? - padding0=pad{level+1};%repmat(sprintf('\t'),1,level); - if (~isempty(name)) - txt=sprintf('%s"%s": [',padding0, name); name=''; - else - txt=sprintf('%s[',padding0); - end - if len==0 - txt = [txt ']']; - else - for i=1:len - txt=[txt newline obj2json(name,item{i},level+1)]; - if(i1); - names = fieldnames(item); - checkedNames = cellfun(@checkname, names, 'uni', false); - padding0=pad{level+1}; %padding for this level - padding1=pad{bracelevel+1}; %padding for braces, maybe deeper than this - values = struct2cell(item); % all structure values in a table - elemstxt = cell(size(item)); % array to store each elements object text - for e=1:len - elements = cell(1,numel(names)); - for f=1:numel(names) - elements{f} = obj2json(checkedNames{f},values{f,e},bracelevel+1); - end - elementstxt = sprintf('\n%s,', elements{:}); % format all fields together - elemstxt{e} = elementstxt(1:end-1); - end - linepad = [newline padding1]; - intxt = sprintf(['{%s\n' padding1 '},' linepad], elemstxt{:}); % format all elements - intxt = intxt(1:(end-numel(linepad)-1)); % snip off next element tokens/formatting - if(len==1) - if ~isempty(name) - txt=[padding0 '"' name '": ' intxt]; - else - txt=intxt; - end - else - txt=[padding0 '"' name '": [' linepad intxt newline padding0 ']']; - end - end - - %%------------------------------------------------------------------------- - function txt=str2json(name,item,level,varargin) - txt=''; - if(~ischar(item)) - error('input is not a string'); - end - item=reshape(item, max(size(item),[1 0])); - len=size(item,1); - sep=sprintf(',\n'); - - padding1=pad{level+1}; - - if(~isempty(name)) - if(len>1) txt=sprintf('%s"%s": [\n',padding1,name); end - else - if(len>1) txt=sprintf('%s[\n',padding1); end - end - isoct=jsonopt('IsOctave',0,varargin{:}); - for e=1:len - if(isoct) - val=regexprep(item(e,:),'\\','\\'); - val=regexprep(val,'"','\"'); - val=regexprep(val,'^"','\"'); - else - val=regexprep(item(e,:),'\\','\\\\'); - val=regexprep(val,'"','\\"'); - val=regexprep(val,'^"','\\"'); - end - if(len==1) - obj=['"' name '": ' '"',val,'"']; - if(isempty(name)) obj=['"',val,'"']; end - txt=sprintf('%s%s%s%s',txt,padding1,obj); - else - txt=[txt pad{level+2} '"',val,'"']; - end - if(e==len) sep=''; end - txt=sprintf('%s%s',txt,sep); - end - if(len>1) txt=sprintf('%s\n%s%s',txt,padding1,']'); end - end - - %%------------------------------------------------------------------------- - function txt=mat2json(name,item,level) - if(~isnumeric(item) && ~islogical(item)) - error('input is not an array'); - end - - padding1=pad{level+1};%repmat(tab,1,level); - padding0=pad{level+2};%repmat(tab,1,level+1); - - if(length(size(item))>2 || issparse(item) || ~isreal(item) || ... - isempty(item) || arrayToStruct) - if(isempty(name)) - txt=sprintf('%s{\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... - padding1,padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); - else - txt=sprintf('%s"%s": {\n%s"_ArrayType_": "%s",\n%s"_ArraySize_": %s,\n',... - padding1,name,padding0,class(item),padding0,regexprep(mat2str(size(item)),'\s+',',') ); - end - else - if(isempty(name)) - %% warning i may have broken this part - txt=sprintf('%s%s',padding1,matdata2json(item,level+1)); -% txt=sprintf('%s[%s]',padding1,matdata2json(item,level+1)); - else - txt = [padding1 '"' name '": ' matdata2json(item,level+1)]; -% if(numel(item)==1 && ~noRowBracket) -% %numtxt=regexprep(regexprep(numtxt,'^\[',''),']',''); -% %assume square brackets first + last chars -% numtxt = numtxt(2:end-1); -% end -% txt=[padding1 '"' name '": ' numtxt]; - end - return; - end - dataformat='%s%s%s%s%s'; - - if(issparse(item)) - [ix,iy]=find(item); - data=full(item(find(item))); - if(~isreal(item)) - data=[real(data(:)),imag(data(:))]; - if(size(item,1)==1) - % Kludge to have data's 'transposedness' match item's. - % (Necessary for complex row vector handling below.) - data=data'; - end - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); - end - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsSparse_": ','1', sprintf(',\n')); - if(size(item,1)==1) - % Row vector, store only column indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([iy(:),data'],level+2), sprintf('\n')); - elseif(size(item,2)==1) - % Column vector, store only row indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([ix,data],level+2), sprintf('\n')); - else - % General case, store row and column indices. - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([ix,iy,data],level+2), sprintf('\n')); - end - else - if(isreal(item)) - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json(item(:)',level+2), sprintf('\n')); - else - txt=sprintf(dataformat,txt,padding0,'"_ArrayIsComplex_": ','1', sprintf(',\n')); - txt=sprintf(dataformat,txt,padding0,'"_ArrayData_": ',... - matdata2json([real(item(:)) imag(item(:))],level+2), sprintf('\n')); - end - end - txt=sprintf('%s%s%s',txt,padding1,'}'); - end - - %%------------------------------------------------------------------------- - function txt=matdata2json(mat,level) - % handle each case individually for maximum speed - if numel(mat) == 1 - txt = sprintf(floatformat, mat); - if ~noRowBracket - txt = ['[' txt ']']; - end - elseif isempty(mat) - txt = 'null'; - elseif isrow(mat) - txt = sprintf([floatformat ','], mat); - txt = ['[' txt(1:end-1) ']']; - elseif iscolumn(mat) - txt = sprintf([newline pad{level+1} '[' floatformat '],'], mat); - txt = ['[' txt(1:end-1) newline pad{level} ']']; - else % matrix with rows>1 & cols>1 - formatstr=['[' repmat([floatformat ','],1,size(mat,2)-1) floatformat '],' newline]; - if arrayIndent - formatstr = [pad{level+1} formatstr]; - end - txt = sprintf(formatstr,mat'); - txt = ['[' newline txt(1:end-2) newline pad{level} ']']; - end -% if(size(mat,1)==1) -% pre=''; -% post=''; -% level=level-1; -% else -% pre=sprintf('[\n'); -% post=sprintf('\n%s]',pad{level}); -% end -% if(isempty(mat)) -% txt='null'; -% return; -% end -% -% formatstr=['[' repmat([floatformat ','],1,size(mat,2)-1) floatformat '],' newline]; -% -% if(nargin>=2 && size(mat,1)>1 && arrayIndent) -% formatstr=[pad{level+1} formatstr]; -% end -% % intxt=sprintf(formatstr,mat'); -% txt=sprintf(formatstr,mat'); - if(parseLogical && islogical(mat)) - txt=regexprep(txt,'1','true'); - txt=regexprep(txt,'0','false'); - end - if(any(isinf(mat(:)))) - txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',opt)); - end - if(any(isnan(mat(:)))) - txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',opt)); - end - end - - %%------------------------------------------------------------------------- - function name=checkname(name) - if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once'))) - return - elseif unpackHex - isoct=jsonopt('IsOctave',0,opt); - if(~isoct) - name=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}'); - else - pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start'); - pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end'); - if(isempty(pos)) return; end - str0=name; - pos0=[0 pend(:)' length(name)]; - name=''; - for i=1:length(pos) - name=[name str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))]; - end - if(pos(end)~=length(name)) - name=[name str0(pos0(end-1)+1:pos0(end))]; - end - end - end - end - -end - diff --git a/cb-tools/jsonlab/saveubjson.m b/cb-tools/jsonlab/saveubjson.m deleted file mode 100644 index eb975de0..00000000 --- a/cb-tools/jsonlab/saveubjson.m +++ /dev/null @@ -1,490 +0,0 @@ -function json=saveubjson(rootname,obj,varargin) -% -% json=saveubjson(rootname,obj,filename) -% or -% json=saveubjson(rootname,obj,opt) -% json=saveubjson(rootname,obj,'param1',value1,'param2',value2,...) -% -% convert a MATLAB object (cell, struct or array) into a Universal -% Binary JSON (UBJSON) binary string -% -% author: Qianqian Fang (fangq nmr.mgh.harvard.edu) -% created on 2013/08/17 -% -% $Id: saveubjson.m 410 2013-08-24 03:33:18Z fangq $ -% -% input: -% rootname: name of the root-object, if set to '', will use variable name -% obj: a MATLAB object (array, cell, cell array, struct, struct array) -% filename: a string for the file name to save the output JSON data -% opt: a struct for additional options, use [] if all use default -% opt can have the following fields (first in [.|.] is the default) -% -% opt.FileName [''|string]: a file name to save the output JSON data -% opt.ArrayToStruct[0|1]: when set to 0, saveubjson outputs 1D/2D -% array in JSON array format; if sets to 1, an -% array will be shown as a struct with fields -% "_ArrayType_", "_ArraySize_" and "_ArrayData_"; for -% sparse arrays, the non-zero elements will be -% saved to _ArrayData_ field in triplet-format i.e. -% (ix,iy,val) and "_ArrayIsSparse_" will be added -% with a value of 1; for a complex array, the -% _ArrayData_ array will include two columns -% (4 for sparse) to record the real and imaginary -% parts, and also "_ArrayIsComplex_":1 is added. -% opt.ParseLogical [1|0]: if this is set to 1, logical array elem -% will use true/false rather than 1/0. -% opt.NoRowBracket [1|0]: if this is set to 1, arrays with a single -% numerical element will be shown without a square -% bracket, unless it is the root object; if 0, square -% brackets are forced for any numerical arrays. -% opt.ForceRootName [0|1]: when set to 1 and rootname is empty, saveubjson -% will use the name of the passed obj variable as the -% root object name; if obj is an expression and -% does not have a name, 'root' will be used; if this -% is set to 0 and rootname is empty, the root level -% will be merged down to the lower level. -% opt.JSONP [''|string]: to generate a JSONP output (JSON with padding), -% for example, if opt.JSON='foo', the JSON data is -% wrapped inside a function call as 'foo(...);' -% opt.UnpackHex [1|0]: conver the 0x[hex code] output by loadjson -% back to the string form -% opt can be replaced by a list of ('param',value) pairs. The param -% string is equivallent to a field in opt. -% output: -% json: a string in the JSON format (see http://json.org) -% -% examples: -% a=struct('node',[1 9 10; 2 1 1.2], 'elem',[9 1;1 2;2 3],... -% 'face',[9 01 2; 1 2 3; NaN,Inf,-Inf], 'author','FangQ'); -% saveubjson('mesh',a) -% saveubjson('',a,'ArrayIndent',0,'FloatFormat','\t%.5g') -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -if(nargin==1) - varname=inputname(1); - obj=rootname; - if(isempty(varname)) - varname='root'; - end - rootname=varname; -else - varname=inputname(2); -end -if(length(varargin)==1 && ischar(varargin{1})) - opt=struct('FileName',varargin{1}); -else - opt=varargin2struct(varargin{:}); -end -opt.IsOctave=exist('OCTAVE_VERSION'); -rootisarray=0; -rootlevel=1; -forceroot=jsonopt('ForceRootName',0,opt); -if((isnumeric(obj) || islogical(obj) || ischar(obj) || isstruct(obj) || iscell(obj)) && isempty(rootname) && forceroot==0) - rootisarray=1; - rootlevel=0; -else - if(isempty(rootname)) - rootname=varname; - end -end -if((isstruct(obj) || iscell(obj))&& isempty(rootname) && forceroot) - rootname='root'; -end -json=obj2ubjson(rootname,obj,rootlevel,opt); -if(~rootisarray) - json=['{' json '}']; -end - -jsonp=jsonopt('JSONP','',opt); -if(~isempty(jsonp)) - json=[jsonp '(' json ')']; -end - -% save to a file if FileName is set, suggested by Patrick Rapin -if(~isempty(jsonopt('FileName','',opt))) - fid = fopen(opt.FileName, 'wt'); - fwrite(fid,json,'char'); - fclose(fid); -end - -%%------------------------------------------------------------------------- -function txt=obj2ubjson(name,item,level,varargin) - -if(iscell(item)) - txt=cell2ubjson(name,item,level,varargin{:}); -elseif(isstruct(item)) - txt=struct2ubjson(name,item,level,varargin{:}); -elseif(ischar(item)) - txt=str2ubjson(name,item,level,varargin{:}); -else - txt=mat2ubjson(name,item,level,varargin{:}); -end - -%%------------------------------------------------------------------------- -function txt=cell2ubjson(name,item,level,varargin) -txt=''; -if(~iscell(item)) - error('input is not a cell'); -end - -dim=size(item); -len=numel(item); % let's handle 1D cell first -padding1=''; -padding0=''; -if(len>1) - if(~isempty(name)) - txt=[S_(checkname(name,varargin{:})) '[']; name=''; - else - txt='['; - end -elseif(len==0) - if(~isempty(name)) - txt=[S_(checkname(name,varargin{:})) 'Z']; name=''; - else - txt='Z'; - end -end -for i=1:len - txt=[txt obj2ubjson(name,item{i},level+(len>1),varargin{:})]; -end -if(len>1) txt=[txt ']']; end - -%%------------------------------------------------------------------------- -function txt=struct2ubjson(name,item,level,varargin) -txt=''; -if(~isstruct(item)) - error('input is not a struct'); -end -len=numel(item); -padding1=''; -padding0=''; -sep=','; - -if(~isempty(name)) - if(len>1) txt=[S_(checkname(name,varargin{:})) '[']; end -else - if(len>1) txt='['; end -end -for e=1:len - names = fieldnames(item(e)); - if(~isempty(name) && len==1) - txt=[txt S_(checkname(name,varargin{:})) '{']; - else - txt=[txt '{']; - end - if(~isempty(names)) - for i=1:length(names) - txt=[txt obj2ubjson(names{i},getfield(item(e),... - names{i}),level+1+(len>1),varargin{:})]; - end - end - txt=[txt '}']; - if(e==len) sep=''; end -end -if(len>1) txt=[txt ']']; end - -%%------------------------------------------------------------------------- -function txt=str2ubjson(name,item,level,varargin) -txt=''; -if(~ischar(item)) - error('input is not a string'); -end -item=reshape(item, max(size(item),[1 0])); -len=size(item,1); -sep=''; - -padding1=''; -padding0=''; - -if(~isempty(name)) - if(len>1) txt=[S_(checkname(name,varargin{:})) '[']; end -else - if(len>1) txt='['; end -end -isoct=jsonopt('IsOctave',0,varargin{:}); -for e=1:len - val=item(e,:); - if(len==1) - obj=['' S_(checkname(name,varargin{:})) '' '',S_(val),'']; - if(isempty(name)) obj=['',S_(val),'']; end - txt=[txt,'',obj]; - else - txt=[txt,'',['',S_(val),'']]; - end - if(e==len) sep=''; end - txt=[txt sep]; -end -if(len>1) txt=[txt ']']; end - -%%------------------------------------------------------------------------- -function txt=mat2ubjson(name,item,level,varargin) -if(~isnumeric(item) && ~islogical(item)) - error('input is not an array'); -end - -padding1=''; -padding0=''; - -if(length(size(item))>2 || issparse(item) || ~isreal(item) || ... - isempty(item) || jsonopt('ArrayToStruct',0,varargin{:})) - cid=I_(uint32(max(size(item)))); - if(isempty(name)) - txt=['{' S_('_ArrayType_'),S_(class(item)),padding0,S_('_ArraySize_'),I_a(size(item),cid(1)) ]; - else - txt=[S_(checkname(name,varargin{:})),'{',S_('_ArrayType_'),S_(class(item)),padding0,S_('_ArraySize_'),I_a(size(item),cid(1))]; - end -else - if(isempty(name)) - txt=matdata2ubjson(item,level+1,varargin{:}); - else - if(numel(item)==1 && jsonopt('NoRowBracket',1,varargin{:})==1) - numtxt=regexprep(regexprep(matdata2ubjson(item,level+1,varargin{:}),'^\[',''),']',''); - txt=[S_(checkname(name,varargin{:})) numtxt]; - else - txt=[S_(checkname(name,varargin{:})),matdata2ubjson(item,level+1,varargin{:})]; - end - end - return; -end -dataformat='%s%s%s%s%s'; - -if(issparse(item)) - [ix,iy]=find(item); - data=full(item(find(item))); - if(~isreal(item)) - data=[real(data(:)),imag(data(:))]; - if(size(item,1)==1) - % Kludge to have data's 'transposedness' match item's. - % (Necessary for complex row vector handling below.) - data=data'; - end - txt=[txt,S_('_ArrayIsComplex_'),'T']; - end - txt=[txt,S_('_ArrayIsSparse_'),'T']; - if(size(item,1)==1) - % Row vector, store only column indices. - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson([iy(:),data'],level+2,varargin{:})]; - elseif(size(item,2)==1) - % Column vector, store only row indices. - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson([ix,data],level+2,varargin{:})]; - else - % General case, store row and column indices. - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson([ix,iy,data],level+2,varargin{:})]; - end -else - if(isreal(item)) - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson(item(:)',level+2,varargin{:})]; - else - txt=[txt,S_('_ArrayIsComplex_'),'T']; - txt=[txt,S_('_ArrayData_'),... - matdata2ubjson([real(item(:)) imag(item(:))],level+2,varargin{:})]; - end -end -txt=[txt,'}']; - -%%------------------------------------------------------------------------- -function txt=matdata2ubjson(mat,level,varargin) -if(isempty(mat)) - txt='Z'; - return; -end -if(size(mat,1)==1) - level=level-1; -end -type=''; -hasnegtive=find(mat<0); -if(isa(mat,'integer') || (isfloat(mat) && all(mod(mat(:),1) == 0))) - if(isempty(hasnegtive)) - if(max(mat(:))<=2^8) - type='U'; - end - end - if(isempty(type)) - % todo - need to consider negative ones separately - id= histc(abs(max(mat(:))),[0 2^7 2^15 2^31 2^63]); - if(isempty(find(id))) - error('high-precision data is not yet supported'); - end - key='iIlL'; - type=key(find(id)); - end - txt=[I_a(mat(:),type,size(mat))]; -elseif(islogical(mat)) - logicalval='FT'; - if(numel(mat)==1) - txt=logicalval(mat+1); - else - txt=['[$U#' I_a(size(mat),'l') typecast(uint8(mat(:)'),'uint8')]; - end -else - if(numel(mat)==1) - txt=['[' D_(mat) ']']; - else - txt=D_a(mat(:),'D',size(mat)); - end -end - -%txt=regexprep(mat2str(mat),'\s+',','); -%txt=regexprep(txt,';',sprintf('],[')); -% if(nargin>=2 && size(mat,1)>1) -% txt=regexprep(txt,'\[',[repmat(sprintf('\t'),1,level) '[']); -% end -if(any(isinf(mat(:)))) - txt=regexprep(txt,'([-+]*)Inf',jsonopt('Inf','"$1_Inf_"',varargin{:})); -end -if(any(isnan(mat(:)))) - txt=regexprep(txt,'NaN',jsonopt('NaN','"_NaN_"',varargin{:})); -end - -%%------------------------------------------------------------------------- -function newname=checkname(name,varargin) -isunpack=jsonopt('UnpackHex',1,varargin{:}); -newname=name; -if(isempty(regexp(name,'0x([0-9a-fA-F]+)_','once'))) - return -end -if(isunpack) - isoct=jsonopt('IsOctave',0,varargin{:}); - if(~isoct) - newname=regexprep(name,'(^x|_){1}0x([0-9a-fA-F]+)_','${native2unicode(hex2dec($2))}'); - else - pos=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','start'); - pend=regexp(name,'(^x|_){1}0x([0-9a-fA-F]+)_','end'); - if(isempty(pos)) return; end - str0=name; - pos0=[0 pend(:)' length(name)]; - newname=''; - for i=1:length(pos) - newname=[newname str0(pos0(i)+1:pos(i)-1) char(hex2dec(str0(pos(i)+3:pend(i)-1)))]; - end - if(pos(end)~=length(name)) - newname=[newname str0(pos0(end-1)+1:pos0(end))]; - end - end -end -%%------------------------------------------------------------------------- -function val=S_(str) -if(length(str)==1) - val=['C' str]; -else - val=['S' I_(int32(length(str))) str]; -end -%%------------------------------------------------------------------------- -function val=I_(num) -if(~isinteger(num)) - error('input is not an integer'); -end -if(num>=0 && num<255) - val=['U' data2byte(cast(num,'uint8'),'uint8')]; - return; -end -key='iIlL'; -cid={'int8','int16','int32','int64'}; -for i=1:4 - if((num>0 && num<2^(i*8-1)) || (num<0 && num>=-2^(i*8-1))) - val=[key(i) data2byte(cast(num,cid{i}),'uint8')]; - return; - end -end -error('unsupported integer'); - -%%------------------------------------------------------------------------- -function val=D_(num) -if(~isfloat(num)) - error('input is not a float'); -end - -if(isa(num,'single')) - val=['d' data2byte(num,'uint8')]; -else - val=['D' data2byte(num,'uint8')]; -end -%%------------------------------------------------------------------------- -function data=I_a(num,type,dim,format) -id=find(ismember('iUIlL',type)); - -if(id==0) - error('unsupported integer array'); -end - -if(id==1) - data=data2byte(int8(num),'uint8'); - blen=1; -elseif(id==2) - data=data2byte(uint8(num),'uint8'); - blen=1; -elseif(id==3) - data=data2byte(int16(num),'uint8'); - blen=2; -elseif(id==4) - data=data2byte(int32(num),'uint8'); - blen=4; -elseif(id==5) - data=data2byte(int64(num),'uint8'); - blen=8; -end - -if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2)) - format='opt'; -end -if((nargin<4 || strcmp(format,'opt')) && numel(num)>1) - if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2)))) - cid=I_(uint32(max(dim))); - data=['$' type '#' I_a(dim,cid(1)) data(:)']; - else - data=['$' type '#' I_(int32(numel(data)/blen)) data(:)']; - end - data=['[' data(:)']; -else - data=reshape(data,blen,numel(data)/blen); - data(2:blen+1,:)=data; - data(1,:)=type; - data=data(:)'; - data=['[' data(:)' ']']; -end -%%------------------------------------------------------------------------- -function data=D_a(num,type,dim,format) -id=find(ismember('dD',type)); - -if(id==0) - error('unsupported float array'); -end - -if(id==1) - data=data2byte(single(num),'uint8'); -elseif(id==2) - data=data2byte(double(num),'uint8'); -end - -if(nargin>=3 && length(dim)>=2 && prod(dim)~=dim(2)) - format='opt'; -end -if((nargin<4 || strcmp(format,'opt')) && numel(num)>1) - if(nargin>=3 && (length(dim)==1 || (length(dim)>=2 && prod(dim)~=dim(2)))) - cid=I_(uint32(max(dim))); - data=['$' type '#' I_a(dim,cid(1)) data(:)']; - else - data=['$' type '#' I_(int32(numel(data)/(id*4))) data(:)']; - end - data=['[' data]; -else - data=reshape(data,(id*4),length(data)/(id*4)); - data(2:(id*4+1),:)=data; - data(1,:)=type; - data=data(:)'; - data=['[' data(:)' ']']; -end -%%------------------------------------------------------------------------- -function bytes=data2byte(varargin) -bytes=typecast(varargin{:}); -bytes=bytes(:)'; diff --git a/cb-tools/jsonlab/varargin2struct.m b/cb-tools/jsonlab/varargin2struct.m deleted file mode 100644 index 43c27af4..00000000 --- a/cb-tools/jsonlab/varargin2struct.m +++ /dev/null @@ -1,40 +0,0 @@ -function opt=varargin2struct(varargin) -% -% opt=varargin2struct('param1',value1,'param2',value2,...) -% or -% opt=varargin2struct(...,optstruct,...) -% -% convert a series of input parameters into a structure -% -% authors:Qianqian Fang (fangq nmr.mgh.harvard.edu) -% date: 2012/12/22 -% -% input: -% 'param', value: the input parameters should be pairs of a string and a value -% optstruct: if a parameter is a struct, the fields will be merged to the output struct -% -% output: -% opt: a struct where opt.param1=value1, opt.param2=value2 ... -% -% license: -% BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details -% -% -- this function is part of jsonlab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab) -% - -len=length(varargin); -opt=struct; -if(len==0) return; end -i=1; -while(i<=len) - if(isstruct(varargin{i})) - opt=mergestruct(opt,varargin{i}); - elseif(ischar(varargin{i}) && i cmd=search&db=pubmed&term=wtf+batman -% -% IMPORTANT: This function does not filter parameters, sort them, -% or remove empty inputs (if necessary), this must be done before hand - -if ~exist('encodeOption','var') - encodeOption = 1; -end - -if size(params,2) == 2 && size(params,1) > 1 - params = params'; - params = params(:); -end - -str = ''; -for i=1:2:length(params) - if (i == 1), separator = ''; else separator = '&'; end - switch encodeOption - case 1 - param = urlencode(params{i}); - value = urlencode(params{i+1}); -% case 2 -% param = oauth.percentEncodeString(params{i}); -% value = oauth.percentEncodeString(params{i+1}); -% header = http_getContentTypeHeader(1); - otherwise - error('Case not used') - end - str = [str separator param '=' value]; %#ok -end - -switch encodeOption - case 1 - header = http_createHeader('Content-Type','application/x-www-form-urlencoded'); -end - - -end \ No newline at end of file diff --git a/cb-tools/urlread2/license.txt b/cb-tools/urlread2/license.txt deleted file mode 100644 index 1d783b74..00000000 --- a/cb-tools/urlread2/license.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2012, Jim Hokanson -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/cb-tools/urlread2/urlread2.m b/cb-tools/urlread2/urlread2.m deleted file mode 100644 index b552861c..00000000 --- a/cb-tools/urlread2/urlread2.m +++ /dev/null @@ -1,371 +0,0 @@ -function [output,extras] = urlread2(urlChar,method,body,headersIn,varargin) -%urlread2 Makes HTTP requests and processes response -% -% [output,extras] = urlread2(urlChar, *method, *body, *headersIn, varargin) -% -% * indicates optional inputs that must be entered in place -% -% UNDOCUMENTED MATLAB VERSION -% -% EXAMPLE CALLING FORMS -% ... = urlread2(urlChar) -% ... = urlread2(urlChar,'GET','',[],prop1,value1,prop2,value2,etc) -% ... = urlread2(urlChar,'POST',body,headers) -% -% FEATURES -% ======================================================================= -% 1) Allows specification of any HTTP method -% 2) Allows specification of any header. Very little is hard-coded -% in for header handling. -% 3) Returns response status and headers -% 4) Should handle unicode properly ... -% -% OUTPUTS -% ======================================================================= -% output : body of the response, either text or binary depending upon -% CAST_OUTPUT property -% extras : (structure) -% .allHeaders - stucture, fields have cellstr values, HTTP headers may -% may be repeated but will have a single field entry, with each -% repeat's value another being another entry in the cellstr, for -% example: -% .Set_Cookie = {'first_value' 'second_value'} -% .firstHeaders - (structure), variable fields, contains the first -% string entry for each field in allHeaders, this -% structure can be used to avoid dereferencing a cell -% for fields you expect not to be repeated ... -% EXAMPLE: -% .Response : 'HTTP/1.1 200 OK'} -% .Server : 'nginx' -% .Date : 'Tue, 29 Nov 2011 02:23:16 GMT' -% .Content_Type : 'text/html; charset=UTF-8' -% .Content_Length : '109155' -% .Connection : 'keep-alive' -% .Vary : 'Accept-Encoding, User-Agent' -% .Cache_Control : 'max-age=60, private' -% .Set_Cookie : 'first_value' -% .status - (structure) -% .value : numeric value of status, ex. 200 -% .msg : message that goes along with status, ex. 'OK' -% .url - eventual url that led to output, this can change from -% the input with redirects, see FOLLOW_REDIRECTS -% .isGood - (logical) I believe this is an indicator of the presence of 400 -% or 500 status codes (see status.value) but more -% testing is needed. In other words, true if status.value < 400. -% In code, set true if the response was obtainable without -% resorting to checking the error stream. -% -% INPUTS -% ======================================================================= -% urlChar : The full url, must include scheme (http, https) -% method : examples: 'GET' 'POST' etc -% body : (vector)(char, uint8 or int8) body to write, generally used -% with POST or PUT, use of uint8 or int8 ensures that the -% body input is not manipulated before sending, char is sent -% via unicode2native function with ENCODING input (see below) -% headersIn : (structure array), use empty [] or '' if no headers are needed -% but varargin property/value pairs are, multiple headers -% may be passed in as a structure array -% .name - (string), name of the header, a name property is used -% instead of a field because the name must match a valid -% header -% .value - (string), value to use -% -% OPTIONAL INPUTS (varargin, property/value pairs) -% ======================================================================= -% CAST_OUTPUT : (default true) output is uint8, useful if the body -% of the response is not text -% ENCODING : (default ''), ENCODING input to function unicode2native -% FOLLOW_REDIRECTS : (default true), if false 3xx status codes will -% be returned and need to be handled by the user, -% note this does not handle javascript or meta tag -% redirects, just server based ones -% READ_TIMEOUT : (default 0), 0 means no timeout, value is in -% milliseconds -% -% EXAMPLES -% ======================================================================= -% GET: -% -------------------------------------------- -% url = 'http://www.mathworks.com/matlabcentral/fileexchange/'; -% query = 'urlread2'; -% params = {'term' query}; -% queryString = http_paramsToString(params,1); -% url = [url '?' queryString]; -% [output,extras] = urlread2(url); -% -% POST: -% -------------------------------------------- -% url = 'http://posttestserver.com/post.php'; -% params = {'testChars' char([2500 30000]) 'new code' '?'}; -% [paramString,header] = http_paramsToString(params,1); -% [output,extras] = urlread2(url,'POST',paramString,header); -% -% From behind a firewall, use the Preferences to set your proxy server. -% -% See Also: -% http_paramsToString -% unicode2native -% native2unicode -% -% Subfunctions: -% fixHeaderCasing - small subfunction to fix case errors encountered in real -% world, requires updating when casing doesn't match expected form, like -% if someone sent the header content-Encoding instead of -% Content-Encoding -% -% Based on original urlread code by Matthew J. Simoneau -% -% VERSION = 1.1 - -in.CAST_OUTPUT = true; -in.FOLLOW_REDIRECTS = true; -in.READ_TIMEOUT = 0; -in.ENCODING = ''; - -%Input handling -%--------------------------------------- -if ~isempty(varargin) - for i = 1:2:numel(varargin) - prop = upper(varargin{i}); - value = varargin{i+1}; - if isfield(in,prop) - in.(prop) = value; - else - error('Unrecognized input to function: %s',prop) - end - end -end - -if ~exist('method','var') || isempty(method), method = 'GET'; end -if ~exist('body','var'), body = ''; end -if ~exist('headersIn','var'), headersIn = []; end - -assert(usejava('jvm'),'Function requires Java') - -import com.mathworks.mlwidgets.io.InterruptibleStreamCopier; -com.mathworks.mlwidgets.html.HTMLPrefs.setProxySettings %Proxy settings need to be set - -%Create a urlConnection. -%----------------------------------- -urlConnection = getURLConnection(urlChar); -%For HTTP uses sun.net.www.protocol.http.HttpURLConnection -%Might use ice.net.HttpURLConnection but this has more overhead - -%SETTING PROPERTIES -%------------------------------------------------------- -urlConnection.setRequestMethod(upper(method)); -urlConnection.setFollowRedirects(in.FOLLOW_REDIRECTS); -urlConnection.setReadTimeout(in.READ_TIMEOUT); - -for iHeader = 1:length(headersIn) - curHeader = headersIn(iHeader); - urlConnection.setRequestProperty(curHeader.name,curHeader.value); -end - -if ~isempty(body) - %Ensure vector? - if size(body,1) > 1 - if size(body,2) > 1 - error('Input parameter to function: body, must be a vector') - else - body = body'; - end - end - - if ischar(body) - %NOTE: '' defaults to Matlab's default encoding scheme - body = unicode2native(body,in.ENCODING); - elseif ~(strcmp(class(body),'uint8') || strcmp(class(body),'int8')) - error('Function input: body, should be of class char, uint8, or int8, detected: %s',class(body)) - end - - urlConnection.setRequestProperty('Content-Length',int2str(length(body))); - urlConnection.setDoOutput(true); - outputStream = urlConnection.getOutputStream; - outputStream.write(body); - outputStream.close; -else - urlConnection.setRequestProperty('Content-Length','0'); -end - -%========================================================================== -% Read the data from the connection. -%========================================================================== -%This should be done first because it tells us if things are ok or not -%NOTE: If there is an error, functions below using urlConnection, notably -%getResponseCode, will fail as well -try - inputStream = urlConnection.getInputStream; - isGood = true; -catch ME - isGood = false; -%NOTE: HTTP error codes will throw an error here, we'll allow those for now -%We might also get another error in which case the inputStream will be -%undefined, those we will throw here - inputStream = urlConnection.getErrorStream; - - if isempty(inputStream) - msg = ME.message; - I = strfind(msg,char([13 10 9])); %see example by setting timeout to 1 - %Should remove the barf of the stack, at ... at ... at ... etc - %Likely that this could be improved ... (generate link with full msg) - if ~isempty(I) - msg = msg(1:I(1)-1); - end - fprintf(2,'Response stream is undefined\n below is a Java Error dump (truncated):\n'); - error(msg) - end -end - -%POPULATING HEADERS -%-------------------------------------------------------------------------- -allHeaders = struct; -allHeaders.Response = {char(urlConnection.getHeaderField(0))}; -done = false; -headerIndex = 0; - -while ~done - headerIndex = headerIndex + 1; - headerValue = char(urlConnection.getHeaderField(headerIndex)); - if ~isempty(headerValue) - headerName = char(urlConnection.getHeaderFieldKey(headerIndex)); - headerName = fixHeaderCasing(headerName); %NOT YET FINISHED - - %Important, for name safety all hyphens are replace with underscores - headerName(headerName == '-') = '_'; - if isfield(allHeaders,headerName) - allHeaders.(headerName) = [allHeaders.(headerName) headerValue]; - else - allHeaders.(headerName) = {headerValue}; - end - else - done = true; - end -end - -firstHeaders = struct; -fn = fieldnames(allHeaders); -for iHeader = 1:length(fn) - curField = fn{iHeader}; - firstHeaders.(curField) = allHeaders.(curField){1}; -end - -status = struct(... - 'value', urlConnection.getResponseCode(),... - 'msg', char(urlConnection.getResponseMessage)); - -%PROCESSING OF OUTPUT -%---------------------------------------------------------- -byteArrayOutputStream = java.io.ByteArrayOutputStream; -% This StreamCopier is unsupported and may change at any time. OH GREAT :/ -isc = InterruptibleStreamCopier.getInterruptibleStreamCopier; -isc.copyStream(inputStream,byteArrayOutputStream); -inputStream.close; -byteArrayOutputStream.close; - -if in.CAST_OUTPUT - charset = ''; - - %Extraction of character set from Content-Type header if possible - if isfield(firstHeaders,'Content_Type') - text = firstHeaders.Content_Type; - %Always open to regexp improvements - charset = regexp(text,'(?<=charset=)[^\s]*','match','once'); - end - - if ~isempty(charset) - output = native2unicode(typecast(byteArrayOutputStream.toByteArray','uint8'),charset); - else - output = char(typecast(byteArrayOutputStream.toByteArray','uint8')); - end -else - %uint8 is more useful for later charecter conversions - %uint8 or int8 is somewhat arbitary at this point - output = typecast(byteArrayOutputStream.toByteArray','uint8'); -end - -extras = struct; -extras.allHeaders = allHeaders; -extras.firstHeaders = firstHeaders; -extras.status = status; -%Gets eventual url even with redirection -extras.url = char(urlConnection.getURL); -extras.isGood = isGood; - - - -end - -function headerNameOut = fixHeaderCasing(headerName) -%fixHeaderCasing Forces standard casing of headers -% -% headerNameOut = fixHeaderCasing(headerName) -% -% This is important for field access in a structure which -% is case sensitive -% -% Not yet finished. -% I've been adding to this function as problems come along - - switch lower(headerName) - case 'location' - headerNameOut = 'Location'; - case 'content_type' - headerNameOut = 'Content_Type'; - otherwise - headerNameOut = headerName; - end -end - -%========================================================================== -%========================================================================== -%========================================================================== - -function urlConnection = getURLConnection(urlChar) -%getURLConnection -% -% urlConnection = getURLConnection(urlChar) - -% Determine the protocol (before the ":"). -protocol = urlChar(1:find(urlChar==':',1)-1); - - -% Try to use the native handler, not the ice.* classes. -try - switch protocol - case 'http' - %http://www.docjar.com/docs/api/sun/net/www/protocol/http/HttpURLConnection.html - handler = sun.net.www.protocol.http.Handler; - case 'https' - handler = sun.net.www.protocol.https.Handler; - end -catch ME - handler = []; -end - -% Create the URL object. -try - if isempty(handler) - url = java.net.URL(urlChar); - else - url = java.net.URL([],urlChar,handler); - end -catch ME - error('Failure to parse URL or protocol not supported for:\nURL: %s',urlChar); -end - -% Get the proxy information using MathWorks facilities for unified proxy -% preference settings. -mwtcp = com.mathworks.net.transport.MWTransportClientPropertiesFactory.create(); -proxy = mwtcp.getProxy(); - -% Open a connection to the URL. -if isempty(proxy) - urlConnection = url.openConnection; -else - urlConnection = url.openConnection(proxy); -end - - -end diff --git a/cb-tools/urlread2/urlread_notes.txt b/cb-tools/urlread2/urlread_notes.txt deleted file mode 100644 index 8257f092..00000000 --- a/cb-tools/urlread2/urlread_notes.txt +++ /dev/null @@ -1,86 +0,0 @@ -========================================================================== - Unicode & Matlab -========================================================================== -native2unicode - works with uint8, fails with char - -Taking a unicode character and encoding as bytes: -unicode2native(char(1002),'UTF-8') -back to the character: -native2unicode(uint8([207 170]),'UTF-8') -this doesn't work: -native2unicode(char([207 170]),'UTF-8') -in documentation: If BYTES is a CHAR vector, it is returned unchanged. - -Java - only supports int8 -Matlab to Java -> uint8 or int8 to bytes -Java to Matlab -> bytes to int8 -char - 16 bit - -Maintenance of underlying bytes: -typecast(java.lang.String(uint8(250)).getBytes,'uint8') = 250 -see documentation: Handling Data Returned from a Java Method - -Command Window difficulty --------------------------------------------------------------------- -The typical font in the Matlab command window will often fail to render -unicode properly. I often can see unicode better in the variable editor -although this may be fixed if you change your font preferences ... -Copying unicode from the command window often results in the -generations of the value 26 (aka substitute) - -More documentation on input/output to urlread2 to follow eventually ... - -small notes to self: -for output -native2unicode(uint8(output),encoding) - -========================================================================== - HTTP Headers -========================================================================== -Handling of repeated http readers is a bit of a tricky situation. Most -headers are not repeated although sometimes http clients will assume this -for too many headers which can result in a problem if you want to see -duplicated headers. I've passed the problem onto the user who can decide -to handle it how they wish instead of providing the right solution, which -after some brief searching, I am not sure exists. - -========================================================================== - PROBLEMS -========================================================================== -1) Page requires following a redirect: -%------------------------------------------- -ref: http://www.mathworks.com/matlabcentral/newsreader/view_thread/302571 -fix: FOLLOW_REDIRECTS is enabled by default, you're fine. - -2) Basic authentication required: -%------------------------------------------ -Create and pass in the following header: -user = 'test'; -password = 'test'; -encoder = sun.misc.BASE64Encoder(); -str = java.lang.String([user ':' password]) %NOTE: format may be -%different for your server -header = http_createHeader('Authorization',char(encoder.encode(str.getBytes()))) -NOTE: Ideally you would make this a function - -3) The text returned doesn't make sense. -%----------------------------------------- -The text may not be encoded correctly. Requires native2unicode function. -See Unicode & Matlab section above. - -4) I get a different result in my web browser than I do in Matlab -%----------------------------------------- -This is generally seen for two reasons. -1 - The easiest and silly reason is user agent filtering. -When you make a request you identify yourself -as being a particular "broswer" or "user agent". Setting a header -with the user agent of the browser may fix the problem. -See: http://en.wikipedia.org/wiki/User_agent -See: http://whatsmyuseragent.com -value = '' -header = http_createHeader('User-Agent',value); -2 - You are not processing cookies and the server is not sending -you information because you haven't sent it cookies (everyone likes em!) -I've implemented cookie support but it requires some extra files that -I need to clean up. Feel free to email me if you'd really like to have them. - diff --git a/cb-tools/urlread2/urlread_todos.txt b/cb-tools/urlread2/urlread_todos.txt deleted file mode 100644 index 0434bcea..00000000 --- a/cb-tools/urlread2/urlread_todos.txt +++ /dev/null @@ -1,13 +0,0 @@ -========================================================================== - IMPROVEMENTS -========================================================================== -1) The function could be improved to support file streaming both in sending -and in receiving (saving) to reduce memory usage but this is very low priority - -2) Implement better casing handling - -3) Choose a better function name than urlread2 -> sorry Chris :) - -4) Support multipart/form-data, this ideally would be handled by a helper function - -5) Allow for throwing an error if the HTTP status code is an error (400) and 500 as well? \ No newline at end of file diff --git a/cb-tools/urlread2/urlread_versionInfo.txt b/cb-tools/urlread2/urlread_versionInfo.txt deleted file mode 100644 index a8448f49..00000000 --- a/cb-tools/urlread2/urlread_versionInfo.txt +++ /dev/null @@ -1,13 +0,0 @@ -===================== -Version 1.1 -3/25/2012 - -Summary: Bug fixes related to Matlab throwing errors when it shouldn't have been. Thanks to Shane Lin for pointing out the problems. - -Bug Fix: Modified code so that http status errors wouldn't throw errors in the code, but rather would be passed on to the user to process - -Bug Fix: Provided GET example code apparently doesn't work for all users, changed to a different example. - -===================== -Version 1 -3/17/2012 \ No newline at end of file diff --git a/cortexlab/+dat/findNextSeqNum.m b/cortexlab/+dat/findNextSeqNum.m new file mode 100644 index 00000000..f5f40796 --- /dev/null +++ b/cortexlab/+dat/findNextSeqNum.m @@ -0,0 +1,29 @@ + + +function expSeq = findNextSeqNum(subject, varargin) +% expSeq = findNextSeqNum(subject[, date]) +% +% Returns the next experiment number (aka Sequence number) that should be +% chosen for the given subject. Optionally specify a particular date to +% consider. + +if isempty(varargin) + expDate = now; %default to today +else + expDate = varargin{1}; +end + + +% retrieve list of experiments for subject +[~, dateList, seqList] = dat.listExps(subject); + +% filter the list by expdate +expDate = floor(expDate); +filterIdx = dateList == expDate; + +% find the next sequence number +expSeq = max(seqList(filterIdx)) + 1; +if isempty(expSeq) + % if none today, max will have returned [], so override this to 1 + expSeq = 1; +end \ No newline at end of file diff --git a/cortexlab/+dat/subjectSelector.m b/cortexlab/+dat/subjectSelector.m new file mode 100644 index 00000000..23df89b9 --- /dev/null +++ b/cortexlab/+dat/subjectSelector.m @@ -0,0 +1,92 @@ +function [subjectName, expNum] = subjectSelector(varargin) +% function [subjectName, expNum] = subjectSelector([parentFig], [alyxInstance]) +% make a popup window that will allow selection of a subject and expNum +% +% If you provide an alyxInstance, it will populate with a list of subjects +% from alyx; otherwise, from dat.listSubjects +% +% example usage: +% >> alyxInstance = alyx.loginWindow(); +% >> [subj, expNum] = subjectSelector([], alyxInstance); +% +% Created by NS 2017 + +subjectName = []; +expNum = 1; + +f = figure(); +set(f, 'MenuBar', 'none', 'Name', 'Select subject', 'NumberTitle', 'off','Resize', 'off', ... + 'WindowStyle', 'modal'); +w = 300; +h = 50; + +if nargin>0 && ~isempty(varargin{1}) + parentPos = get(varargin{1}, 'Position'); +else + parentPos = get(f, 'Position'); +end + +newPos = [parentPos(1)+parentPos(3)/2-w/2, parentPos(2)+parentPos(4)/2-h/2, w, h]; +set(f, 'Position', newPos); + +txtChooseSubject = uicontrol('Style', 'text', 'Parent', f, ... + 'Position',[10 h-30 90 25], ... + 'String', 'Choose subject:', 'HorizontalAlignment', 'right'); + +txtChooseExpNum = uicontrol('Style', 'text', 'Parent', f, ... + 'Position',[10 h-55 90 25], ... + 'String', 'Choose exp num:', 'HorizontalAlignment', 'right'); + +subjectDropdown = uicontrol('Style', 'popupmenu', 'Parent', f, ... + 'Position',[110 h-25 90 25], ... + 'Background', [1 1 1], 'Callback', @pickExpNum); + +if nargin>1 + ai = varargin{2}; + + set(subjectDropdown, 'String', dat.listSubjects(ai)); +else + set(subjectDropdown, 'String', dat.listSubjects); +end + +edtExpNum = uicontrol('Style', 'text', 'Parent', f, ... + 'Position',[110 h-50 90 25], ... + 'String', num2str(expNum), 'Background', [1 1 1]); + + +uicontrol('Style', 'pushbutton', 'String', 'OK', 'Position', ... + [210 h-25 90 25],'Callback', @ok); +uicontrol('Style', 'pushbutton', 'String', 'Cancel', 'Position', ... + [210 h-50 90 25],'Callback', @cancel); + +uiwait(); + + function ok(~,~) + + subjectList = get(subjectDropdown, 'String'); + subjectName = subjectList{get(subjectDropdown, 'Value')}; + expNum = str2num(get(edtExpNum, 'String')); + delete(f) + + end + + function cancel(~,~) + + subjectName = []; + expNum = []; + delete(f) + + end + + function pickExpNum(~,~) + + subjectList = get(subjectDropdown, 'String'); + subjectName = subjectList{get(subjectDropdown, 'Value')}; + try + expNumSuggestion = dat.findNextSeqNum(subjectName); + set(edtExpNum, 'String', num2str(expNumSuggestion)); + catch + end + + end +end \ No newline at end of file diff --git a/+dat/whichExpNums.m b/cortexlab/+dat/whichExpNums.m similarity index 63% rename from +dat/whichExpNums.m rename to cortexlab/+dat/whichExpNums.m index 2cc4f05d..68127da9 100644 --- a/+dat/whichExpNums.m +++ b/cortexlab/+dat/whichExpNums.m @@ -1,6 +1,22 @@ function [expNums, blocks, hasBlock, pars, isMpep, tl, hasTimeline] = ... whichExpNums(mouseName, thisDate) - +% [expNums, blocks, hasBlock, pars, isMpep, tl, hasTimeline] = ... +% whichExpNums(mouseName, thisDate) +% +% Attempt to automatically determine what experiments of what types were +% run for a subject on a given date. +% +% Returns: +% - expNums - list of the experiment numbers that exist +% - blocks - cell array of the Block structs +% - hasBlock - boolean array indicating whether each experiment had a block +% - pars - cell array of parameters structs +% - isMpep - boolean array of whether the experiment was mpep type +% - tl - cell array of Timeline structs +% - hasTimeline - boolean array of whether timeline was present for each +% experiment +% +% Created by NS 2017 rootExp = dat.expFilePath(mouseName, thisDate, 1, 'Timeline', 'master'); expInf = fileparts(fileparts(rootExp)); @@ -26,6 +42,8 @@ hasBlock(e) = true; end + % if there is a parameters file, load it and determine whether it is + % mpep type dPars = dat.expFilePath(mouseName, thisDate, expNums(e), 'parameters', 'master'); if exist(dPars) load(dPars) @@ -37,8 +55,7 @@ end - % if there is a timeline, load it and get photodiode events, mpep UDP - % events. + % if there is a timeline, load it dTL = dat.expFilePath(mouseName, thisDate, expNums(e), 'Timeline', 'master'); if exist(dTL) fprintf(1, 'expNum %d has timeline\n', e); diff --git a/cortexlab/+exp/choiceWorldParams.m b/cortexlab/+exp/choiceWorldParams.m index 377678fc..cbd07930 100644 --- a/cortexlab/+exp/choiceWorldParams.m +++ b/cortexlab/+exp/choiceWorldParams.m @@ -25,17 +25,17 @@ '''Reward'' volume delivered after stimulus onset'); % trial temporal structure -setValue('onsetVisStimDelay', 0.1, 's',... +setValue('onsetVisStimDelay', 0, 's',... 'Duration between the start of the onset tone and visual stimulus presentation'); setValue('onsetToneDuration', 0.1, 's',... 'Duration of the onset tone'); setValue('onsetToneRampDuration', 0.01, 's',... 'Duration of the onset tone amplitude ramp (up and down each this length)'); -setValue('preStimQuiescentPeriod', [2; 3], 's',... +setValue('preStimQuiescentPeriod', [0.2; 0.6], 's',... 'Required period of no input before stimulus presentation'); -setValue('bgCueDelay', 0.3, 's',... +setValue('bgCueDelay', 0, 's',... 'Delay period between target column presentation and grating cue'); -setValue('cueInteractiveDelay', 1, 's',... +setValue('cueInteractiveDelay', 0, 's',... 'Delay period between grating cue presentation and interactive phase'); setValue('hideCueDelay', inf, 's',... 'Delay period between cue presentation onset to hide it'); @@ -47,7 +47,7 @@ 'Duration of negative feedback phase (with stimulus locked in response position)'); setValue('feedbackDeliveryDelay', 0, 's',... 'Delay period between response completion and feedback provided'); -setValue('negFeedbackSoundDuration', 2, 's',... +setValue('negFeedbackSoundDuration', 0.5, 's',... 'Duration of negative feedback noise burst'); setValue('interTrialDelay', 0, 's', 'Delay between trials'); setValue('waitOnEarlyResponse', false, 'logical',... @@ -58,7 +58,7 @@ % visual stimulus characteristics setValue('targetWidth', 35, '°',... 'Width of target columns (visual angle)'); -setValue('distBetweenTargets', 80, '°',... +setValue('distBetweenTargets', 180, '°',... 'Width between target column centres (visual angle)'); setValue('targetAltitude', 0, '°',... 'Visual angle of target centre above horizon'); @@ -66,31 +66,31 @@ 'Horizontal translation of targets to reach response threshold (visual angle)'); setValue('cueSpatialFrequency', 0.1, 'cyc/°',... 'Spatial frequency of grating cue at the initial location on the horizon'); -setValue('cueSigma', [5; 5], '°',... +setValue('cueSigma', [9; 9], '°',... 'Size (w,h) of the grating, in terms of Gabor ? parameter (visual angle)'); -setValue('targetOrientation', 90, '°',... +setValue('targetOrientation', 45, '°',... 'Orientation of gabor grating (cw from horizontal)'); setValue('bgColour', 0*255*ones(3, 1), 'rgb',... 'Colour of background area'); setValue('targetColour', 0.5*255*ones(3, 1), 'rgb',... 'Colour of target columns'); -setValue('visWheelGain', 3, '°/mm',... +setValue('visWheelGain', 3.5, '°/mm',... 'Visual stimulus translation per movement at wheel surface (for stimuli ahead)'); % audio setValue('onsetToneMaxAmp', 1, 'normalised',... 'Maximum amplitude of onset tone'); -setValue('onsetToneFreq', 12e3, 'Hz',... +setValue('onsetToneFreq', 11e3, 'Hz',... 'Frequency of the onset tone'); -setValue('negFeedbackSoundAmp', 0.025, 'normalised',... +setValue('negFeedbackSoundAmp', 0.01, 'normalised',... 'Amplitude of negative feedback noise burst'); % misc -setValue('quiescenceThreshold', 1, 'sensor units',... +setValue('quiescenceThreshold', 10, 'sensor units',... 'Input movement must be under this threshold to count as quiescent'); %% configure trial-specific parameters -contrast = [0.5 0.4 0.2 0.1 0.05 0]; % contrast list to use on one side or the other +contrast = [1 0.5 0.25 0.12 0.06 0]; % contrast list to use on one side or the other % compute contrast one each target - ones side has contrast, other has zero targetCon = [contrast, zeros(1, numel(contrast));... zeros(1, numel(contrast)), contrast]; @@ -100,12 +100,11 @@ -ones(1, numel(contrast)), ones(1, numel(contrast)); ... -ones(1, numel(contrast) - 1) 1 -ones(1, numel(contrast) - 1) 1]; % repeat all incorrect trials except zero contrast ones -repIncorrect = abs(diff(targetCon)) > 0; +repIncorrect = abs(diff(targetCon)) > 0.25; % by default only use only the 50% contrast condition -useConditions = abs(diff(targetCon)) == 0.5 | abs(diff(targetCon)) == 0.2... - | abs(diff(targetCon)) == 0.1; +useConditions = abs(diff(targetCon)) == 0.5 | abs(diff(targetCon)) == 1; % uniform repeats for at least 300 trials -nReps = ceil(300*useConditions./sum(useConditions)); +nReps = ceil(1000*useConditions./sum(useConditions)); setValue('visCueContrast', targetCon, 'normalised', 'Contrast of grating cue at each target'); setValue('feedbackForResponse', feedback, 'normalised', 'Feedback given for each target'); setValue('repeatIncorrectTrial', repIncorrect, 'logical',... diff --git a/cortexlab/+hw/DaqLever.m b/cortexlab/+hw/DaqLever.m new file mode 100644 index 00000000..47c9f3f8 --- /dev/null +++ b/cortexlab/+hw/DaqLever.m @@ -0,0 +1,129 @@ +classdef DaqLever < hw.PositionSensor + %HW.DaqLever Gets output from button + % Adopted from DaqRotaryEncoder + % AP 170629 + + properties + % hardcoded for zgood at the moment, not sure where this is normally changed AP 170629 + DaqSession = [] %DAQ session for input (see session-based interface docs) + DaqId = 'Dev2' %DAQ's device ID, e.g. 'Dev1' + DaqChannelId = 'port0/line3' %DAQ's ID for the counter channel. e.g. 'ctr0' + %DaqChannelId = 'ai3'; + end + + properties (Access = protected) + %Created when listenForAvailableData is called, allowing logging of + %positions during DAQ background acquision + DaqListener + DaqInputChannelIdx %Index into acquired input data matrices for our channel + LastDaqValue %Last value obtained from the DAQ counter + %Accumulated cycle number for position (i.e. when the DAQ's counter has + %over- or underflowed its range, this is incremented or decremented + %accordingly) + Cycle + end + + properties (Dependent) + DaqChannelIdx % index into DaqSession's channels for our data + end + + methods + function value = get.DaqChannelIdx(obj) + inputs = find(strcmpi('input', io.daqSessionChannelDirections(obj.DaqSession))); + value = inputs(obj.DaqInputChannelIdx); + end + + function set.DaqChannelIdx(obj, value) + % get directions of all channels on this session + dirs = io.daqSessionChannelDirections(obj.DaqSession); + % logical array flagging all input channels + inputsUptoChannel = strcmp(dirs(1:value), 'Input'); + % ensure the channel we're setting is an input + assert(inputsUptoChannel(value), 'Channel %i is not an input', value); + % find channel number counting inputs only + obj.DaqInputChannelIdx = sum(inputsUptoChannel); + end + + function createDaqChannel(obj) + % this didn't work - doesn't support timers, and something that I + % don't know never starts on stimserver + [ch, idx] = obj.DaqSession.addDigitalChannel(obj.DaqId, obj.DaqChannelId,'InputOnly'); + %[ch, idx] = obj.DaqSession.addAnalogInputChannel(obj.DaqId, obj.DaqChannelId,'Voltage'); + % quadrature encoding where each pulse from the channel updates + % the counter - ie. maximum resolution (see http://www.ni.com/white-paper/7109/en) + obj.DaqChannelIdx = idx; % record the index of the channel + %initialise LastDaqValue with current counter value + daqValue = obj.DaqSession.inputSingleScan(); + obj.LastDaqValue = daqValue(obj.DaqInputChannelIdx); + %reset cycle number + obj.Cycle = 0; + end + + function msg = wiringInfo(obj) + ch = obj.DaqSession.Channels(obj.DaqChannelIdx); + s1 = sprintf('Terminals: A = %s, B = %s\n', ... + ch.TerminalA, ch.TerminalB); + s2 = sprintf('For KÜBLER 2400 series wiring is:\n'); + s3 = sprintf('GREEN -> %s, GREY -> %s, BROWN -> +5V, WHITE -> DGND\n',... + ch.TerminalA, ch.TerminalB); + msg = [s1 s2 s3]; + end + + function listenForAvailableData(obj) + % adds a listener to the DAQ session that will receive and process + % data when the DAQ is acquiring data in the background (i.e. + % startBackground() has been called on the session). + deleteListeners(obj); + obj.DaqListener = obj.DaqSession.addlistener('DataAvailable', ... + @(src, event) daqListener(obj, src, event)); + end + + function delete(obj) + deleteListeners(obj); + end + + function deleteListeners(obj) + if ~isempty(obj.DaqListener) + delete(obj.DaqListener); + end; + end + + function x = decodeDaq(obj, newValue) + %correct for 32-bit overflow/underflow + d = diff([obj.LastDaqValue; newValue]); + %decrement cycle for 'underflows', i.e. below 0 to a large value + %increment cycle for 'overflows', i.e. past max value to small values + cycle = obj.Cycle + cumsum(d < -0.5*obj.DaqCounterPeriod)... + - cumsum(d > 0.5*obj.DaqCounterPeriod); + x = obj.DaqCounterPeriod*cycle + newValue; + obj.Cycle = cycle(end); + obj.LastDaqValue = newValue(end); + end + end + + methods %(Access = protected) + function [x, time] = readAbsolutePosition(obj) + if obj.DaqSession.IsRunning + disp('waiting for session'); + obj.DaqSession.wait; + disp('done waiting'); + end + preTime = obj.Clock.now; + daqVal = inputSingleScan(obj.DaqSession); + x = daqVal; % AP 170629 straight digital read from lever + %x = decodeDaq(obj, daqVal(obj.DaqInputChannelIdx)); + postTime = obj.Clock.now; + time = 0.5*(preTime + postTime); % time is mean of before & after + end + end + + methods (Access = protected) + function daqListener(obj, src, event) + acqStartTime = obj.Clock.fromMatlab(event.TriggerTime); + values = decode(obj, event.Data(:,obj.DaqInputChannelIdx)) - obj.ZeroOffset; + times = acqStartTime + event.TimeStamps(:,obj.DaqInputChannelIdx); + logSamples(obj, values, times); + end + end +end + diff --git a/cortexlab/+hw/DaqLick.m b/cortexlab/+hw/DaqLick.m new file mode 100644 index 00000000..636c9ca9 --- /dev/null +++ b/cortexlab/+hw/DaqLick.m @@ -0,0 +1,131 @@ +classdef DaqLick < hw.PositionSensor + %HW.DaqLick Gets output from button + % Adopted from DaqRotaryEncoder + % AP 170629 + + properties + % hardcoded for zgood at the moment, not sure where this is normally changed AP 170629 + % (I think the protocol is to hardcode whatever, change manually, and + % save) + DaqSession = [] %DAQ session for input (see session-based interface docs) + DaqId = 'Dev2' %DAQ's device ID, e.g. 'Dev1' + DaqChannelId = 'port0/line2' %DAQ's ID for the counter channel. e.g. 'ctr0' + %DaqChannelId = 'ai3'; + end + + properties (Access = protected) + %Created when listenForAvailableData is called, allowing logging of + %positions during DAQ background acquision + DaqListener + DaqInputChannelIdx %Index into acquired input data matrices for our channel + LastDaqValue %Last value obtained from the DAQ counter + %Accumulated cycle number for position (i.e. when the DAQ's counter has + %over- or underflowed its range, this is incremented or decremented + %accordingly) + Cycle + end + + properties (Dependent) + DaqChannelIdx % index into DaqSession's channels for our data + end + + methods + function value = get.DaqChannelIdx(obj) + inputs = find(strcmpi('input', io.daqSessionChannelDirections(obj.DaqSession))); + value = inputs(obj.DaqInputChannelIdx); + end + + function set.DaqChannelIdx(obj, value) + % get directions of all channels on this session + dirs = io.daqSessionChannelDirections(obj.DaqSession); + % logical array flagging all input channels + inputsUptoChannel = strcmp(dirs(1:value), 'Input'); + % ensure the channel we're setting is an input + assert(inputsUptoChannel(value), 'Channel %i is not an input', value); + % find channel number counting inputs only + obj.DaqInputChannelIdx = sum(inputsUptoChannel); + end + + function createDaqChannel(obj) + % this didn't work - doesn't support timers, and something that I + % don't know never starts on stimserver + [ch, idx] = obj.DaqSession.addDigitalChannel(obj.DaqId, obj.DaqChannelId,'InputOnly'); + %[ch, idx] = obj.DaqSession.addAnalogInputChannel(obj.DaqId, obj.DaqChannelId,'Voltage'); + % quadrature encoding where each pulse from the channel updates + % the counter - ie. maximum resolution (see http://www.ni.com/white-paper/7109/en) + obj.DaqChannelIdx = idx; % record the index of the channel + %initialise LastDaqValue with current counter value + daqValue = obj.DaqSession.inputSingleScan(); + obj.LastDaqValue = daqValue(obj.DaqInputChannelIdx); + %reset cycle number + obj.Cycle = 0; + end + + function msg = wiringInfo(obj) + ch = obj.DaqSession.Channels(obj.DaqChannelIdx); + s1 = sprintf('Terminals: A = %s, B = %s\n', ... + ch.TerminalA, ch.TerminalB); + s2 = sprintf('For KÜBLER 2400 series wiring is:\n'); + s3 = sprintf('GREEN -> %s, GREY -> %s, BROWN -> +5V, WHITE -> DGND\n',... + ch.TerminalA, ch.TerminalB); + msg = [s1 s2 s3]; + end + + function listenForAvailableData(obj) + % adds a listener to the DAQ session that will receive and process + % data when the DAQ is acquiring data in the background (i.e. + % startBackground() has been called on the session). + deleteListeners(obj); + obj.DaqListener = obj.DaqSession.addlistener('DataAvailable', ... + @(src, event) daqListener(obj, src, event)); + end + + function delete(obj) + deleteListeners(obj); + end + + function deleteListeners(obj) + if ~isempty(obj.DaqListener) + delete(obj.DaqListener); + end; + end + + function x = decodeDaq(obj, newValue) + %correct for 32-bit overflow/underflow + d = diff([obj.LastDaqValue; newValue]); + %decrement cycle for 'underflows', i.e. below 0 to a large value + %increment cycle for 'overflows', i.e. past max value to small values + cycle = obj.Cycle + cumsum(d < -0.5*obj.DaqCounterPeriod)... + - cumsum(d > 0.5*obj.DaqCounterPeriod); + x = obj.DaqCounterPeriod*cycle + newValue; + obj.Cycle = cycle(end); + obj.LastDaqValue = newValue(end); + end + end + + methods %(Access = protected) + function [x, time] = readAbsolutePosition(obj) + if obj.DaqSession.IsRunning + disp('waiting for session'); + obj.DaqSession.wait; + disp('done waiting'); + end + preTime = obj.Clock.now; + daqVal = inputSingleScan(obj.DaqSession); + x = daqVal; % AP 170629 straight digital read from lever + %x = decodeDaq(obj, daqVal(obj.DaqInputChannelIdx)); + postTime = obj.Clock.now; + time = 0.5*(preTime + postTime); % time is mean of before & after + end + end + + methods (Access = protected) + function daqListener(obj, src, event) + acqStartTime = obj.Clock.fromMatlab(event.TriggerTime); + values = decode(obj, event.Data(:,obj.DaqInputChannelIdx)) - obj.ZeroOffset; + times = acqStartTime + event.TimeStamps(:,obj.DaqInputChannelIdx); + logSamples(obj, values, times); + end + end +end + diff --git a/cortexlab/+hw/DaqPiezo.m b/cortexlab/+hw/DaqPiezo.m new file mode 100644 index 00000000..c83500f9 --- /dev/null +++ b/cortexlab/+hw/DaqPiezo.m @@ -0,0 +1,129 @@ +classdef DaqPiezo < hw.PositionSensor + %HW.DaqPiezo Gets output from button + % Adopted from DaqRotaryEncoder + % AP 170629 + + properties + % hardcoded for zgood at the moment, not sure where this is normally changed AP 170629 + DaqSession = [] %DAQ session for input (see session-based interface docs) + DaqId = 'Dev2' %DAQ's device ID, e.g. 'Dev1' + DaqChannelId = 'port0/line3' %DAQ's ID for the counter channel. e.g. 'ctr0' + %DaqChannelId = 'ai3'; + end + + properties (Access = protected) + %Created when listenForAvailableData is called, allowing logging of + %positions during DAQ background acquision + DaqListener + DaqInputChannelIdx %Index into acquired input data matrices for our channel + LastDaqValue %Last value obtained from the DAQ counter + %Accumulated cycle number for position (i.e. when the DAQ's counter has + %over- or underflowed its range, this is incremented or decremented + %accordingly) + Cycle + end + + properties (Dependent) + DaqChannelIdx % index into DaqSession's channels for our data + end + + methods + function value = get.DaqChannelIdx(obj) + inputs = find(strcmpi('input', io.daqSessionChannelDirections(obj.DaqSession))); + value = inputs(obj.DaqInputChannelIdx); + end + + function set.DaqChannelIdx(obj, value) + % get directions of all channels on this session + dirs = io.daqSessionChannelDirections(obj.DaqSession); + % logical array flagging all input channels + inputsUptoChannel = strcmp(dirs(1:value), 'Input'); + % ensure the channel we're setting is an input + assert(inputsUptoChannel(value), 'Channel %i is not an input', value); + % find channel number counting inputs only + obj.DaqInputChannelIdx = sum(inputsUptoChannel); + end + + function createDaqChannel(obj) + % this didn't work - doesn't support timers, and something that I + % don't know never starts on stimserver + [ch, idx] = obj.DaqSession.addDigitalChannel(obj.DaqId, obj.DaqChannelId,'InputOnly'); + %[ch, idx] = obj.DaqSession.addAnalogInputChannel(obj.DaqId, obj.DaqChannelId,'Voltage'); + % quadrature encoding where each pulse from the channel updates + % the counter - ie. maximum resolution (see http://www.ni.com/white-paper/7109/en) + obj.DaqChannelIdx = idx; % record the index of the channel + %initialise LastDaqValue with current counter value + daqValue = obj.DaqSession.inputSingleScan(); + obj.LastDaqValue = daqValue(obj.DaqInputChannelIdx); + %reset cycle number + obj.Cycle = 0; + end + + function msg = wiringInfo(obj) + ch = obj.DaqSession.Channels(obj.DaqChannelIdx); + s1 = sprintf('Terminals: A = %s, B = %s\n', ... + ch.TerminalA, ch.TerminalB); + s2 = sprintf('For KÜBLER 2400 series wiring is:\n'); + s3 = sprintf('GREEN -> %s, GREY -> %s, BROWN -> +5V, WHITE -> DGND\n',... + ch.TerminalA, ch.TerminalB); + msg = [s1 s2 s3]; + end + + function listenForAvailableData(obj) + % adds a listener to the DAQ session that will receive and process + % data when the DAQ is acquiring data in the background (i.e. + % startBackground() has been called on the session). + deleteListeners(obj); + obj.DaqListener = obj.DaqSession.addlistener('DataAvailable', ... + @(src, event) daqListener(obj, src, event)); + end + + function delete(obj) + deleteListeners(obj); + end + + function deleteListeners(obj) + if ~isempty(obj.DaqListener) + delete(obj.DaqListener); + end; + end + + function x = decodeDaq(obj, newValue) + %correct for 32-bit overflow/underflow + d = diff([obj.LastDaqValue; newValue]); + %decrement cycle for 'underflows', i.e. below 0 to a large value + %increment cycle for 'overflows', i.e. past max value to small values + cycle = obj.Cycle + cumsum(d < -0.5*obj.DaqCounterPeriod)... + - cumsum(d > 0.5*obj.DaqCounterPeriod); + x = obj.DaqCounterPeriod*cycle + newValue; + obj.Cycle = cycle(end); + obj.LastDaqValue = newValue(end); + end + end + + methods %(Access = protected) + function [x, time] = readAbsolutePosition(obj) + if obj.DaqSession.IsRunning + disp('waiting for session'); + obj.DaqSession.wait; + disp('done waiting'); + end + preTime = obj.Clock.now; + daqVal = inputSingleScan(obj.DaqSession); + x = daqVal; % AP 170629 straight digital read from lever + %x = decodeDaq(obj, daqVal(obj.DaqInputChannelIdx)); + postTime = obj.Clock.now; + time = 0.5*(preTime + postTime); % time is mean of before & after + end + end + + methods (Access = protected) + function daqListener(obj, src, event) + acqStartTime = obj.Clock.fromMatlab(event.TriggerTime); + values = decode(obj, event.Data(:,obj.DaqInputChannelIdx)) - obj.ZeroOffset; + times = acqStartTime + event.TimeStamps(:,obj.DaqInputChannelIdx); + logSamples(obj, values, times); + end + end +end + diff --git a/cortexlab/+hw/daqControllerForValve.m b/cortexlab/+hw/daqControllerForValve.m deleted file mode 100644 index 0418cc4c..00000000 --- a/cortexlab/+hw/daqControllerForValve.m +++ /dev/null @@ -1,28 +0,0 @@ -function daqController = daqControllerForValve(daqRewardValve, calibrations, addLaser) -%UNTITLED Summary of this function goes here -% Detailed explanation goes here - -daqController = hw.DaqController; -daqController.ChannelNames = {'rewardValve'}; -daqController.DaqIds = 'Dev1'; -daqController.DaqChannelIds = {daqRewardValve.DaqChannelId}; -daqController.SignalGenerators = hw.RewardValveControl; -daqController.SignalGenerators.ClosedValue = daqRewardValve.ClosedValue; -daqController.SignalGenerators.DefaultValue = daqRewardValve.ClosedValue; -daqController.SignalGenerators.OpenValue = daqRewardValve.OpenValue; -daqController.SignalGenerators.Calibrations = calibrations; -daqController.SignalGenerators.DefaultCommand = daqRewardValve.DefaultRewardSize; - -if nargin > 2 && addLaser - daqController.DaqChannelIds{2} = 'ao1'; - daqController.ChannelNames{2} = {'laserShutter'}; - daqController.SignalGenerators(2) = hw.PulseSwitcher; - daqController.SignalGenerators(2).ClosedValue = 0; - daqController.SignalGenerators(2).DefaultValue = 0; - daqController.SignalGenerators(2).OpenValue = 5; - daqController.SignalGenerators(2).DefaultCommand = 10; - daqController.SignalGenerators(2).ParamsFun = @(sz) deal(10/1000, sz, 25); -end - - -end \ No newline at end of file diff --git a/cortexlab/+io/MpepUDPDataHosts.m b/cortexlab/+io/MpepUDPDataHosts.m index 96a9a130..1f476d97 100644 --- a/cortexlab/+io/MpepUDPDataHosts.m +++ b/cortexlab/+io/MpepUDPDataHosts.m @@ -6,6 +6,7 @@ % 2014-01 CB created % 2015-07 DS record UDP message to timeline + % 2016-12 MW update for new timeline object properties (Dependent, SetAccess = protected) Connected @@ -18,6 +19,8 @@ DaqDevId DigitalOutDaqChannelId Verbose = false % whether to output I/O messages etc + Timeline % An instance of timeline for for recording UDP messages + AlyxInstance end properties (SetAccess = protected) @@ -47,7 +50,9 @@ end methods - function obj = MpepUDPDataHosts(remoteHosts) + function obj = MpepUDPDataHosts(remoteHosts, timeline) + if nargin<2; timeline = []; end + obj.Timeline = timeline; obj.RemoteHosts = remoteHosts; end @@ -166,8 +171,8 @@ function stimEnded(obj, num) msg = sprintf('StimEnd %s %d %d 1 %d', subject, seriesNum, expNum, num); broadcast(obj, msg); - if tl.running %copied from bindMpepServer.m - tl.record('mpepUDP', msg); % record the UDP event in Timeline + if ~isempty(obj.Timeline)&&isfield(obj.Timeline, 'IsRunning')&&obj.Timeline.IsRunning + obj.Timeline.record('mpepUDP', msg); % record the UDP event in Timeline end dt = toc; if obj.Verbose @@ -196,7 +201,13 @@ function expEnded(obj) obj.ExpRef = []; end - function start(obj, expRef) + function start(obj, ref) + [expRef, ai] = dat.parseAlyxInstance(ref); + obj.AlyxInstance = ai; + [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); + alyxmsg = sprintf('alyx %s %d %d %s', subject, seriesNum, expNum, ref); + confirmedBroadcast(obj, alyxmsg); + % equivalent to startExp(expRef) expStarted(obj, expRef); end @@ -226,11 +237,9 @@ function stop(obj) function confirmedBroadcast(obj, msg) broadcast(obj, msg); validateResponses(obj); - - if tl.running %copied from bindMpepServer.m - tl.record('mpepUDP', msg); % record the UDP event in Timeline + if ~isempty(obj.Timeline)&&isfield(obj.Timeline, 'IsRunning')&&obj.Timeline.IsRunning + obj.Timeline.record('mpepUDP', msg); % record the UDP event in Timeline end - end function broadcast(obj, msg) diff --git a/cortexlab/+psy/plot2AUFC.m b/cortexlab/+psy/plot2AUFC.m new file mode 100644 index 00000000..0c260f24 --- /dev/null +++ b/cortexlab/+psy/plot2AUFC.m @@ -0,0 +1,73 @@ +function plot2AUFC(ax, block) + +% numCompletedTrials = block.numCompletedTrials; + +[block.trial(arrayfun(@(a)isempty(a.contrast), block.trial)).contrast] = deal(nan); +[block.trial(arrayfun(@(a)isempty(a.response), block.trial)).response] = deal(nan); +[block.trial(arrayfun(@(a)isempty(a.repeatNum), block.trial)).repeatNum] = deal(nan); +[block.trial(arrayfun(@(a)isempty(a.feedback), block.trial)).feedback] = deal(nan); +contrast = [block.trial.contrast]; +response = [block.trial.response]; +repeatNum = [block.trial.repeatNum]; +% feedback = [block.trial.feedback]; +if any(structfun(@isempty, block.trial(end))) % strip incomplete trials + contrast = contrast(1:end-1); + response = response(1:end-1); + repeatNum = repeatNum(1:end-1); +end +respTypes = unique(response); +numRespTypes = numel(respTypes); + +cVals = unique(contrast); + +psychoM = zeros(numRespTypes,length(cVals)); +psychoMCI = zeros(numRespTypes,length(cVals)); +numTrials = zeros(1,length(cVals)); +numChooseR = zeros(numRespTypes, length(cVals)); +for r = 1:numRespTypes + for c = 1:length(cVals) + incl = repeatNum==1&contrast==cVals(c); + numTrials(c) = sum(incl); + numChooseR(r,c) = sum(response==respTypes(r)&incl); + + psychoM(r, c) = numChooseR(r,c)/numTrials(c); + psychoMCI(r, c) = 1.96*sqrt(psychoM(r, c)*(1-psychoM(r, c))/numTrials(c)); + + end +end + +colors = [0 1 1 + 1 0 0 + 0 1 0];%hsv(numRespTypes); +% hsv(3) + +for r = 1:numRespTypes + + xdata = 100*cVals; + ydata = 100*psychoM(r,:); +% errBars = 100*psychoMCI(r,:); + + plot(ax, xdata, ydata, '-o', 'Color', colors(r,:), 'LineWidth', 1.0); + + % set all NaN values to 0 so the fill function can proceed just + % skipping over those points. +% ydata(isnan(ydata)) = 0; +% errBars(isnan(errBars)) = 0; + + %TODO:update to use plt.hshade +% fillhandle = fill([xdata xdata(end:-1:1)],... +% [ydata+errBars ydata(end:-1:1)-errBars(end:-1:1)], colors(r,:),... +% 'Parent', ax); +% set(fillhandle, 'FaceAlpha', 0.15, 'EdgeAlpha', 0); + %,... + + +% hold on; + + +end +ylim(ax, [-1 101]); +xdata = xdata(~isnan(xdata)); +if numel(xdata) > 1 + xlim(ax, xdata([1 end])*1.1); +end diff --git a/cortexlab/+srv/RemoteMPEPService.m b/cortexlab/+srv/RemoteMPEPService.m new file mode 100644 index 00000000..457428f4 --- /dev/null +++ b/cortexlab/+srv/RemoteMPEPService.m @@ -0,0 +1,312 @@ +classdef RemoteMPEPService < srv.Service + %SRV.REMOTETLSERVICE UDP-based service for starting and stopping Timeline + % A UDP interface that uses the udp function of the Instument Control + % Toolbox. Unlike SRV.PRIMITIVEUDPSERVICE, this can send and recieve + % messages asynchronously and can be used both to start remote services + % and, service side, to listen for remote start/stop commands. + % + % To send a message simply use sentUDP(msg). Use confirmedSend(msg) to + % send send a message and await a confirmation (the same message echoed + % back). To receive messaged only, simply use bind() and add a + % listener to the MessageReceived event. + % + % Examples: + % remoteTL = srv.BasicUDPService('tl-host', 10000, 10000); + % remoteTL.start('2017-10-27-1-default'); % Start remote service with + % an experiment reference + % remoteTL.stop; remoteTL.delete; % Clean up after stopping remote + % rig + % + % experimentRig = srv.BasicUDPService('mainRigHostName', 10000, 10000); + % experimentRig.bind(); % Connect to the remote rig + % remoteStatus = requestStatus(experimentRig); % Get the status of + % the experimental rig + % lh = events.listener(experimentRig, 'MessageReceived', + % @(srv, evt)processMessage(srv, evt)); % Add a listener to do + % something when a message is received. + % + % See also SRV.PRIMITIVEUDPSERVICE, UDP. + % + % Part of Rigbox + + % 2017-10 MW created + + properties (GetObservable, SetAccess = protected) + Status % Status of remote service + end + + properties + LocalStatus % Local status to send upon request + ResponseTimeout = Inf % How long to wait for confirmation of receipt + Timeline % Holds an instance of Timeline + Callbacks = {@obj.processMsg, @nop} % Holds callback functions for each instruction + end + + properties (SetObservable, AbortSet = true) + RemoteHost % Host name of the remote service + ListenPorts % Localhost port number to listen for messages on + RemotePort = 1103 % Which port to send messages to remote service on + EnablePortSharing = 'off' % If set to 'on' other applications can use the listen port + end + + properties (SetAccess = protected) + RemoteIP % The IP address of the remote service + Sockets % A handle to the udp object + LastSentMessage = '' % A copy of the message sent from this host + LastReceivedMessage = '' % A copy of the message received by this host + end + + properties (Access = private) + Listener % A listener for the MessageReceived event + ResponseTimer % A timer object set when expecting a confirmation message (if ResponseTimeout < Inf) + AwaitingConfirmation = false % True when awaiting a confirmation message + ConfirmID % A random integer to confirm UDP status response. See requestStatus() + end + + events (NotifyAccess = 'protected') + MessageReceived % Notified by receiveUDP() when a UDP message is received + end + + methods + function delete(obj) + % To be called before destroying BasicUDPService object. Deletes all + % timers, sockets and listeners Tidy up after ourselves by closing + % the listening sockets + if ~isempty(obj.Sockets) + cellfun(@fclose, obj.Sockets); % Close the connection + delete(obj.Sockets); % Delete the socket + obj.Sockets = []; % Delete udp object + obj.Listener = []; % Delete any listeners to that object + if ~isempty(obj.ResponseTimer) % If there is a timer object + stop(obj.ResponseTimer) % Stop the timer.. + delete(obj.ResponseTimer) % Delete the timer... + obj.ResponseTimer = []; % ... and remove it + end + end + end + + function obj = RemoteMPEPService(name, listenPort, callback) + % SRV.REMOTETLSERVICE([remoteHost, remotePort, listenPort]) + % remoteHost is the hostname of the service with which to send and + % receive messages. + paths = dat.paths(hostname); % Get list of paths for timeline +% obj.Callbacks = struct('Instruction', {'ExpStart', 'BlockStart',... +% 'StimStart', 'StimEnd', 'BlockEnd', 'ExpEnd', 'ExpInterrupt'}, 'Callback', @nop); + load(fullfile(paths.rigConfig, 'hardware.mat'), 'timeline'); % Load timeline object + obj.Timeline = timeline; + obj.addListener(name, listenPort, callback); +% if nargin < 1; remoteHost = ''; end % Set local port +% obj.RemoteHost = remotehost; % Set hostname +% obj.RemoteIP = ipaddress(remoteHost); % Get IP address +% if nargin >= 3; obj.ListenPort = listenPort; end % Set local port +% if nargin >= 2; obj.RemotePort = remotePort; end % Set remote port +% obj.Socket = udp(obj.RemoteIP,... % Create udp object +% 'RemotePort', obj.RemotePort, 'LocalPort', obj.ListenPort); +% obj.Socket.BytesAvailableFcn = @obj.receiveUDP; % Add callback to receiveUDP when enough bytes arrive in the buffer + % Add listener to MessageReceived event, notified when receiveUDP is + % called. This event can be listened to by anyone. +% obj.Listener = event.listener(obj, 'MessageReceived', @processMsg); + % Add listener for when the observable properties are set +% obj.addlistener({'RemoteHost', 'ListenPort', 'RemotePort', 'EnablePortSharing'},... +% 'PostSet',@(src,~)obj.update(src)); + % Add listener for when the remote service's status is requested +% obj.addlistener('Status', 'PreGet', @obj.requestStatus); + end + + function obj = addListener(obj, name, listenPort, callback) + if nargin<4; callback = @nop; end + if any(listenPort==obj.ListenPorts{:}) + error('Listen port already added'); + end + idx = length(obj.Sockets)+1; + obj.Sockets{idx} = udp(name, 'RemotePort', obj.RemotePort,... + 'LocalPort', listenPort, 'ReadAsyncMode', 'continuous'); + obj.Sockets{idx}.BytesAvailableFcnCount = 10; % Number of bytes in buffer required to trigger BytesAvailableFcn + obj.Sockets{idx}.BytesAvailableFcn = @(~,~)obj.receiveUDP(src,evt); + obj.Sockets{idx}.Tag = name; + obj.ListenPorts{idx} = listenPort; + obj.Callbacks{idx} = callback; + end + + function obj = removeHost(obj, name) + %TODO + if nargin<3; callback = @nop; end + if listenPort==obj.ListenPorts + error('Listen port already added'); + end + obj.Sockets(end+1) = udp(obj.RemoteIP, 'RemotePort', obj.RemotePort, 'LocalPort', listenPort); + obj.Sockets(end).BytesAvailableFcn = @(~,~)obj.receiveUDP(src,evt); + obj.Sockets(end).Tag = name; + obj.ListenPorts(end+1) = listenPort; + obj.Callbacks(end+1) = callback; + end + + function update(obj, src) + % Callback for setting udp relevant properties. Some properties can + % only be set when the socket is closed. + % Check if socket is open + isOpen = strcmp(obj.Socket.Status, 'open'); + % Close connection before setting, if required to do so + if any(strcmp(src.name, {'RemoteHost', 'LocalPort', 'EnablePortSharing'}))&&isOpen + fclose(obj.Socket); + end + % Set all the relevant properties + obj.RemoteIP = ipaddress(obj.RemoteHost); + obj.Socket.LocalPort = obj.ListenPort; + obj.Socket.RemotePort = obj.RemotePort; + obj.Socket.RemoteHost = obj.RemoteIP; + if isOpen; bind(obj); end % If socket was open before, re-open + end + + function bind(obj, names) + if isempty(obj.Sockets) + warning('No sockets to bind') + return + end + if nargin<2 + % Close all sockets, in case they are open + cellfun(@fclose, obj.Sockets) + % Open the connection to allow messages to be sent and received + cellfun(@fopen, obj.Sockets) + else + names = ensureCell(names); + hosts = arrayfun(@(s)s.Tag, obj.Sockets); + idx = cellfun(@(n)find(strcmp(n,hosts)), names); + cellfun(@fopen, obj.Sockets(idx)) + end + obj.log('Polling for UDP messages'); + end + + function start(obj, ref) + % Send start message to remotehost and await confirmation +% [expRef, AlyxInstance] = parseAlyxInstance(ref); + % Convert expRef to MPEP style +% [subject, seriesNum, expNum] = dat.expRefToMpep(expRef); + % Build start message +% msg = sprintf('ExpStart %s %d %d', subject, seriesNum, expNum); + msg = sprintf('GOGO%s*%s', ref, hostname); + % Send the start message + obj.confirmedSend(msg, obj.RemoteHost); + % Wait for response + while obj.AwaitingConfirmation; pause(0.2); end +% % Start a block (we only use one per experiment) +% msg = sprintf('BlockStart %s %d %d 1', subject, seriesNum, expNum); +% obj.confirmedSend(msg, obj.RemoteHost); +% % Wait for response +% while obj.AwaitingConfirmation; pause(0.2); end + end + + function stop(obj) + % Send stop message to remotehost and await confirmation + obj.confirmedSend(sprintf('STOP*%s', obj.RemoteHost)); + end + + function requestStatus(obj) + % Request a status update from the remote service + obj.ConfirmID = randi(1e6); + obj.sendUDP(sprintf('WHAT%i*%s', obj.ConfirmID, obj.RemoteHost)); + disp('Requested status update from remote service') + end + + function confirmedSend(obj, msg) + sendUDP(obj, msg) + obj.AwaitingConfirmation = true; + % Add timer to impose a response timeout + if ~isinf(obj.ResponseTimeout) + obj.ResponseTimer = timer('StartDelay', obj.ResponseTimout,... + 'TimerFcn', @(~,~)obj.processMsg); + start(obj.ResponseTimer) % start the timer + end + end + + function receiveUDP(obj, src, evt) + obj.LastReceivedMessage = strtrim(fscanf(obj.Socket)); + % Let everyone know a message was recieved + notify(obj, 'MessageReceived') + hosts = arrayfun(@(s)s.Tag, obj.Sockets); + if ~isempty(obj.Timeline)&&obj.Timeline.IsRunning + t = obj.Timeline.time; % Note the time + % record the UDP event in Timeline + obj.Timeline.record([hosts 'UDP'], msg, t); + end + % Pass message to callback function for precessing + feval(obj.Callbacks{strcmp(hosts, src.Tag)}, src, evt); + end + + function sendUDP(obj, msg) + % Ensure socket is open before sending message + if strcmp(obj.Socket.Status, 'closed'); bind(obj); end + fprintf(obj.Socket, msg); % Send message + obj.LastSentMessage = msg; % Save a copy of the message + disp(['Sent message to ' obj.RemoteHost]) % Display success + end + + function echo(obj, src, ~) + % Echo message + fclose(src); + src.RemoteHost = src.DatagramAddress; + src.RemotePort = src.DatagramPort; + fopen(src); + fprintf(obj.Socket, obj.LastReceivedMessage); % Send message + obj.LastSentMessage = obj.LastReceivedMessage; % Save a copy of the message + log(obj,'Echo''d message to %s', src.Tag) % Display success + end + end + + methods (Access = protected) + function processMsg(obj, src, ~) + %PROCESSMSG Processes messages from expServer and MPEP + % As the remote host me be either expServer or MPEP, we first + % determine the type of message. Parse the message into its + % constituent parts +% if strcmp(obj.LastReceivedMessage(1:4), {'WHAT', 'GOGO', 'ALYX', 'STOP'}) + try % Try to process message as MPEP command + msg = dat.mpepMessageParse(obj.LastReceivedMessage); + catch + msg = regexp(obj.LastReceivedMessage,... + '(?[A-Z]{4})(?.*)\*(?\w*)', 'names'); + % If the message body contains and expRef, explicity set this + if regexp(msg.body,dat.expRefRegExp); msg.expRef = msg.body; end + end + + % Process the instruction + switch lower(msg.instruction) + case {'expstart', 'gogo'} + try + % Start Timeline + log(obj, 'Received start request') + obj.LocalStatus = 'starting'; + obj.Timeline.start(dat.parseAlyxInstance(msg.expRef)) + obj.LocalStatus = 'running'; + obj.echo(src); + % re-record the UDP event in Timeline since it wasn't started + % when we tried earlier. Treat it as having arrived at time zero. + obj.Timeline.record('mpepUDP', obj.LastReceivedMessage, 0); + catch ex + % flag up failure so we do not echo the UDP message back below + failed = true; + disp(getReport(ex)); + end + case {'expend', 'stop', 'expinterrupt'} + obj.Timeline.stop(); % stop Timeline + case 'what' + % TODO fix status updates so that they're meaningful + parsed = regexp(msg.body, '(?\d+)(?[a-z]*)', 'names'); + obj.sendUDP([parsed.status parsed.id obj.LocalStatus]) + case 'alyx' + % TODO Add Alyx token request + obj.sendUDP() + otherwise + % TODO RemoteHost + log(obj, ['Received ''' obj.LastReceivedMessage ''' from ' obj.RemoteHost]) + end + end + + function log(varargin) + message = sprintf(varargin{:}); + timestamp = datestr(now, 'dd-mm-yyyy HH:MM:SS'); + fprintf('[%s] %s\n', timestamp, message); + end + + end +end \ No newline at end of file diff --git a/cortexlab/+tl/bindMpepServer.m b/cortexlab/+tl/bindMpepServer.m index 3ddd033e..533e375b 100644 --- a/cortexlab/+tl/bindMpepServer.m +++ b/cortexlab/+tl/bindMpepServer.m @@ -18,9 +18,9 @@ mpepListenPort = 1001; % listen for commands on this port end -mpepSendPort = 1103; % send responses back to this remote port - +% mpepSendPort = 1103; % send responses back to this remote port quitKey = KbName('esc'); +manualStartKey = KbName('t'); %% Start UDP communication listeners = struct(... @@ -36,6 +36,13 @@ tls.close = @closeConns; tls.process = @process; tls.listen = @listen; +tls.AlyxInstance = []; + + +%% Initialize timeline +rig = hw.devices([], false); +tlObj = rig.timeline; +tls.tlObj = tlObj; %% Helper functions @@ -52,10 +59,10 @@ function process() function processListener(listener) sz = pnet(listener.socket, 'readpacket', 1000, 'noblock'); if sz > 0 - t = tl.time(false); % save the time we got the UDP packet + t = tlObj.time(false); % save the time we got the UDP packet msg = pnet(listener.socket, 'read'); - if tl.running - tl.record([listener.name 'UDP'], msg, t); % record the UDP event in Timeline + if tlObj.IsRunning + tlObj.record([listener.name 'UDP'], msg, t); % record the UDP event in Timeline end listener.callback(listener, msg); % call special handling function end @@ -71,23 +78,28 @@ function processMpep(listener, msg) failed = false; % flag for preventing UDP echo %% Experiment-level events start/stop timeline switch lower(info.instruction) + case 'alyx' + fprintf(1, 'received alyx token message\n'); + idx = find(msg==' ', 1, 'last'); + [~, ai] = dat.parseAlyxInstance(msg(idx+1:end)); + tls.AlyxInstance = ai; case 'expstart' % create a file path & experiment ref based on experiment info try % start Timeline - tl.start(info.expRef); + tlObj.start(info.expRef, tls.AlyxInstance); % re-record the UDP event in Timeline since it wasn't started % when we tried earlier. Treat it as having arrived at time zero. - tl.record('mpepUDP', msg, 0); + tlObj.record('mpepUDP', msg, 0); catch ex % flag up failure so we do not echo the UDP message back below failed = true; disp(getReport(ex)); end case 'expend' - tl.stop(); % stop Timeline + tlObj.stop(); % stop Timeline case 'expinterrupt' - tl.stop(); % stop Timeline + tlObj.stop(); % stop Timeline end if ~failed %% echo the UDP message back to the sender @@ -97,7 +109,7 @@ function processMpep(listener, msg) % connected = true; % end pnet(listener.socket, 'write', msg); - pnet(listener.socket, 'writepacket', ipstr, mpepSendPort); + pnet(listener.socket, 'writepacket', ipstr, port); end end @@ -116,6 +128,30 @@ function listen() if firstPress(quitKey) running = false; end + if firstPress(manualStartKey) && ~tlObj.IsRunning + + if isempty(tls.AlyxInstance) + % first get an alyx instance + ai = alyx.loginWindow(); + else + ai = tls.AlyxInstance; + end + + [mouseName, ~] = dat.subjectSelector([],ai); + + if ~isempty(mouseName) + clear expParams; + expParams.experimentType = 'timelineManualStart'; + [newExpRef, ~, subsessionURL] = dat.newExp(mouseName, now, expParams, ai); + ai.subsessionURL = subsessionURL; + tls.AlyxInstance = ai; + tlObj.start(newExpRef, ai); + end + KbQueueFlush; + elseif firstPress(manualStartKey) && tlObj.IsRunning + fprintf(1, 'stopping timeline\n'); + tlObj.stop(); + end if toc(tid) > 0.2 pause(1e-3); % allow timeline aquisition every so often tid = tic; diff --git a/cortexlab/+tl/bindMpepServerWithWS.m b/cortexlab/+tl/bindMpepServerWithWS.m index 33d8996b..0adbafca 100644 --- a/cortexlab/+tl/bindMpepServerWithWS.m +++ b/cortexlab/+tl/bindMpepServerWithWS.m @@ -37,6 +37,7 @@ tls.close = @closeConns; tls.process = @process; tls.listen = @listen; +tls.AlyxInstance = []; listenPort = io.WSJCommunicator.DefaultListenPort; communicator = io.WSJCommunicator.server(listenPort); @@ -45,6 +46,12 @@ communicator.EventMode = false; communicator.open(); +%% initialize timeline + +rig = hw.devices([], false); +tlObj = rig.timeline; +tls.tlObj = tlObj; + %% Helper functions function closeConns() @@ -61,10 +68,10 @@ function process() function processListener(listener) sz = pnet(listener.socket, 'readpacket', 1000, 'noblock'); if sz > 0 - t = tl.time(false); % save the time we got the UDP packet + t = tlObj.time(false); % save the time we got the UDP packet msg = pnet(listener.socket, 'read'); - if tl.running - tl.record([listener.name 'UDP'], msg, t); % record the UDP event in Timeline + if tlObj.IsRunning + tlObj.record([listener.name 'UDP'], msg, t); % record the UDP event in Timeline end listener.callback(listener, msg); % call special handling function end @@ -76,19 +83,29 @@ function processMpep(listener, msg) ipstr = sprintf('%i.%i.%i.%i', ip{:}); log('%s: ''%s'' from %s:%i', listener.name, msg, ipstr, port); % parse the message - info = dat.mpepMessageParse(msg); + info = dat.mpepMessageParse(msg); + failed = false; % flag for preventing UDP echo %% Experiment-level events start/stop timeline switch lower(info.instruction) + case 'alyx' + fprintf(1, 'received alyx token message\n'); + idx = find(msg==' ', 1, 'last'); + [expref, ai] = dat.parseAlyxInstance(msg(idx+1:end)); + disp(ai) + + tls.AlyxInstance = ai; case 'expstart' % create a file path & experiment ref based on experiment info try % start Timeline + communicator.send('AlyxSend', {tls.AlyxInstance}); communicator.send('status', { 'starting', info.expRef}); - tl.start(info.expRef); + + tlObj.start(info.expRef, tls.AlyxInstance); % re-record the UDP event in Timeline since it wasn't started % when we tried earlier. Treat it as having arrived at time zero. - tl.record('mpepUDP', msg, 0); + tlObj.record('mpepUDP', msg, 0); catch ex % flag up failure so we do not echo the UDP message back below failed = true; @@ -96,11 +113,11 @@ function processMpep(listener, msg) end case 'expend' - tl.stop(); % stop Timeline + tlObj.stop(); % stop Timeline communicator.send('status', { 'completed', info.expRef}); case 'expinterrupt' - tl.stop(); % stop Timeline + tlObj.stop(); % stop Timeline communicator.send('status', { 'completed', info.expRef}); end if ~failed @@ -135,20 +152,34 @@ function listen() if firstPress(quitKey) running = false; end - if firstPress(manualStartKey) && ~tl.running - [mouseName, ~] = dat.subjectSelector(); + if firstPress(manualStartKey) && ~tlObj.IsRunning + + if isempty(tls.AlyxInstance) + % first get an alyx instance + ai = alyx.loginWindow(); + else + ai = tls.AlyxInstance; + end + + [mouseName, ~] = dat.subjectSelector([],ai); + if ~isempty(mouseName) clear expParams; expParams.experimentType = 'timelineManualStart'; - newExpRef = dat.newExp(mouseName, now, expParams); + [newExpRef, newExpSeq, subsessionURL] = dat.newExp(mouseName, now, expParams, ai); + ai.subsessionURL = subsessionURL; + tls.AlyxInstance = ai; + %[subjectRef, expDate, expSequence] = dat.parseExpRef(newExpRef); %newExpRef = dat.constructExpRef(mouseName, now, expNum); + communicator.send('AlyxSend', {tls.AlyxInstance}); communicator.send('status', { 'starting', newExpRef}); - tl.start(newExpRef); + tlObj.start(newExpRef, ai); end - elseif firstPress(manualStartKey) && tl.running && ~isempty(newExpRef) - - tl.stop(); + KbQueueFlush; + elseif firstPress(manualStartKey) && tlObj.IsRunning && ~isempty(newExpRef) + fprintf(1, 'stopping timeline\n'); + tlObj.stop(); communicator.send('status', { 'completed', newExpRef}); newExpRef = []; end @@ -172,8 +203,8 @@ function handleMessage(id, data, host) % client disconnected log('WS: ''%s'' disconnected', host); else - command = data{1}; - args = data(2:end); + command = data{1} + args = data(2:end) if ~strcmp(command, 'status') % log the command received log('WS: Received ''%s''', command); @@ -181,7 +212,7 @@ function handleMessage(id, data, host) switch command case 'status' % status request - if ~tl.running + if ~tlObj.IsRunning communicator.send(id, {'idle'}); else communicator.send(id, {'running'}); diff --git a/cortexlab/+vis/checker4.m b/cortexlab/+vis/checker4.m new file mode 100644 index 00000000..be837609 --- /dev/null +++ b/cortexlab/+vis/checker4.m @@ -0,0 +1,126 @@ +function elem = checker3(t) +%vis.checker A grid of rectangles +% Detailed explanation goes here + +elem = t.Node.Net.subscriptableOrigin('checker'); + +%% make initial layers to be used as templates +maskTemplate = vis.emptyLayer(); +maskTemplate.isPeriodic = false; +maskTemplate.interpolation = 'nearest'; +maskTemplate.show = true; +maskTemplate.colourMask = [false false false true]; + +maskTemplate.textureId = 'checkerMaskPixel'; +[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); +maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value + +stencilTemplate = maskTemplate; +stencilTemplate.textureId = 'checkerStencilPixel'; +[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); +stencilTemplate.blending = 'none'; + +% pattern layer uses the alpha values laid down by mask layers +patternLayer = vis.emptyLayer(); +patternLayer.textureId = sprintf('~checker%i', randi(2^32)); +patternLayer.isPeriodic = false; +patternLayer.interpolation = 'nearest'; +patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this + +%% construct signals used to assemble layers +% N rows by cols signal is derived from the size of the pattern array but +% we skip repeats so that pattern changes don't update the mask layers +% unless the size has acutally changed +nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); +aziRange = elem.azimuthRange.flatten(); +altRange = elem.altitudeRange.flatten(); +sizeFrac = elem.rectSizeFrac.flatten(); +% signal containing the masking layers +gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... + maskTemplate, stencilTemplate, @gridMask); +% signal contain the checker layer +checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... + elem.colour.flatten(), @updateColour,... + elem.azimuthRange.flatten(), @updateAzi,... + elem.altitudeRange.flatten(), @updateAlt,... + elem.show.flatten(), @updateShow,... + patternLayer); % initial value +%% set default attribute values +elem.layers = [gridMaskLayers checkerLayer]; +elem.azimuthRange = [-132 132]; +elem.altitudeRange = [-36 36]; +elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle +elem.pattern = [ + 1 -1 1 -1 + -1 0 0 0 + 1 0 0 0 + -1 1 -1 1]; + elem.show = true; +end + +%% helper functions +function layer = updatePattern(layer, pattern) +% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then +% convert to RGBA texture format. +[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); +end + +function layer = updateColour(layer, colour) +layer.maxColour = [colour 1]; +end + +function layer = updateAzi(layer, aziRange) +layer.size(1) = abs(diff(aziRange)); +layer.texOffset(1) = mean(aziRange); +end + +function layer = updateAlt(layer, altRange) +layer.size(2) = abs(diff(altRange)); +layer.texOffset(2) = mean(altRange); +end + +function layer = updateShow(layer, show) +layer.show = show; +end + +function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) +gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; +cellSize = gridDims./flip(nRowsByCols); +nCols = nRowsByCols(2) + 1; +nRows = nRowsByCols(1) + 1; +midAzi = mean(aziRange); +midAlt = mean(altRange); +%% base layer to imprint area the checker can draw on (by applying an alpha mask) +stencil.texOffset = [midAzi midAlt]; +stencil.size = gridDims; +if any(sizeFrac < 1) + %% layers for lines making up mask grid - masks out margins around each square + % make layers for vertical lines + if nCols > 1 + azi = linspace(aziRange(1), aziRange(2), nCols); + else + azi = midAzi; + end + collayers = repmat(mask, 1, nCols); + for vi = 1:nCols + collayers(vi).texOffset = [azi(vi) midAlt]; + end + [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); + % make layers for horizontal lines + if nRows > 1 + alt = linspace(altRange(1), altRange(2), nRows); + else + alt = midAlt; + end + rowlayers = repmat(mask, 1, nRows); + for hi = 1:nRows + rowlayers(hi).texOffset = [midAzi alt(hi)]; + end + [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); + %% combine the layers and return + layers = [stencil collayers rowlayers]; +else % no mask grid needed as each cell is full size + layers = stencil; +end + +end \ No newline at end of file diff --git a/cortexlab/+vis/checker5.m b/cortexlab/+vis/checker5.m new file mode 100644 index 00000000..be37a1f4 --- /dev/null +++ b/cortexlab/+vis/checker5.m @@ -0,0 +1,126 @@ +function elem = checker5(t) +%vis.checker A grid of rectangles +% Detailed explanation goes here + +elem = t.Node.Net.subscriptableOrigin('checker'); + +%% make initial layers to be used as templates +maskTemplate = vis.emptyLayer(); +maskTemplate.isPeriodic = false; +maskTemplate.interpolation = 'nearest'; +maskTemplate.show = true; +maskTemplate.colourMask = [false false false true]; + +maskTemplate.textureId = 'checkerMaskPixel'; +[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); +maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value + +stencilTemplate = maskTemplate; +stencilTemplate.textureId = 'checkerStencilPixel'; +[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); +stencilTemplate.blending = 'none'; + +% pattern layer uses the alpha values laid down by mask layers +patternLayer = vis.emptyLayer(); +patternLayer.textureId = sprintf('~checker%i', randi(2^32)); +patternLayer.isPeriodic = false; +patternLayer.interpolation = 'nearest'; +patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this + +%% construct signals used to assemble layers +% N rows by cols signal is derived from the size of the pattern array but +% we skip repeats so that pattern changes don't update the mask layers +% unless the size has acutally changed +nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); +aziRange = elem.azimuthRange.flatten(); +altRange = elem.altitudeRange.flatten(); +sizeFrac = elem.rectSizeFrac.flatten(); +% signal containing the masking layers +gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... + maskTemplate, stencilTemplate, @gridMask); +% signal contain the checker layer +checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... + elem.colour.flatten(), @updateColour,... + elem.azimuthRange.flatten(), @updateAzi,... + elem.altitudeRange.flatten(), @updateAlt,... + elem.show.flatten(), @updateShow,... + patternLayer); % initial value +%% set default attribute values +elem.layers = [gridMaskLayers checkerLayer]; +elem.azimuthRange = [-132 132]; +elem.altitudeRange = [-36 36]; +elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle +elem.pattern = [ + 1 -1 1 -1 + -1 0 0 0 + 1 0 0 0 + -1 1 -1 1]; + elem.show = true; +end + +%% helper functions +function layer = updatePattern(layer, pattern) +% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then +% convert to RGBA texture format. +[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8_PC(uint8(127.5*(1 + pattern)), 1); +end + +function layer = updateColour(layer, colour) +layer.maxColour = [colour 1]; +end + +function layer = updateAzi(layer, aziRange) +layer.size(1) = abs(diff(aziRange)); +layer.texOffset(1) = mean(aziRange); +end + +function layer = updateAlt(layer, altRange) +layer.size(2) = abs(diff(altRange)); +layer.texOffset(2) = mean(altRange); +end + +function layer = updateShow(layer, show) +layer.show = show; +end + +function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) +gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; +cellSize = gridDims./fliplr(nRowsByCols); +nCols = nRowsByCols(2) + 1; +nRows = nRowsByCols(1) + 1; +midAzi = mean(aziRange); +midAlt = mean(altRange); +%% base layer to imprint area the checker can draw on (by applying an alpha mask) +stencil.texOffset = [midAzi midAlt]; +stencil.size = gridDims; +if any(sizeFrac < 1) + %% layers for lines making up mask grid - masks out margins around each square + % make layers for vertical lines + if nCols > 1 + azi = linspace(aziRange(1), aziRange(2), nCols); + else + azi = midAzi; + end + collayers = repmat(mask, 1, nCols); + for vi = 1:nCols + collayers(vi).texOffset = [azi(vi) midAlt]; + end + [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); + % make layers for horizontal lines + if nRows > 1 + alt = linspace(altRange(1), altRange(2), nRows); + else + alt = midAlt; + end + rowlayers = repmat(mask, 1, nRows); + for hi = 1:nRows + rowlayers(hi).texOffset = [midAzi alt(hi)]; + end + [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); + %% combine the layers and return + layers = [stencil collayers rowlayers]; +else % no mask grid needed as each cell is full size + layers = stencil; +end + +end \ No newline at end of file diff --git a/cortexlab/+vis/checker6.m b/cortexlab/+vis/checker6.m new file mode 100644 index 00000000..4901b88c --- /dev/null +++ b/cortexlab/+vis/checker6.m @@ -0,0 +1,126 @@ +function elem = checker3(t) +%vis.checker A grid of rectangles +% Detailed explanation goes here + +elem = t.Node.Net.subscriptableOrigin('checker'); + +%% make initial layers to be used as templates +maskTemplate = vis.emptyLayer(); +maskTemplate.isPeriodic = false; +maskTemplate.interpolation = 'nearest'; +maskTemplate.show = true; +maskTemplate.colourMask = [false false false true]; + +maskTemplate.textureId = 'checkerMaskPixel'; +[maskTemplate.rgba, maskTemplate.rgbaSize] = vis.rgba(0, 0); +maskTemplate.blending = '1-source'; % allows us to lay down our zero alpha value + +stencilTemplate = maskTemplate; +stencilTemplate.textureId = 'checkerStencilPixel'; +[stencilTemplate.rgba, stencilTemplate.rgbaSize] = vis.rgba(1, 1); +stencilTemplate.blending = 'none'; + +% pattern layer uses the alpha values laid down by mask layers +patternLayer = vis.emptyLayer(); +patternLayer.textureId = sprintf('~checker%i', randi(2^32)); +patternLayer.isPeriodic = false; +patternLayer.interpolation = 'nearest'; +patternLayer.blending = 'destination'; % use the alpha mask gets laid down before this + +%% construct signals used to assemble layers +% N rows by cols signal is derived from the size of the pattern array but +% we skip repeats so that pattern changes don't update the mask layers +% unless the size has acutally changed +nRowsByCols = elem.pattern.flatten().map(@size).skipRepeats(); +aziRange = elem.azimuthRange.flatten(); +altRange = elem.altitudeRange.flatten(); +sizeFrac = elem.rectSizeFrac.flatten(); +% signal containing the masking layers +gridMaskLayers = mapn(nRowsByCols, aziRange, altRange, sizeFrac, ... + maskTemplate, stencilTemplate, @gridMask); +% signal contain the checker layer +checkerLayer = scan(elem.pattern.flatten(), @updatePattern,... + elem.colour.flatten(), @updateColour,... + elem.azimuthRange.flatten(), @updateAzi,... + elem.altitudeRange.flatten(), @updateAlt,... + elem.show.flatten(), @updateShow,... + patternLayer); % initial value +%% set default attribute values +elem.layers = [gridMaskLayers checkerLayer]; +elem.azimuthRange = [-135 135]; +elem.altitudeRange = [-37.5 37.5]; +elem.rectSizeFrac = [1 1]; % horizontal and vertical size of each rectangle +elem.pattern = [ + 1 -1 1 -1 + -1 0 0 0 + 1 0 0 0 + -1 1 -1 1]; + elem.show = true; +end + +%% helper functions +function layer = updatePattern(layer, pattern) +% map pattern from -1 -> 1 range to 0->255, cast to 8 bit integers, then +% convert to RGBA texture format. +[layer.rgba, layer.rgbaSize] = vis.rgbaFromUint8(uint8(127.5*(1 + pattern)), 1); +end + +function layer = updateColour(layer, colour) +layer.maxColour = [colour 1]; +end + +function layer = updateAzi(layer, aziRange) +layer.size(1) = abs(diff(aziRange)); +layer.texOffset(1) = mean(aziRange); +end + +function layer = updateAlt(layer, altRange) +layer.size(2) = abs(diff(altRange)); +layer.texOffset(2) = mean(altRange); +end + +function layer = updateShow(layer, show) +layer.show = show; +end + +function layers = gridMask(nRowsByCols, aziRange, altRange, sizeFrac, mask, stencil) +gridDims = [abs(diff(aziRange)) abs(diff(altRange))]; +cellSize = gridDims./flip(nRowsByCols); +nCols = nRowsByCols(2) + 1; +nRows = nRowsByCols(1) + 1; +midAzi = mean(aziRange); +midAlt = mean(altRange); +%% base layer to imprint area the checker can draw on (by applying an alpha mask) +stencil.texOffset = [midAzi midAlt]; +stencil.size = gridDims; +if any(sizeFrac < 1) + %% layers for lines making up mask grid - masks out margins around each square + % make layers for vertical lines + if nCols > 1 + azi = linspace(aziRange(1), aziRange(2), nCols); + else + azi = midAzi; + end + collayers = repmat(mask, 1, nCols); + for vi = 1:nCols + collayers(vi).texOffset = [azi(vi) midAlt]; + end + [collayers.size] = deal([(1 - sizeFrac(1))*cellSize(1) gridDims(2)]); + % make layers for horizontal lines + if nRows > 1 + alt = linspace(altRange(1), altRange(2), nRows); + else + alt = midAlt; + end + rowlayers = repmat(mask, 1, nRows); + for hi = 1:nRows + rowlayers(hi).texOffset = [midAzi alt(hi)]; + end + [rowlayers.size] = deal([gridDims(1) (1 - sizeFrac(2))*cellSize(2)]); + %% combine the layers and return + layers = [stencil collayers rowlayers]; +else % no mask grid needed as each cell is full size + layers = stencil; +end + +end \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..0e01d560 --- /dev/null +++ b/readme.md @@ -0,0 +1,94 @@ +---------- +# Rigbox + +Rigbox is a (mostly) object-oriented MATLAB software package for designing and controlling behavioural experiments. Principally, the steering wheel setup we developed to probe mouse behaviour. It requires two computers, one for stimulus presentation ('the stimulus server') and another for controlling and monitoring the experiment ('mc'). + +## Getting Started + +The following is a brief description of how to install Rigbox on your experimental rig. However detailed, step-by-step information can be found [here](https://www.ucl.ac.uk/cortexlab/tools/wheel). + +## Prerequisites +Rigbox has a number of essential and optional software dependencies, listed below: +* Windows 7 or later +* [MATLAB](https://uk.mathworks.com/downloads/web_downloads/?s_iid=hp_ff_t_downloads) 2016a or later + * [Psychophsics Toolbox](https://github.com/Psychtoolbox-3/Psychtoolbox-3/releases) v3 or later + * [NI-DAQmx support package](https://uk.mathworks.com/hardware-support/nidaqmx.html) + * [GUI Layout Toolbox](https://uk.mathworks.com/matlabcentral/fileexchange/47982-gui-layout-toolbox) v2 or later + * Data Acquisition Toolbox + * Signal Processing Toolbox + * Instrument Control Toolbox + +Additionally, Rigbox works with a number of extra repositories: +* [Signals](https://github.com/dendritic/signals) (for running bespoke experiment designs) + * Statistics and Machine Learning Toolbox + * [Microsoft Visual C++ Redistributable for Visual Studio 2015](https://www.microsoft.com/en-us/download/details.aspx?id=48145) +* [Alyx-matlab](https://github.com/cortex-lab/alyx-matlab) (for registering data to, and retrieving from, an Alyx database + * [Missing HTTP v1](https://github.com/psexton/missing-http/releases/tag/missing-http-1.0.0) or later + * [JSONlab](https://uk.mathworks.com/matlabcentral/fileexchange/33381-jsonlab--a-toolbox-to-encode-decode-json-files) + +## Installing +1. To install Rigbox, first ensure that all the above dependencies are installed. +2. Pull the latest Rigbox-lite branch. This branch is currently the 'cleanest' one, however in the future it will likely be merged with the master branch. +3. In MATLAB run 'addRigboxPaths.m' and restart the program. +4. Set the correct paths by following the instructions in Rigbox\+dat\paths.m on both computers. +5. On the stimulus server, load the hardware.mat file in Rigbox\Repositories\code\config\exampleRig and edit according to your specific hardware setup (link to detailed instructions above, under 'Getting started'). + +## Running an experiment + +On the stimulus server, run: +> srv.expServer + +On the mc computer, run: +> mc + +This opens a GUI that will allow you to choose a subject, edit some of the experimental parameters and press 'Start' to begin the basic steering wheel task on the stimulus server. + +# Code organization +Below is a list of the principle directories and their general purpose. +## +dat +The data package contains all the code pertaining to the organization and logging of data. It contains functions that generate and parse unique experiment reference ids, that return the file paths where subject data and rig configuration information is stored. Other functions include those that manage experimental log entries and parameter profiles. This package is akin to a lab notebook. + +## +eui +This package contains the code pertaining to the Rigbox user interface. It contains code for constructing the mc GUI (MControl.m), and for plotting live experiment data or generating tables for viewing experiment parameters and subject logs. This package is exclusively used by the mc computer. + +## +exp +The experiment package is for the initialization and running of behavioural experiments. These files define a framework for event- and state-based experiments. Actions such as visual stimulus presentation or reward delivery can be controlled by experiment phases, and experiment phases are managed by an event-handling system (e.g. ResponseEventInfo). + +The package also triggers auxiliary services (e.g. starting remote acquisition software), and loads parameters for presentation each trail. The principle two base classes that control these experiments are Experiment and its Signals counterpart, SignalsExp. + +This package is almost exclusively used by the stimulus server + +## +hw +The hardware package is for configuring, and interfacing with, hardware such as screens, DAQ devices, weighing scales and lick detectors. Withing this is the +ptb package which contains classes for interacting with PsychToolbox. + +The devices file loads and initializes all the hardware for a specific experimental rig. There are also classes for unifying system and hardware clocks. + +## +psy +This package contains simple functions for processing and plotting psychometric data + +## +srv +This package contains the expServer function as well as classes that manage communications between rig computers. + +The Service base class allows the stimulus server to start and stop auxiliary acquisition systems at the beginning and end of experiments + +The StimulusControl class is used by the mc computer to manage the stimulus server + +NB: Lower-level communication protocol code is found in the +io package + +## cb-tools\burgbox +Burgbox contains many simply helper functions that are used by the main packages. Within this directory are further packages: +* +bui --- Classes for managing graphics objects such as axes +* +aud --- Functions for interacting with PsychoPortAudio +* +file --- Functions for simplifying directory and file management, for instance returning the modified dates for specified folders or filtering an array of directories by those that exist +* +fun --- Convenience functions for working with function handles in MATLAB, e.g. functions similar cellfun that are agnostic of input type, or ones that cache function outputs +* +img --- Classes that deal with image and frame data (DEPRICATED) +* +io --- Lower-level communications classes for managing UDP and TCP/IP Web sockets +* +plt --- A few small plotting functions (DEPRICATED) +* +vis --- Functions for returning various windowed visual stimuli (i.g. gabor gratings) +* +ws --- An early Web socket package using SuperWebSocket (DEPRICATED) + +## cortexlab +The cortexlab directory is intended for functions and classes that are rig or lab specific, for instance code that allows compatibility with other stimulus presentation packages used by cortexlab (i.e. MPEP) + +## Authors +The majority of the Rigbox code was written by [Chris Burgess](https://github.com/dendritic/) in 2013. It is now maintained and developed by a number of people at [CortexLab](https://www.ucl.ac.uk/cortexlab). diff --git a/readme.txt b/readme.txt deleted file mode 100644 index 501b335e..00000000 --- a/readme.txt +++ /dev/null @@ -1,22 +0,0 @@ -In order to install on any computer: - -- run the Rigbox/addRigboxPaths.m -- install the GUI Layout Toolbox from here: https://uk.mathworks.com/matlabcentral/fileexchange/47982-gui-layout-toolbox -- double check that the added paths (including those to the Toolbox) are above the paths to zserver - -Main changes: - -- handles to objects are no longer numerical -- the UI is now using the most recent version of GUI Layout Toolbox -- all code now works in the latest version of MATLAB - -Little fixes: - -- checkbox in param editor now functions correctly (added line 382 +eui.ParamEditor/addParamUI) -- more documentation, particularly for the UI elements -- saved parameters dropdown now ordered in mc - -To do: - -- rename the cortexlab folder and move +exp to ExpDefinitions -- add specific path for ExpDefinitions in dat.paths (see line 115 in MControl) \ No newline at end of file