diff --git a/README.md b/README.md index 85d3beb5..1ebe012c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ - [CPP_BIDS](#cpp_bids) - [Usage](#usage) + - [To save events.tsv file](#to-save-eventstsv-file) - [Functions descriptions](#functions-descriptions) - [userInputs](#userinputs) - [createFilename](#createfilename) @@ -24,6 +25,8 @@ A set of function for matlab and octave to create [BIDS-compatible](https://bids ## Usage +### To save events.tsv file + ```matlab % define the folder where the data will be saved @@ -36,14 +39,12 @@ expParameters.task = 'testtask'; % expParameters = userInputs; % or declare it directly -expParameters.subjectGrp = ''; expParameters.subjectNb = 1; -expParameters.sessionNb = 1; expParameters.runNb = 1; % by default we assume you are running things on a behavioral PC with no eyetracker -cfg.eyeTracker = false; -cfg.testingDevice = 'PC'; +% cfg.eyeTracker = false; +% cfg.testingDevice = 'PC'; % if the testing device is set to 'PC' then the data will be saved in the `beh` folder % if set to 'mri' then the data will be saved in the `func` folder @@ -54,34 +55,88 @@ cfg.testingDevice = 'PC'; % create the filenames: this include a step to check that all the information is there (checkCFG) [cfg, expParameters] = createFilename(cfg, expParameters); -% initialize the events files with the typical BIDS -% columns (onsets, duration, trial_type) -% and add some more in this case (Speed and is_Fixation) -logFile = saveEventsFile('open', expParameters, [], 'Speed', 'is_Fixation'); +% initialize the events files with the typical BIDS columns (onsets, duration, trial_type) +% logFile = saveEventsFile('open', expParameters); -% to initialize a stim file in case you want to store the info about the stimuli in it -stimFile = saveEventsFile('open_stim', expParameters, []); +% You can add some more in this case (Speed and is_Fixation) +logFile.extraColumns = {'Speed', 'is_Fixation'}; +logFile = saveEventsFile('open', expParameters, logFile); -% create the information about 2 events that we want to save +% The information about 2 events that we want to save +% NOTE : If the user DOES NOT provide `onset`, `trial_type`, this events will be skipped. logFile(1,1).onset = 2; logFile(1,1).trial_type = 'motion_up'; logFile(1,1).duration = 1; -logFile(1,1).speed = 2; -logFile(1,1).is_fixation = true; +logFile(1,1).Speed = 2; +logFile(1,1).is_Fixation = true; logFile(2,1).onset = 3; logFile(2,1).trial_type = 'static'; logFile(2,1).duration = 4; -logFile(2,1).is_fixation = 3; +logFile(2,1).is_Fixation = 3; % add those 2 events to the events.tsv file -saveEventsFile('save', expParameters, logFile, 'speed', 'is_fixation'); +saveEventsFile('save', expParameters, logFile); % close the file saveEventsFile('close', expParameters, logFile); ``` +If you want to save more complex events.tsv file you can save several columns at once. + +```matlab +expParameters.subjectNb = 1; +expParameters.runNb = 1; +expParameters.task = 'testtask'; +expParameters.outputDir = outputDir; + +cfg.testingDevice = 'mri'; + +[cfg, expParameters] = createFilename(cfg, expParameters); + +% You can specify how many columns we want for each variable +% will set 1 columns with name Speed +% will set 12 columns with names LHL24-01, LHL24-02, ... +% will set 1 columns with name is_Fixation + +logFile.extraColumns.Speed.length = 1; +logFile.extraColumns.LHL24.length = 12; +logFile.extraColumns.is_Fixation.length = 1; + +logFile = saveEventsFile('open', expParameters, logFile); + +logFile(1, 1).onset = 2; +logFile(end, 1).trial_type = 'motion_up'; +logFile(end, 1).duration = 3; +logFile(end, 1).Speed = 2; +logFile(end, 1).is_Fixation = true; +logFile(end, 1).LHL24 = 1:12; + +saveEventsFile('save', expParameters, logFile); + +saveEventsFile('close', expParameters, logFile); + +``` + +If you have many columns to define but only a few with several columns, you can do this: + +```matlab +% define the extra columns: they will be added to the tsv files in the order the user input them +logFile.extraColumns = {'Speed', 'is_Fixation'}; + +[cfg, expParameters] = createFilename(cfg, expParameters); + +% dummy call to initialize the logFile variable +logFile = saveEventsFile('open', expParameters, logFile); + +% set the real length we really want +logFile.extraColumns.Speed.length = 12; + +% actual inititalization +logFile = saveEventsFile('open', expParameters, logFile); +``` + ## Functions descriptions ### userInputs @@ -127,7 +182,11 @@ For the moment the date of acquisition is appended to the filename Function to save output files for events that will be BIDS compliant. +If the user DOES NOT provide `onset`, `trial_type`, this events will be skipped. `duration` will be set to "NaN" if +no value is provided. + ### checkCFG + Check that we have all the fields that we need in the experiment parameters. ## How to install diff --git a/checkCFG.m b/checkCFG.m index 91bc5b1b..6bc03e8f 100644 --- a/checkCFG.m +++ b/checkCFG.m @@ -2,19 +2,19 @@ % check that we have all the fields that we need in the experiment parameters %% set the expParameters defaults - + fieldsToSet.verbose = 0; fieldsToSet.outputDir = fullfile( ... - fileparts(mfilename('fullpath')), ... - '..', ... - 'output'); + fileparts(mfilename('fullpath')), ... + '..', ... + 'output'); fieldsToSet.subjectGrp = []; % in case no group was provided - fieldsToSet.sessionNb = []; % in case no session was provided + fieldsToSet.sessionNb = 1; % in case no session was provided fieldsToSet.askGrpSess = [true true]; - + % BIDS - + % dataset description json % required fieldsToSet.bids.datasetDescription.json.Name = ''; @@ -27,7 +27,7 @@ fieldsToSet.bids.datasetDescription.json.Funding = {''}; fieldsToSet.bids.datasetDescription.json.ReferencesAndLinks = {''}; fieldsToSet.bids.datasetDescription.json.DatasetDOI = ''; - + % mri % for json fieldsToSet.MRI.repetitionTime = []; @@ -37,38 +37,39 @@ fieldsToSet.MRI.rec = []; % reconstruction of fMRI images fieldsToSet.MRI.echo = []; % echo fMRI images fieldsToSet.MRI.acq = []; % acquisition of fMRI images - - %% loop through the defaults and set them in expParameters if they don't exist - names = fieldnames(fieldsToSet); - - for i = 1:numel(names) - expParameters = setFieldToIfNotPresent(... - expParameters, ... - names{i}, ... - getfield(fieldsToSet, names{i})); %#ok - end - + + expParameters = setDefaults(expParameters, fieldsToSet); + %% set the cfg defaults - - clear fieldsToSet + + clear fieldsToSet; fieldsToSet.testingDevice = 'pc'; fieldsToSet.eyeTracker = false; - - % loop through the defaults and set them in cfg if they don't exist + + cfg = setDefaults(cfg, fieldsToSet); + +end + +function structure = setDefaults(structure, fieldsToSet) + % loop through the defaults fiels to set and update if they don't exist + names = fieldnames(fieldsToSet); - + for i = 1:numel(names) - cfg = setFieldToIfNotPresent(... - cfg, ... + + thisField = fieldsToSet.(names{i}); + + structure = setFieldToIfNotPresent( ... + structure, ... names{i}, ... - getfield(fieldsToSet, names{i})); %#ok + thisField); + end end - -function struct = setFieldToIfNotPresent(struct, fieldName, value) - if ~isfield(struct, fieldName) - struct = setfield(struct, fieldName, value); %#ok +function structure = setFieldToIfNotPresent(structure, fieldName, value) + if ~isfield(structure, fieldName) + structure.(fieldName) = value; end -end \ No newline at end of file +end diff --git a/createFilename.m b/createFilename.m index 1d793e05..0135ad9e 100644 --- a/createFilename.m +++ b/createFilename.m @@ -21,6 +21,23 @@ [cfg, expParameters] = checkCFG(cfg, expParameters); + if ~isfield(expParameters, 'task') + error('createFilename: missing a task name. i.e expParameters.task'); + end + + expParameters = getModality(cfg, expParameters); + + expParameters = createDirectories(cfg, expParameters); + + expParameters = setSuffixes(expParameters); + + expParameters = setFilenames(cfg, expParameters); + + talkToMe(cfg, expParameters); + +end + +function expParameters = getModality(cfg, expParameters) switch lower(cfg.testingDevice) case 'pc' modality = 'beh'; @@ -35,16 +52,8 @@ otherwise modality = 'beh'; end - expParameters.modality = modality; - - expParameters = createDirectories(cfg, expParameters); - - expParameters = setSuffixes(expParameters); - - expParameters = setFilenames(cfg, expParameters); - - talkToMe(cfg, expParameters); + expParameters.modality = modality; end function [subjectGrp, subjectNb, sessionNb, modality] = extractInput(expParameters) @@ -54,6 +63,10 @@ sessionNb = expParameters.sessionNb; modality = expParameters.modality; + if isempty(sessionNb) + sessionNb = 1; + end + end function expParameters = createDirectories(cfg, expParameters) diff --git a/miss_hit.cfg b/miss_hit.cfg index bf034a66..fce7fa45 100644 --- a/miss_hit.cfg +++ b/miss_hit.cfg @@ -1,3 +1,3 @@ line_length: 100 -regex_function_name: "[a-z]+(([A-Z]|[0-9]){1}[a-z]+)*" +regex_function_name: "[a-z]+(([A-Z]){1}[A-Za-z]+)*" suppress_rule: "copyright_notice" \ No newline at end of file diff --git a/saveEventsFile.m b/saveEventsFile.m index 3daeee8a..98f14ee8 100644 --- a/saveEventsFile.m +++ b/saveEventsFile.m @@ -1,4 +1,4 @@ -function [logFile] = saveEventsFile(action, expParameters, logFile, varargin) +function [logFile] = saveEventsFile(action, expParameters, logFile) % Function to save output files for events that will be BIDS compliant. % % INPUTS @@ -21,24 +21,6 @@ % logFile(2,1).is_fixation = 3; % % - % logFile: - % When you want to save your data logFile contains the data you want to save. - % The logFile variable that contains the n events you want to % save must be a nx1 structure. - % Each field will be saved in a separate column. - % - % example: - % logFile(1,1).onset = 2; - % logFile(1,1).trial_type = 'motion_up'; - % logFile(1,1).duration = 1; - % logFile(1,1).speed = 2; - % logFile(1,1).is_fixation = true; - % - % logFile(2,1).onset = 3; - % logFile(2,1).trial_type = 'static'; - % logFile(2,1).duration = 4; - % logFile(2,1).is_fixation = 3; - % - % % action: % - 'open': will create the file ID and return it in logFile.fileID using the information in % the expParameters structure. This file ID is then reused when calling that function @@ -59,86 +41,114 @@ % to true then this will tell you where the file is located. % % See test_saveEventsFile in the test folder for more details on how to use it. - + + if nargin < 1 + errror('Missing action input'); + end + if nargin < 3 || isempty(logFile) - logFile = struct(); + logFile = checklLogFile('init'); end - + switch action - + case 'open' - + logFile.filename = expParameters.fileName.events; - - logFile = initializeFile(expParameters, logFile, varargin); - + + logFile = initializeFile(expParameters, logFile); + case 'open_stim' - + logFile.filename = expParameters.fileName.stim; - - logFile = initializeFile(expParameters, logFile, varargin); - + + logFile = initializeFile(expParameters, logFile); + case 'save' - - if ~isstruct(logFile) || size(logFile, 2) > 1 - error('The variable containing the n events to save must be a nx1 structure.'); - end - + + checklLogFile('checkID', logFile); + checklLogFile('type&size', logFile); + % appends to the logfile all the data stored in the structure % first with the standard BIDS data and then any extra things for iEvent = 1:size(logFile, 1) - + onset = checkInput(logFile(iEvent).onset); duration = checkInput(logFile(iEvent).duration); trial_type = checkInput(logFile(iEvent).trial_type); - - fprintf(logFile(1).fileID, '%f\t%s\t%f\t', ... - onset, ... - trial_type, ... - duration); - - for iExtraColumn = 1:numel(varargin) - - % if the field we are looking for does not exist or is empty in the - % action logFile structure we will write a NaN otherwise we - % write its content - - data = 'NA'; - if isfield(logFile, varargin{iExtraColumn}) - data = getfield(logFile(iEvent), varargin{iExtraColumn}); %#ok - end - - data = checkInput(data); - - if ischar(data) - fprintf(logFile(1).fileID, '%s\t', data); - else - fprintf(logFile(1).fileID, '%f\t', data); - end - + + if any(isnan([onset trial_type])) || ... + any(isempty([onset trial_type])) || ... + any(strcmp({onset, trial_type}, 'NA')) + + warning('\nSkipping saving this event.\n onset: %f \n trial_type: %s\n', ... + onset, ... + trial_type); + + else + + fprintf(logFile(1).fileID, '%f\t%s\t%f\t', ... + onset, ... + trial_type, ... + duration); + + printExtraColumns(logFile, iEvent); + + fprintf(logFile(1).fileID, '\n'); + end - - fprintf(logFile(1).fileID, '\n'); end - + case 'close' - + + checklLogFile('checkID', logFile); + % close txt log file fclose(logFile(1).fileID); - + fprintf(1, '\nData were saved in this file:\n\n%s\n\n', ... fullfile( ... expParameters.subjectOutputDir, ... expParameters.modality, ... logFile.filename)); - - + + otherwise + + errorSaveEventsFile('unknownActionType'); + end - + + logFile = resetLogFileVar(logFile); + end -function logFile = initializeFile(expParameters, logFile, varargin) - +function logFile = checklLogFile(action, logFile) + + switch action + + case 'init' + + logFile = struct('filename', [], 'extraColumns', cell(1)); + logFile(1).filename = ''; + + case 'checkID' + + if ~isfield(logFile(1), 'fileID') || isempty(logFile(1).fileID) + errorSaveEventsFile('missingFileID'); + end + + case 'type&size' + + if ~isstruct(logFile) || size(logFile, 2) > 1 + errorSaveEventsFile('wrongFileID'); + end + + end + +end + +function logFile = initializeFile(expParameters, logFile) + % Initialize txt logfiles and empty fields for the standard BIDS % event file logFile.fileID = fopen( ... @@ -147,29 +157,173 @@ expParameters.modality, ... logFile.filename), ... 'w'); - + % print the basic BIDS columns fprintf(logFile.fileID, '%s\t%s\t%s\t', 'onset', 'trial_type', 'duration'); - - % print any extra column specified by the user - % also prepare an empty field in the structure to collect data - % for those - for iExtraColumn = 1:numel(varargin{1}) - fprintf(logFile.fileID, '%s\t', varargin{1}{iExtraColumn}); - end - + + logFile = printHeaderExtraColumns(logFile); + % next line so we start printing at the right place fprintf(logFile.fileID, '\n'); - + +end + +function [namesExtraColumns, logFile] = returnNamesExtraColumns(logFile) + + namesExtraColumns = []; + + % convert the cell of column name into a structure + if iscell(logFile(1).extraColumns) + tmp = struct(); + for iExtraColumn = 1:numel(logFile(1).extraColumns) + extraColumnName = logFile(1).extraColumns{iExtraColumn}; + tmp.(extraColumnName) = struct('length', 1); + end + logFile(1).extraColumns = tmp; + end + + if isfield(logFile, 'extraColumns') && ~isempty(logFile(1).extraColumns) + namesExtraColumns = fieldnames(logFile(1).extraColumns); + end + end +function logFile = printHeaderExtraColumns(logFile) + % print any extra column specified by the user + + [namesExtraColumns, logFile] = returnNamesExtraColumns(logFile); + + for iExtraColumn = 1:numel(namesExtraColumns) -function data = checkInput(data) - - if ischar(data) && isempty(data) || strcmp(data, '') + nbCol = returnNbColumns(logFile, namesExtraColumns{iExtraColumn}); + + if ~isfield(logFile(1).extraColumns.(namesExtraColumns{iExtraColumn}), 'length') + logFile(1).extraColumns.(namesExtraColumns{iExtraColumn}).length = nbCol; + end + + for iColNb = 1:nbCol + + if nbCol == 1 + headerName = sprintf('%s', namesExtraColumns{iExtraColumn}); + else + headerName = sprintf('%s-%02.0f', namesExtraColumns{iExtraColumn}, iColNb); + end + + fprintf(logFile.fileID, '%s\t', headerName); + + end + + end + +end + +function nbCol = returnNbColumns(logFile, nameExtraColumn) + + thisExtraColumn = logFile(1).extraColumns.(nameExtraColumn); + + nbCol = 1; + + if isfield(thisExtraColumn, 'length') + nbCol = thisExtraColumn.length; + end +end + +function data = checkInput(data, expectedLength) + % check the data to write + % default will be 'NA' for chars and NaN for numeric data + % for numeric data that don't have the expected length, it will be padded with NaNs + + if nargin < 2 + expectedLength = []; + end + + if ischar(data) && isempty(data) || strcmp(data, ' ') data = 'NA'; elseif isempty(data) data = nan; end - -end \ No newline at end of file + + if islogical(data) && data + data = 'true'; + elseif islogical(data) && ~data + data = 'false'; + end + + if ~isempty(expectedLength) && isnumeric(data) && max(size(data)) < expectedLength + padding = expectedLength - max(size(data)); + data(end + 1:end + padding) = nan(1, padding); + end + +end + +function printExtraColumns(logFile, iEvent) + + namesExtraColumns = returnNamesExtraColumns(logFile); + + for iExtraColumn = 1:numel(namesExtraColumns) + + nbCol = returnNbColumns(logFile, namesExtraColumns{iExtraColumn}); + + % if the field we are looking for does not exist or is empty in the + % action logFile structure we will write a NaN otherwise we + % write its content + data = 'NA'; + if isfield(logFile, namesExtraColumns{iExtraColumn}) + data = logFile(iEvent).(namesExtraColumns{iExtraColumn}); + end + + data = checkInput(data, nbCol); + + if any(isnan(data)) + warning('Missing some %s data for this event.', namesExtraColumns{iExtraColumn}); + disp(logFile(iEvent)); + elseif all(isnan(data)) || strcmp(data, 'NA') + warning('Missing %s data for this event.', namesExtraColumns{iExtraColumn}); + disp(logFile(iEvent)); + end + + if ischar(data) + fprintf(logFile(1).fileID, '%s\t', data); + else + fprintf(logFile(1).fileID, '%f\t', data); + end + + end + +end + +function logFile = resetLogFileVar(logFile) + + logFile(2:end) = []; + + namesColumns = {'onset', 'duration', 'trial_type'}; + namesExtraColumns = returnNamesExtraColumns(logFile); + namesColumns = cat(2, namesColumns, namesExtraColumns'); + + for iColumn = 1:numel(namesColumns) + + if isfield(logFile, namesColumns{iColumn}) + logFile = rmfield(logFile, namesColumns{iColumn}); + end + + end + +end + +function errorSaveEventsFile(identifier) + + switch identifier + case 'unknownActionType' + errorStruct.message = 'unknown action for saveEventsFile'; + + case 'missingFileID' + errorStruct.message = 'logFile must contain a valid fileID field'; + + case 'wrongFileID' + errorStruct.message = 'logFile must be a nx1 structure'; + + end + + errorStruct.identifier = ['saveEventsFile:' identifier]; + error(errorStruct); +end diff --git a/tests/miss_hit.cfg b/tests/miss_hit.cfg index bbb08b15..30aa3b8c 100644 --- a/tests/miss_hit.cfg +++ b/tests/miss_hit.cfg @@ -1,3 +1,3 @@ line_length: 100 -regex_function_name: "((test_[a-z]+)|[a-z]+)(([A-Z]|[0-9]){1}[a-z]+)*" +regex_function_name: "((test_[a-z]+)|[a-z]+)(([A-Z]){1}[A-Za-z]+)*" suppress_rule: "copyright_notice" \ No newline at end of file diff --git a/tests/test_createFilename.m b/tests/test_createFilename.m index 6ddcbb6b..a9fec298 100644 --- a/tests/test_createFilename.m +++ b/tests/test_createFilename.m @@ -2,14 +2,13 @@ function test_createFilename() % test for filename creation and their directories %% PC + fprintf('\n\n--------------------------------------------------------------------\n\n'); outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); %%% set up part - expParameters.subjectGrp = ''; expParameters.subjectNb = 1; - expParameters.sessionNb = 1; expParameters.runNb = 1; expParameters.task = 'testtask'; expParameters.outputDir = outputDir; @@ -42,6 +41,7 @@ function test_createFilename() assert(strcmp(expParameters.fileName.stim, stimFilename)); %% fMRI and eye tracker + fprintf('\n\n--------------------------------------------------------------------\n\n'); clear; @@ -84,6 +84,7 @@ function test_createFilename() assert(strcmp(expParameters.fileName.eyetracker, eyetrackerFilename)); %% EEG + fprintf('\n\n--------------------------------------------------------------------\n\n'); clear; diff --git a/tests/test_saveEventsFile.m b/tests/test_saveEventsFile.m deleted file mode 100644 index 75464372..00000000 --- a/tests/test_saveEventsFile.m +++ /dev/null @@ -1,101 +0,0 @@ -function test_saveEventsFile() - % test for events.tsv file creation - - clear - - outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); - - %% set up - - expParameters.subjectGrp = ''; - expParameters.subjectNb = 1; - expParameters.sessionNb = 1; - expParameters.runNb = 1; - expParameters.task = 'testtask'; - expParameters.outputDir = outputDir; - - cfg.testingDevice = 'mri'; - - [cfg, expParameters] = createFilename(cfg, expParameters); - - %% create the events file - - logFile = saveEventsFile('open', expParameters, [], 'Speed', 'is_Fixation'); - - %%% test section - - % test data - funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); - eventFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-' ... - expParameters.date '.tsv']; - - % check that the file has the right path and name - assert(exist(fullfile(funcDir, eventFilename), 'file') == 2); - - - %% write things in it - - logFile(1).onset = 1; - logFile(1).trial_type = 'motion_up'; - logFile(1).duration = 1; - logFile(1).speed = []; - logFile(1).is_fixation = 'true'; - - saveEventsFile('save', expParameters, logFile, 'speed', 'is_fixation'); - - logFile(1, 1).onset = 2; - logFile(1, 1).trial_type = 'motion_up'; - logFile(1, 1).duration = 1; - logFile(1, 1).speed = 2; - logFile(1, 1).is_fixation = true; - - logFile(2, 1).onset = 3; - logFile(2, 1).trial_type = 'static'; - logFile(2, 1).duration = 4; - logFile(2, 1).is_fixation = 3; - - logFile(3, 1).onset = []; - logFile(3, 1).trial_type = ''; - logFile(3, 1).duration = []; - - - saveEventsFile('save', expParameters, logFile, 'speed', 'is_fixation'); - - % close the file - saveEventsFile('close', expParameters, logFile); - - - %%% test section - - % check the extra columns of the header and some of the content - - FID = fopen(fullfile(funcDir, eventFilename), 'r'); - C = textscan(FID, '%s%s%s%s%s', 'Delimiter', '\t', 'EndOfLine', '\n'); - - % check header - assert(isequal(C{4}{1}, 'Speed')); - - % check that empty values are entered as NaN - assert(isequal(C{4}{2}, 'NaN')); - - % check that missing fields are entered as NaN - assert(isequal(C{4}{4}, 'NaN')); - - % check values entered properly - assert(isequal(str2double(C{4}{3}), 2)); - - % check values entered properly - assert(isequal(str2double(C{5}{4}), 3)); - - - - - - % stimFile = saveEventsFile('open_stim', expParameters, []); - - % stimFileName = fullfile( ... - % expParameters.outputDir, ... - % expParameters.modality, ... - % expParameters.fileName.stim); - -% assert(exist(stimFileName, 'file') == 2); \ No newline at end of file diff --git a/tests/test_saveEventsFileOpen.m b/tests/test_saveEventsFileOpen.m new file mode 100644 index 00000000..c4435567 --- /dev/null +++ b/tests/test_saveEventsFileOpen.m @@ -0,0 +1,95 @@ +function test_saveEventsFileOpen() + + %% Initialize file + fprintf('\n\n--------------------------------------------------------------------\n\n'); + + clear; + + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); + + %%% set up + + expParameters.subjectNb = 1; + expParameters.runNb = 1; + expParameters.task = 'testtask'; + expParameters.outputDir = outputDir; + + cfg.testingDevice = 'mri'; + + %%% do stuff + + [cfg, expParameters] = createFilename(cfg, expParameters); + + % create the events file and header + logFile = saveEventsFile('open', expParameters); + + % close the file + saveEventsFile('close', expParameters, logFile); + + %%% test section + + % test data + funcDir = fullfile(outputDir, 'source', 'sub-001', 'ses-001', 'func'); + eventFilename = ['sub-001_ses-001_task-testtask_run-001_events_date-' ... + expParameters.date '.tsv']; + + % open the file + FID = fopen(fullfile(funcDir, eventFilename), 'r'); + C = textscan(FID, repmat('%s', 1, 3), 'Delimiter', '\t', 'EndOfLine', '\n'); + + % check that the file has the right path and name + assert(exist(fullfile(funcDir, eventFilename), 'file') == 2); + + % check the extra columns of the header + assert(isequal(C{1}{1}, 'onset')); + assert(isequal(C{2}{1}, 'trial_type')); + assert(isequal(C{3}{1}, 'duration')); + + %% check header writing with extra columns + fprintf('\n\n--------------------------------------------------------------------\n\n'); + + clear; + + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); + + %%% set up + + expParameters.subjectNb = 1; + expParameters.runNb = 1; + expParameters.task = 'testtask'; + expParameters.outputDir = outputDir; + + cfg.testingDevice = 'mri'; + + % define the extra columns: they will be added to the tsv files in the order the user input them + logFile.extraColumns = {'Speed', 'is_Fixation'}; + + %%% do stuff + + [cfg, expParameters] = createFilename(cfg, expParameters); + + % create the events file and header + logFile = saveEventsFile('open', expParameters, logFile); + + % close the file + saveEventsFile('close', expParameters, logFile); + + %%% test section + + % open the file + nbExtraCol = 2; + FID = fopen(fullfile( ... + expParameters.subjectOutputDir, ... + expParameters.modality, ... + expParameters.fileName.events), ... + 'r'); + C = textscan(FID, repmat('%s', 1, nbExtraCol + 3), 'Delimiter', '\t', 'EndOfLine', '\n'); + + % check the extra columns of the header + assert(isequal(C{1}{1}, 'onset')); + assert(isequal(C{2}{1}, 'trial_type')); + assert(isequal(C{3}{1}, 'duration')); + assert(isequal(C{4}{1}, 'Speed')); + assert(isequal(C{5}{1}, 'is_Fixation')); + +end diff --git a/tests/test_saveEventsFileOpenMultiColumn.m b/tests/test_saveEventsFileOpenMultiColumn.m new file mode 100644 index 00000000..4ff7e64a --- /dev/null +++ b/tests/test_saveEventsFileOpenMultiColumn.m @@ -0,0 +1,55 @@ +function test_saveEventsFileOpenMultiColumn() + + %% check header writing with several columns for one variable + fprintf('\n\n--------------------------------------------------------------------\n\n'); + + clear; + + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); + + %%% set up + + expParameters.subjectNb = 1; + expParameters.runNb = 1; + expParameters.task = 'testtask'; + expParameters.outputDir = outputDir; + + cfg.testingDevice = 'mri'; + + [cfg, expParameters] = createFilename(cfg, expParameters); + + % define the extra columns: here we specify how many columns we want for + % each variable + logFile.extraColumns.Speed.length = 1; % will set 1 columns with name Speed + logFile.extraColumns.LHL24.length = 12; % will set 12 columns with names LHL24-01, LHL24-02, ... + logFile.extraColumns.is_Fixation = []; % will set 1 columns with name is_Fixation + + %%% do stuff + + % create the events file and header + logFile = saveEventsFile('open', expParameters, logFile); + + % close the file + saveEventsFile('close', expParameters, logFile); + + %%% test section + + % check the extra columns of the header and some of the content + nbExtraCol = ... + logFile(1).extraColumns.Speed.length + ... + logFile(1).extraColumns.LHL24.length + ... + logFile(1).extraColumns.is_Fixation.length; + FID = fopen(fullfile( ... + expParameters.subjectOutputDir, ... + expParameters.modality, ... + expParameters.fileName.events), ... + 'r'); + C = textscan(FID, repmat('%s', 1, nbExtraCol + 3), 'Delimiter', '\t', 'EndOfLine', '\n'); + + % check the extra columns of the header + assert(isequal(C{4}{1}, 'Speed')); + assert(isequal(C{5}{1}, 'LHL24-01')); + assert(isequal(C{16}{1}, 'LHL24-12')); + assert(isequal(C{17}{1}, 'is_Fixation')); + +end diff --git a/tests/test_saveEventsFileSave.m b/tests/test_saveEventsFileSave.m new file mode 100644 index 00000000..9fcca9ae --- /dev/null +++ b/tests/test_saveEventsFileSave.m @@ -0,0 +1,112 @@ +function test_saveEventsFileSave() + + %% write things in it + fprintf('\n\n--------------------------------------------------------------------\n\n'); + + clear; + + outputDir = fullfile(fileparts(mfilename('fullpath')), '..', 'output'); + + %%% set up + + expParameters.subjectNb = 1; + expParameters.runNb = 1; + expParameters.task = 'testtask'; + expParameters.outputDir = outputDir; + + cfg.testingDevice = 'mri'; + + [cfg, expParameters] = createFilename(cfg, expParameters); + + logFile.extraColumns.Speed.length = 1; + logFile.extraColumns.LHL24.length = 12; + logFile.extraColumns.is_Fixation.length = 1; + + % create the events file and header + logFile = saveEventsFile('open', expParameters, logFile); + + %%% do stuff + + % ROW 2: normal events : all info is there + logFile(1, 1).onset = 2; + logFile(end, 1).trial_type = 'motion_up'; + logFile(end, 1).duration = 3; + logFile(end, 1).Speed = 2; + logFile(end, 1).is_Fixation = true; + logFile(end, 1).LHL24 = 1:12; + + logFile = saveEventsFile('save', expParameters, logFile); + + % ROW 3: missing info (speed, LHL24) + logFile(1, 1).onset = 3; + logFile(end, 1).trial_type = 'static'; + logFile(end, 1).duration = 4; + logFile(end, 1).is_Fixation = false; + + % ROW 4: missing info (duration is missing and speed is empty) + logFile(2, 1).onset = 4; + logFile(end, 1).trial_type = 'motion_up'; + logFile(end, 1).Speed = []; + logFile(end, 1).is_Fixation = true; + logFile(end, 1).LHL24 = 1:12; + + % empty events + logFile(3, 1).onset = []; + logFile(end, 1).trial_type = []; + logFile(end, 1).duration = 3; + + logFile(4, 1).onset = 1; + logFile(end, 1).trial_type = ''; + + % ROW 5: missing info (array is not the right size) + logFile(5, 1).onset = 5; + logFile(end, 1).trial_type = 'jazz'; + logFile(end, 1).duration = 3; + logFile(end, 1).LHL24 = rand(1, 10); + + saveEventsFile('save', expParameters, logFile); + + % close the file + saveEventsFile('close', expParameters, logFile); + + %%% test section + + % check the extra columns of the header and some of the content + nbExtraCol = ... + logFile(1).extraColumns.Speed.length + ... + logFile(1).extraColumns.LHL24.length + ... + logFile(1).extraColumns.is_Fixation.length; + FID = fopen(fullfile( ... + expParameters.subjectOutputDir, ... + expParameters.modality, ... + expParameters.fileName.events), ... + 'r'); + C = textscan(FID, repmat('%s', 1, nbExtraCol + 3), 'Delimiter', '\t', 'EndOfLine', '\n'); + + % event 1/ ROW 2: check that values are entered correctly + assert(isequal(C{1}{2}, sprintf('%f', 2))); + assert(isequal(C{2}{2}, 'motion_up')); + assert(isequal(C{3}{2}, sprintf('%f', 3))); + assert(isequal(C{4}{2}, sprintf('%f', 2))); + assert(isequal(C{5}{2}, sprintf('%f', 1))); + assert(isequal(C{16}{2}, sprintf('%f', 12))); + assert(isequal(C{17}{2}, 'true')); + + % event 2 / ROW 3: missing info replaced by nans + assert(isequal(C{4}{3}, 'NaN')); + assert(isequal(C{5}{3}, 'NaN')); + assert(isequal(C{16}{3}, 'NaN')); + assert(isequal(C{17}{3}, 'false')); + + % event 3 / ROW 4: missing info (duration is missing and speed is empty) + assert(isequal(C{3}{4}, 'NaN')); + assert(isequal(C{4}{4}, 'NaN')); + + % event 4-5 / ROW 5-6: skip empty events + assert(~isequal(C{1}{5}, 'NaN')); + + % check values entered properly + assert(isequal(C{15}{5}, 'NaN')); + assert(isequal(C{16}{5}, 'NaN')); + +end diff --git a/tests/userInput_test.m b/tests/userInput_test.m index aa197617..eae757a7 100644 --- a/tests/userInput_test.m +++ b/tests/userInput_test.m @@ -3,33 +3,32 @@ expParameters = struct(); [expParameters] = userInputs(cfg, expParameters); -disp(expParameters) - +disp(expParameters); %% cfg = struct('debug', false); expParameters = struct('askGrpSess', 0); [expParameters] = userInputs(cfg, expParameters); -disp(expParameters) +disp(expParameters); %% cfg = struct('debug', false); expParameters = struct('askGrpSess', [0 0]); [expParameters] = userInputs(cfg, expParameters); -disp(expParameters) +disp(expParameters); %% cfg = struct('debug', false); expParameters = struct('askGrpSess', [0 1]); [expParameters] = userInputs(cfg, expParameters); -disp(expParameters) +disp(expParameters); %% cfg = struct('debug', false); expParameters = struct('askGrpSess', []); [expParameters] = userInputs(cfg, expParameters); -disp(expParameters) \ No newline at end of file +disp(expParameters); diff --git a/userInputs.m b/userInputs.m index 8b4a6a2c..a6433949 100644 --- a/userInputs.m +++ b/userInputs.m @@ -14,7 +14,7 @@ if nargin < 2 expParameters = []; end - + askGrpSess = [true true]; if isfield(expParameters, 'askGrpSess') && ~isempty(expParameters.askGrpSess) askGrpSess = expParameters.askGrpSess; @@ -22,7 +22,7 @@ if numel(askGrpSess) < 2 askGrpSess(2) = 1; end - + subjectGrp = ''; subjectNb = []; %#ok<*NASGU> sessionNb = [];