diff --git a/README.md b/README.md index 0cbe716..f7c028a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,32 @@ git submodule update --init --recursive --remote git pull --recurse-submodules ``` -## Running test locally +## Running tests locally with installed plugins + +From this repository root, use the local runner without opening the MATLAB project: + +```matlab +results = run_eeglab_tests; +assertSuccess(results); +``` + +The runner discovers the root sample data wrapper, all `unittesting_*` test folders except helpers, and `regression_tests`. It does not execute project startup installers, nested runners, or the legacy email reporting harness. Install the required plugins and local test datasets before running it. EEGLAB startup may query the plugin server, but the runner does not upgrade plugins. + +Use a fresh MATLAB process and a disposable checkout or copy. Legacy tests write derived data in the checkout. The runner restores the working directory, MATLAB path, default figure visibility, and EEGLAB preference file. The menu and LIMO wrappers additionally restore their base workspace and random state. Do not run multiple suites concurrently against the same data or preference file. + +Each run saves `manifest.csv`, source path information in `suite.mat`, MATLAB results, a CSV summary, JUnit XML, and individual completed results. `current.txt` identifies the active test if execution stalls. Results are retained under a new `test-results` directory by default. An empty selection is an error, not a passing run. + +```matlab +run_eeglab_tests('DiscoverOnly', true); +results = run_eeglab_tests('IncludeLimo', false); +results = run_eeglab_tests('Name', '*pop_selectevent*'); +``` + +LIMO is included by default. Its two integration cases require the local `ds002718` dataset and can take substantially longer than the other tests. They create new temporary output directories and print their locations. Existing results are never deleted. To choose explicit output directories, call `limo_preproc_stats_hw(datasetPath, newOutputPath)` or `limo_test_integration(datasetPath, newOutputPath)` with the helper folders on the MATLAB path. The integration case saves `first_level_results.mat`. Use `limo_test_second_level(preparationFile)` to rerun the second level cases into another new directory. That directory retains `integration_results.mat`, including the nine internal group outcomes and full exception objects. A prerequisite failure is identified separately from an analysis failure. + +A passing legacy wrapper does not necessarily contain a numerical assertion. The manifest records executed cases, not a guarantee of complete scientific coverage. + +## Running registered project tests Open the EEGLAB_test.prj project, then copy and paste the following code. diff --git a/eeglab_test_suite.m b/eeglab_test_suite.m new file mode 100644 index 0000000..0fc5a29 --- /dev/null +++ b/eeglab_test_suite.m @@ -0,0 +1,26 @@ +function suite = eeglab_test_suite(root, includeLimo) +% Discover local tests explicitly, without opening the MATLAB project. +% Project startup installs plugins. The local runner must use installed code. +if nargin < 1 + root = fileparts(mfilename('fullpath')); +end +if nargin < 2 + includeLimo = true; +end +suite = matlab.unittest.TestSuite.fromFile(fullfile(root, 'eeglab_tests_wrapperTest.m')); +folders = dir(fullfile(root, 'unittesting_*')); +for index = 1:numel(folders) + name = folders(index).name; + if ~folders(index).isdir || strcmp(name, 'unittesting_common') || ... + (~includeLimo && strcmp(name, 'unittesting_limo')) + continue + end + suite = [suite matlab.unittest.TestSuite.fromFolder(fullfile(root, name), ... + 'IncludingSubfolders', true)]; %#ok +end +suite = [suite matlab.unittest.TestSuite.fromFolder(fullfile(root, 'regression_tests'), ... + 'IncludingSubfolders', true)]; +keys = string({suite.Name}) + "|" + string({suite.BaseFolder}); +[~, index] = unique(keys, 'stable'); +suite = suite(index); +end diff --git a/regression_tests/test_limo_test_helpers.m b/regression_tests/test_limo_test_helpers.m new file mode 100644 index 0000000..0d5bd92 --- /dev/null +++ b/regression_tests/test_limo_test_helpers.m @@ -0,0 +1,40 @@ +function tests = test_limo_test_helpers +tests = functiontests(localfunctions); +end + +function setupOnce(testCase) +root = fileparts(fileparts(mfilename('fullpath'))); +testCase.applyFixture(matlab.unittest.fixtures.PathFixture(fullfile(root, 'unittesting_limo'))); +end + +function testRejectsExistingOutput(testCase) +folder = testCase.applyFixture(matlab.unittest.fixtures.TemporaryFolderFixture).Folder; +handle = fopen(fullfile(folder, 'participants.tsv'), 'w'); +fprintf(handle, 'participant_id\n'); +fclose(handle); +verifyError(testCase, @() limo_test_output(folder, folder), ''); +verifyTrue(testCase, isfile(fullfile(folder, 'participants.tsv'))); +end + +function testRejectsMissingDataset(testCase) +folder = testCase.applyFixture(matlab.unittest.fixtures.TemporaryFolderFixture).Folder; +verifyError(testCase, @() limo_test_output(folder, fullfile(folder, 'output')), ''); +verifyFalse(testCase, isfolder(fullfile(folder, 'output'))); +end + +function testListsFollowReturnedPathsAndGroupOrder(testCase) +folder = testCase.applyFixture(matlab.unittest.fixtures.TemporaryFolderFixture).Folder; +STUDY.datasetinfo = struct('group', {'B', 'A', 'B'}); +files.mat = cell(3,1); files.Beta = cell(3,1); files.con = cell(3,1); +for subject = 1:3 + file = fullfile(folder, sprintf('arbitrary_subject_%d.mat', subject)); + save(file, 'subject'); + files.mat{subject} = file; + files.Beta{subject} = file; + files.con{subject} = {file}; +end +lists = limo_test_lists(files, STUDY, fullfile(folder, 'lists')); +verifyEqual(testCase, readlines(lists.beta, 'EmptyLineRule', 'skip'), string(files.Beta)); +verifyEqual(testCase, readlines(lists.group_con{1}, 'EmptyLineRule', 'skip'), string(files.Beta([1 3]))); +verifyEqual(testCase, readlines(lists.group_beta{2}, 'EmptyLineRule', 'skip'), string(files.Beta(2))); +end diff --git a/regression_tests/test_local_discovery.m b/regression_tests/test_local_discovery.m new file mode 100644 index 0000000..d768d70 --- /dev/null +++ b/regression_tests/test_local_discovery.m @@ -0,0 +1,21 @@ +function tests = test_local_discovery +tests = functiontests(localfunctions); +end + +function testNoInstallersOrNestedRunners(testCase) +root = fileparts(fileparts(mfilename('fullpath'))); +testCase.applyFixture(matlab.unittest.fixtures.PathFixture(root)); +suite = eeglab_test_suite(root, false); +names = string({suite.Name}); +verifyEqual(testCase, numel(unique(names)), numel(names)); +verifyFalse(testCase, any(contains(names, {'test_add_plugins', 'example_local_test', 'runtest/'}))); +verifyTrue(testCase, any(names == "adminfunc_eeglab_execmenu_wrapperTest/test_i_pass_general")); +verifyFalse(testCase, any(startsWith(names, "test_eeglab_execmenu/"))); +verifyTrue(testCase, any(startsWith(names, "test_workspace_fixture/"))); +end + +function testLimoWrappersAreDiscoverable(testCase) +root = fileparts(fileparts(mfilename('fullpath'))); +suite = matlab.unittest.TestSuite.fromFile(fullfile(root, 'unittesting_limo', 'limo_wrapperTest.m')); +verifyEqual(testCase, sort(string({suite.Name})), sort(["limo_wrapperTest/test_preprocessing", "limo_wrapperTest/test_integration"])); +end diff --git a/regression_tests/test_workspace_fixture.m b/regression_tests/test_workspace_fixture.m new file mode 100644 index 0000000..4edf7b2 --- /dev/null +++ b/regression_tests/test_workspace_fixture.m @@ -0,0 +1,54 @@ +function tests = test_workspace_fixture +tests = functiontests(localfunctions); +end + +function setupOnce(testCase) +root = fileparts(fileparts(mfilename('fullpath'))); +testCase.applyFixture(matlab.unittest.fixtures.PathFixture(fullfile(root, 'unittesting_common'))); +end + +function testRestoresStateOnSuccess(testCase) +checkRestore(testCase, false); +end + +function testRestoresStateOnError(testCase) +checkRestore(testCase, true); +end + +function testPreservesExistingEeglabWindow(testCase) +original = figure('Visible', 'off', 'Tag', 'EEGLAB', 'UserData', 42); +cleanup = onCleanup(@() delete(original(isgraphics(original)))); %#ok +eeglab_test_workspace('delete(findall(groot, ''Type'', ''figure'', ''Tag'', ''EEGLAB'')); figure(''Tag'', ''EEGLAB'');'); +verifyTrue(testCase, isgraphics(original)); +verifyEqual(testCase, get(original, 'Tag'), 'EEGLAB'); +verifyEqual(testCase, get(original, 'UserData'), 42); +end + +function checkRestore(testCase, shouldError) +name = 'EEGLAB_FIXTURE_SENTINEL'; +previous = evalin('base', 'whos(''EEGLAB_FIXTURE_SENTINEL'')'); +if ~isempty(previous), oldValue = evalin('base', name); else, oldValue = []; end +cleanup = onCleanup(@() restoreSentinel(previous, oldValue)); %#ok +assignin('base', name, 42); +oldFolder = pwd; +oldRandom = rng; +oldFigures = findall(groot, 'Type', 'figure'); +commands = 'EEGLAB_FIXTURE_SENTINEL = 99; rng(71); figure; fid=fopen(''fixture.txt'',''w''); fclose(fid);'; +if shouldError + verifyError(testCase, @() eeglab_test_workspace([commands ' error(''EEGLAB:testFailure'',''expected'');']), 'EEGLAB:testFailure'); +else + eeglab_test_workspace(commands); +end +verifyEqual(testCase, evalin('base', name), 42); +verifyEqual(testCase, pwd, oldFolder); +verifyEqual(testCase, rng, oldRandom); +verifyEqual(testCase, findall(groot, 'Type', 'figure'), oldFigures); +end + +function restoreSentinel(previous, value) +evalin('base', 'clear EEGLAB_FIXTURE_SENTINEL'); +if ~isempty(previous) + if previous.global, evalin('base', 'global EEGLAB_FIXTURE_SENTINEL'); end + assignin('base', 'EEGLAB_FIXTURE_SENTINEL', value); +end +end diff --git a/run_eeglab_tests.m b/run_eeglab_tests.m new file mode 100644 index 0000000..e7ccbb8 --- /dev/null +++ b/run_eeglab_tests.m @@ -0,0 +1,69 @@ +function results = run_eeglab_tests(varargin) +% Run installed EEGLAB tests locally, with a manifest and persistent results. +% run_eeglab_tests('OutputDirectory', folder, 'IncludeLimo', false) +% run_eeglab_tests('DiscoverOnly', true) +% Legacy tests write data in this checkout. Use a disposable copy if needed. +parser = inputParser; +parser.addParameter('OutputDirectory', '', @(x) ischar(x) || isstring(x)); +parser.addParameter('IncludeLimo', true, @(x) islogical(x) && isscalar(x)); +parser.addParameter('DiscoverOnly', false, @(x) islogical(x) && isscalar(x)); +parser.addParameter('Name', '*', @(x) ischar(x) || isstring(x)); +parser.parse(varargin{:}); +options = parser.Results; +root = fileparts(mfilename('fullpath')); +output = char(options.OutputDirectory); +if isempty(output) + output = fullfile(root, 'test-results', char(datetime('now', 'Format', 'yyyyMMdd_HHmmss_SSS'))); +end +if ~isfolder(output) + mkdir(output); +end +% Convert relative paths before legacy tests change the working directory. +[ok, attributes] = fileattrib(output); +assert(ok, 'Cannot resolve output directory.'); +output = attributes.Name; +assert(~isfile(fullfile(output, 'manifest.csv')), 'Choose a new output directory to preserve previous results.'); +oldPath = path; +oldFolder = pwd; +oldVisibility = get(groot, 'DefaultFigureVisible'); +cleanup = onCleanup(@() restoreRunner(oldPath, oldFolder, oldVisibility)); %#ok +cd(root); +addpath(root, fullfile(root, 'eeglab'), fullfile(root, 'regression_tests')); +addpath(genpath(fullfile(root, 'unittesting_common'))); +set(groot, 'DefaultFigureVisible', 'off'); +eeglab; +preferences = eeglab_test_preferences; %#ok +suite = eeglab_test_suite(root, options.IncludeLimo); +pattern = ['^' regexptranslate('wildcard', char(options.Name)) '$']; +suite = suite.selectIf(matlab.unittest.selectors.HasName(matlab.unittest.constraints.Matches(pattern))); +assert(~isempty(suite), 'EEGLAB:NoTests', 'No tests matched the requested selection.'); +manifest = table(string({suite.Name})', string({suite.BaseFolder})', ... + 'VariableNames', {'Name', 'BaseFolder'}); +writetable(manifest, fullfile(output, 'manifest.csv')); +environment = struct('matlab', version, 'architecture', computer, ... + 'eeglab', which('eeglab'), 'version', eeg_getversion, 'path', path); +save(fullfile(output, 'suite.mat'), 'suite', 'environment'); +fprintf('Discovered %d unique tests. Results: %s\n', numel(suite), output); +results = matlab.unittest.TestResult.empty; +if options.DiscoverOnly + return +end +runner = matlab.unittest.TestRunner.withTextOutput; +runner.addPlugin(matlab.unittest.plugins.XMLPlugin.producingJUnitFormat(fullfile(output, 'results.xml'))); +runner.addPlugin(matlab.unittest.plugins.DiagnosticsRecordingPlugin); +runner.addPlugin(EeglabTestProgressPlugin(output)); +started = datetime('now'); +results = runner.run(suite); +finished = datetime('now'); +save(fullfile(output, 'results.mat'), 'results', 'started', 'finished', 'environment'); +summary = table(results); +writetable(summary(:, {'Name', 'Passed', 'Failed', 'Incomplete', 'Duration'}), fullfile(output, 'results.csv')); +fprintf('Completed %d tests: %d passed, %d failed, %d incomplete.\n', ... + numel(results), nnz([results.Passed]), nnz([results.Failed]), nnz([results.Incomplete])); +end + +function restoreRunner(oldPath, oldFolder, oldVisibility) +cd(oldFolder); +path(oldPath); +set(groot, 'DefaultFigureVisible', oldVisibility); +end diff --git a/unittesting_adminfunc/eeglab_execmenu/adminfunc_eeglab_execmenu_wrapperTest.m b/unittesting_adminfunc/eeglab_execmenu/adminfunc_eeglab_execmenu_wrapperTest.m index 167899f..bd713cb 100644 --- a/unittesting_adminfunc/eeglab_execmenu/adminfunc_eeglab_execmenu_wrapperTest.m +++ b/unittesting_adminfunc/eeglab_execmenu/adminfunc_eeglab_execmenu_wrapperTest.m @@ -1,5 +1,9 @@ function tests = adminfunc_eeglab_execmenu_wrapperTest tests = functiontests(localfunctions); +function setupOnce(testCase) +root = fileparts(fileparts(fileparts(mfilename('fullpath')))); +testCase.applyFixture(matlab.unittest.fixtures.PathFixture(fullfile(root, 'unittesting_common'))); + function test_i_pass_general(~) -evalin('base', 'test_eeglab_execmenu'); +test_eeglab_execmenu; diff --git a/unittesting_adminfunc/eeglab_execmenu/eeglab_execmenu_commands.m b/unittesting_adminfunc/eeglab_execmenu/eeglab_execmenu_commands.m new file mode 100644 index 0000000..895472c --- /dev/null +++ b/unittesting_adminfunc/eeglab_execmenu/eeglab_execmenu_commands.m @@ -0,0 +1,23 @@ +%% +eeglab +eeglabp = fileparts(which('eeglab')); +tmpdata = rand(10,1000); +eeglab_execmenu('From ASCII/float file or MATLAB array', 'pop_importdata', { 'dataformat','array','nbchan',0,'data','tmpdata','srate',100 }); +assert(EEG.nbchan == 10 && EEG.pnts == 1000 && EEG.srate == 100); +assert(max(abs(double(EEG.data(:))-tmpdata(:))) < 1e-6); +eeglab_execmenu('Save current dataset as', 'pop_saveset', { 'test.set' }); +eeglab_execmenu('Dataset info', 'pop_editset', { 'subject', 'test2' }); +assert(strcmp(EEG.subject, 'test2')); +eeglab_execmenu('Resave current dataset(s)', 'pop_saveset', { 'savemode' 'resave' }); +eeglab_execmenu('Load existing dataset', 'pop_loadset', { fullfile(eeglabp, 'sample_data', 'eeglab_data.set') }); +eeglab_execmenu('Event values', 'pop_editeventvals', { 'changefield',{1,'position',3} }); +eeglab_execmenu('About this dataset', 'pop_comments', { strvcat('EEGLAB Tutorial Dataset','test') }); +eeglab_execmenu('Channel locations', 'pop_chanedit', { 'lookup', fullfile(eeglabp, 'plugins', 'dipfit', 'standard_BEM', 'elec', 'standard_1005.elc') }); +eeglab_execmenu('Change sampling rate', 'pop_resample', { 64 }); +assert(EEG.srate == 64); + +SAVEDCOM = ALLCOM; +for iCommand = length(SAVEDCOM):-1:1 + evalin('base', SAVEDCOM{iCommand}); +end +assert(EEG.srate == 64); diff --git a/unittesting_adminfunc/eeglab_execmenu/test_eeglab_execmenu.m b/unittesting_adminfunc/eeglab_execmenu/test_eeglab_execmenu.m index 7cedf28..02e4d59 100644 --- a/unittesting_adminfunc/eeglab_execmenu/test_eeglab_execmenu.m +++ b/unittesting_adminfunc/eeglab_execmenu/test_eeglab_execmenu.m @@ -1,18 +1,5 @@ -%% -eeglab -eeglabp = fileparts(which('eeglab')); -tmpdata = rand(10,1000); -eeglab_execmenu('From ASCII/float file or MATLAB array', 'pop_importdata', { 'dataformat','array','nbchan',0,'data','tmpdata','srate',100 }); -eeglab_execmenu('Save current dataset as', 'pop_saveset', { 'test.set' }); -eeglab_execmenu('Dataset info', 'pop_editset', { 'subject', 'test2' }); -eeglab_execmenu('Resave current dataset(s)', 'pop_saveset', { 'savemode' 'resave' }); -eeglab_execmenu('Load existing dataset', 'pop_loadset', { fullfile(eeglabp, 'sample_data', 'eeglab_data.set') }); -eeglab_execmenu('Event values', 'pop_editeventvals', { 'changefield',{1,'position',3} }); -eeglab_execmenu('About this dataset', 'pop_comments', { strvcat('EEGLAB Tutorial Dataset','test') }); -eeglab_execmenu('Channel locations', 'pop_chanedit', { 'lookup', fullfile(eeglabp, 'plugins', 'dipfit', 'standard_BEM', 'elec', 'standard_1005.elc') }); -eeglab_execmenu('Change sampling rate', 'pop_resample', { 64 }); - -SAVEDCOM = ALLCOM; -for iCommand = length(SAVEDCOM):-1:1 - evalin('base', SAVEDCOM{iCommand}); +function test_eeglab_execmenu +% Menu callbacks execute in the base workspace, including history replay. +folder = fileparts(mfilename('fullpath')); +eeglab_test_workspace(fileread(fullfile(folder, 'eeglab_execmenu_commands.m'))); end diff --git a/unittesting_common/EeglabTestProgressPlugin.m b/unittesting_common/EeglabTestProgressPlugin.m new file mode 100644 index 0000000..256da0d --- /dev/null +++ b/unittesting_common/EeglabTestProgressPlugin.m @@ -0,0 +1,31 @@ +classdef EeglabTestProgressPlugin < matlab.unittest.plugins.TestRunnerPlugin + % Preserve completed results and identify a stalled test before completion. + properties (Access = private) + OutputDirectory + Count = 0 + end + methods + function plugin = EeglabTestProgressPlugin(folder) + plugin.OutputDirectory = folder; + mkdir(fullfile(folder, 'individual')); + end + end + methods (Access = protected) + function runTest(plugin, data) + file = fullfile(plugin.OutputDirectory, 'current.txt'); + handle = fopen(file, 'w'); + assert(handle ~= -1, 'Cannot write test progress to %s.', file); + cleanup = onCleanup(@() fclose(handle)); + fprintf(handle, '%s\n%s\n', char(data.Name), char(datetime('now'))); + clear cleanup + runTest@matlab.unittest.plugins.TestRunnerPlugin(plugin, data); + end + function reportFinalizedResult(plugin, data) + result = data.TestResult; + plugin.Count = plugin.Count + 1; + save(fullfile(plugin.OutputDirectory, 'individual', ... + sprintf('%04d.mat', plugin.Count)), 'result'); + reportFinalizedResult@matlab.unittest.plugins.TestRunnerPlugin(plugin, data); + end + end +end diff --git a/unittesting_common/eeglab_test_preferences.m b/unittesting_common/eeglab_test_preferences.m new file mode 100644 index 0000000..b26e03e --- /dev/null +++ b/unittesting_common/eeglab_test_preferences.m @@ -0,0 +1,21 @@ +function cleanup = eeglab_test_preferences +% Restore the user's EEGLAB option file after tests that call pop_editoptions. +eeglab_options; +file = fullfile(homefolder, 'eeg_options.m'); +existed = isfile(file); +backup = tempname; +if existed + copyfile(file, backup); +end +cleanup = onCleanup(@() restorePreferences(file, backup, existed)); +end + +function restorePreferences(file, backup, existed) +if existed + copyfile(backup, file); + delete(backup); +elseif isfile(file) + delete(file); +end +clear eeg_options +end diff --git a/unittesting_common/eeglab_test_workspace.m b/unittesting_common/eeglab_test_workspace.m new file mode 100644 index 0000000..eec60d2 --- /dev/null +++ b/unittesting_common/eeglab_test_workspace.m @@ -0,0 +1,48 @@ +function eeglab_test_workspace(commands) +% Execute legacy menu commands in the base workspace and restore test state. +variables = evalin('base', 'whos'); +values = cell(size(variables)); +for index = 1:numel(variables) + values{index} = evalin('base', variables(index).name); +end +oldFolder = pwd; +oldRandom = rng; +oldFigures = findall(groot, 'Type', 'figure'); +oldVisibility = get(groot, 'DefaultFigureVisible'); +oldPath = path; +oldWarnings = warning; +eeglabFigures = findall(groot, 'Type', 'figure', 'Tag', 'EEGLAB'); +folder = tempname; +mkdir(folder); +state = struct('variables', variables, 'values', {values}, 'folder', oldFolder, ... + 'random', oldRandom, 'figures', oldFigures, 'visibility', oldVisibility, 'temporary', folder, ... + 'path', oldPath, 'warnings', oldWarnings, 'eeglabFigures', eeglabFigures); +cleanup = onCleanup(@() restoreWorkspace(state)); %#ok +% eeglab closes existing windows with this tag. Protect the caller's window. +set(eeglabFigures, 'Tag', 'EEGLAB_test_saved'); +cd(folder); +set(groot, 'DefaultFigureVisible', 'off'); +evalin('base', commands); +end + +function restoreWorkspace(state) +cd(state.folder); +current = evalin('base', 'who'); +% Clear bindings first, so formerly local variables do not stay global. +for k = 1:numel(current) + evalin('base', ['clear ' current{k}]); +end +for k = 1:numel(state.variables) + if state.variables(k).global + evalin('base', ['global ' state.variables(k).name]); + end + assignin('base', state.variables(k).name, state.values{k}); +end +delete(setdiff(findall(groot, 'Type', 'figure'), state.figures)); +set(state.eeglabFigures(isgraphics(state.eeglabFigures)), 'Tag', 'EEGLAB'); +set(groot, 'DefaultFigureVisible', state.visibility); +rng(state.random); +warning(state.warnings); +path(state.path); +rmdir(state.temporary, 's'); +end diff --git a/unittesting_limo/limo_preproc_stats_hw.m b/unittesting_limo/limo_preproc_stats_hw.m index af1a3a6..2aaa0b2 100644 --- a/unittesting_limo/limo_preproc_stats_hw.m +++ b/unittesting_limo/limo_preproc_stats_hw.m @@ -1,3 +1,4 @@ +function limo_preproc_stats_hw(studypath, outputdir) % BIDS Tools / EEGLAB / LIMO EEG % data analysis of Wakeman and Henson 2015 data % tests the full preprocessing pipeline - bids_import @@ -19,13 +20,21 @@ %% Import % start EEGLAB -clear +if nargin < 1 + studypath = fullfile(fileparts(fileparts(mfilename('fullpath'))), 'ds002718'); +end +if nargin < 2, outputdir = ''; end +[studypath, outputdir, cleanup] = limo_test_output(studypath, outputdir); %#ok +preferences = eeglab_test_preferences; %#ok +oldRandom = rng; +randomCleanup = onCleanup(@() rng(oldRandom)); %#ok +rng(0); [ALLEEG, EEG, CURRENTSET, ALLCOM] = eeglab; pop_editoptions( 'option_storedisk', 1); % call BIDS tool BIDS -filepath = fileparts(which('participants.tsv')); -[STUDY, ALLEEG] = pop_importbids(filepath, 'bidsevent','on','bidschanloc','on', 'studyName','Face_detection','outputdir', fullfile(filepath, 'derivatives2'), 'eventtype', 'trial_type'); +filepath = studypath; +[STUDY, ALLEEG] = pop_importbids(filepath, 'bidsevent','on','bidschanloc','on', 'studyName','Face_detection','outputdir', outputdir, 'eventtype', 'trial_type'); ALLEEG = pop_select( ALLEEG, 'nochannel',{'EEG061','EEG062','EEG063','EEG064'}); CURRENTSTUDY = 1; EEG = ALLEEG; CURRENTSET = [1:length(EEG)]; @@ -76,14 +85,15 @@ 'vartype1','categorical','subjselect',{'sub-002','sub-003','sub-004','sub-005','sub-006','sub-007','sub-008',... 'sub-009','sub-010','sub-011','sub-012','sub-013','sub-014','sub-015','sub-016','sub-017','sub-018','sub-019'}); [STUDY, EEG] = pop_savestudy( STUDY, EEG, 'savemode','resave'); -STUDY = pop_limo(STUDY, ALLEEG, 'method',mode,'measure','daterp','timelim',[-50 650],'erase','on','splitreg','off','interaction','off'); +[STUDY, ~, Model_files] = pop_limo(STUDY, ALLEEG, 'method',mode,'measure','daterp','timelim',[-50 650],'erase','on','splitreg','off','interaction','off','verbose','noGUI'); +lists = limo_test_lists(Model_files, STUDY, fullfile(outputdir, 'input_lists')); % 2nd level analysis mkdir([STUDY.filepath filesep '2-ways-ANOVA']) cd([STUDY.filepath filesep '2-ways-ANOVA']) -chanlocs = [STUDY.filepath filesep 'limo_gp_level_chanlocs.mat']; +chanlocs = STUDY.limo.chanloc; limo_random_select('Repeated Measures ANOVA',chanlocs,'LIMOfiles',... - {[STUDY.filepath filesep 'LIMO_Face_detection' filesep 'Beta_files_FaceRepetition_GLM_Channels_Time_' mode '.txt']},... + {lists.beta},... 'analysis_type','Full scalp analysis','parameters',{[1 2 3],[4 5 6],[7 8 9]},... 'factor names',{'face','repetition'},'type','Channels','nboot',1000,'tfce',0,'skip design check','yes'); @@ -91,8 +101,7 @@ mkdir('ERPs'); cd('ERPs'); % compute unweigted ERPs -Files = [STUDY.filepath filesep 'LIMO_' STUDY.filename(1:end-6) filesep ... - 'LIMO_files_FaceRepetition_GLM_Channels_Time_' mode '.txt']; +Files = lists.mat; parameters = [1 2 3]; savename1 = [pwd filesep 'famous_faces']; limo_central_tendency_and_ci(Files, parameters, chanlocs, 'Mean', 'Mean', [],savename1) @@ -102,7 +111,7 @@ parameters = [7 8 9]; savename3 = [pwd filesep 'unfamiliar_faces']; limo_central_tendency_and_ci(Files, parameters, chanlocs, 'Mean', 'Mean', [],savename3) -limo_add_plots({[savename1 '_Mean_of_mean.mat'],[savename2 '_Mean_of_mean.mat'],[savename3 '_Mean_of_mean.mat']},... +limo_add_plots({[savename1 '_Mean_of_Mean.mat'],[savename2 '_Mean_of_Mean.mat'],[savename3 '_Mean_of_Mean.mat']},... [STUDY.filepath filesep '2-ways-ANOVA' filesep 'LIMO.mat'],'channel',50); title('Mean Face types at channel 50') % compute weighted ERPs @@ -121,15 +130,15 @@ % plot these results again and also subject wise figure subplot(1,3,1); -limo_add_plots({[savename1 '_Mean_of_mean.mat'],[savename1 '_Mean_of_Weighted mean.mat']},... +limo_add_plots({[savename1 '_Mean_of_Mean.mat'],[savename1 '_Mean_of_Weighted mean.mat']},... [STUDY.filepath filesep '2-ways-ANOVA' filesep 'LIMO.mat'],'channel',50,'figure','hold'); title('mean and weighed mean Famous Faces','Fontsize',12) subplot(1,3,2); -limo_add_plots({[savename2 '_Mean_of_mean.mat'],[savename2 '_Mean_of_Weighted mean.mat']},... +limo_add_plots({[savename2 '_Mean_of_Mean.mat'],[savename2 '_Mean_of_Weighted mean.mat']},... [STUDY.filepath filesep '2-ways-ANOVA' filesep 'LIMO.mat'],'channel',50,'figure','hold'); title('mean and weighed mean srambled Faces','Fontsize',12) subplot(1,3,3); -limo_add_plots({[savename3 '_Mean_of_mean.mat'],[savename3 '_Mean_of_Weighted mean.mat']},... +limo_add_plots({[savename3 '_Mean_of_Mean.mat'],[savename3 '_Mean_of_Weighted mean.mat']},... [STUDY.filepath filesep '2-ways-ANOVA' filesep 'LIMO.mat'],'channel',50,'figure','hold'); title('mean and weighed mean unfamiliar Faces','Fontsize',12) @@ -145,27 +154,30 @@ % compute mean Betas via contrast cd .. -[~,~,Files] = limo_get_files([],[],[],[STUDY.filepath filesep 'LIMO_' STUDY.filename(1:end-6) filesep ... - 'LIMO_files_FaceRepetition_GLM_Channels_Time_' mode '.txt']); +[~,~,Files] = limo_get_files([],[],[],lists.mat); contrast.LIMO_files = Files; contrast.mat = [1 1 1 0 0 0 0 0 0 0; 0 0 0 1 1 1 0 0 0 0;0 0 0 0 0 0 1 1 1 0]; % average repetition levels -limo_batch('contrast only',[],contrast); +contrastFiles = limo_batch('contrast only',[],contrast,STUDY); +Model_files.con = contrastFiles.con; +lists = limo_test_lists(Model_files, STUDY, fullfile(outputdir, 'contrast_lists')); +% limo_batch changes directory to its batch output folder. +cd(fullfile(STUDY.filepath, '2-ways-ANOVA')); mkdir('famous_faces'); cd('famous_faces') limo_random_select('one sample t-test',chanlocs,'LIMOfiles',... - {[STUDY.filepath filesep 'LIMO_' STUDY.filename(1:end-6) filesep 'con_1_files_FaceRepetition_GLM_Channels_Time_' mode '.txt']},... + {lists.con{1}},... 'analysis_type','Full scalp analysis','type','Channels','nboot',0,'tfce',0); savename1 = [pwd filesep 'famous_faces']; limo_central_tendency_and_ci([pwd filesep 'Yr.mat'], 'Mean', 50,savename1); cd .. mkdir('scrambled_faces'); cd('scrambled_faces') limo_random_select('one sample t-test',chanlocs,'LIMOfiles',... - {[STUDY.filepath filesep 'LIMO_' STUDY.filename(1:end-6) filesep 'con_2_files_FaceRepetition_GLM_Channels_Time_' mode '.txt']},... + {lists.con{2}},... 'analysis_type','Full scalp analysis','type','Channels','nboot',0,'tfce',0); savename2 = [pwd filesep 'scrambled_faces']; limo_central_tendency_and_ci([pwd filesep 'Yr.mat'], 'Mean', 50,savename2); cd .. mkdir('unfamiliar_faces'); cd('unfamiliar_faces') limo_random_select('one sample t-test',chanlocs,'LIMOfiles',... - {[STUDY.filepath filesep 'LIMO_' STUDY.filename(1:end-6) filesep 'con_3_files_FaceRepetition_GLM_Channels_Time_' mode '.txt']},... + {lists.con{3}},... 'analysis_type','Full scalp analysis','type','Channels','nboot',0,'tfce',0); savename3 = [pwd filesep 'unfamiliar_faces']; limo_central_tendency_and_ci([pwd filesep 'Yr.mat'], 'Mean', 50,savename3); cd .. @@ -187,3 +199,5 @@ % print main results limo_eeg(5,fullfile(pwd,'LIMO.mat')) + +end diff --git a/unittesting_limo/limo_test_integration.m b/unittesting_limo/limo_test_integration.m index 317f697..a216f65 100644 --- a/unittesting_limo/limo_test_integration.m +++ b/unittesting_limo/limo_test_integration.m @@ -1,4 +1,4 @@ -function limotest = limo_test_integration +function limotest = limo_test_integration(studypath, outputdir) % Integration test for 1st level and 2nd level analyses % depends upon Wakeman and Henson dataset https://openneuro.org/datasets/ds002718/versions/1.0.2 @@ -24,22 +24,25 @@ % basic stats % plotting -clear variables +if nargin < 1 + studypath = fullfile(fileparts(fileparts(mfilename('fullpath'))), 'ds002718'); +end +if nargin < 2, outputdir = ''; end +[studypath, outputdir, cleanup] = limo_test_output(studypath, outputdir); %#ok +failures = cell(1,9); +Model1_files = []; Model2_files = []; +list1 = []; list2 = []; +oldRandom = rng; +randomCleanup = onCleanup(@() rng(oldRandom)); %#ok +rng(0); % start EEGLAB [ALLEEG, EEG, CURRENTSET, ALLCOM] = eeglab; % call BIDS tool BIDS -studypath = fullfile(pwd, '..', 'ds002718'); studyName = 'Face_detection'; -try - rmdir(fullfile(studypath, 'derivatives_integration'), 's') -catch - lasterror -end - [STUDY, ALLEEG] = pop_importbids(studypath,'bidsevent','on','bidschanloc','on',... - 'studyName',studyName,'outputdir', fullfile(studypath, 'derivatives_integration'), ... + 'studyName',studyName,'outputdir', outputdir, ... 'eventtype', 'trial_type'); ALLEEG = pop_select( ALLEEG, 'nochannel',{'EEG061','EEG062','EEG063','EEG064'}); CURRENTSTUDY = 1; @@ -91,7 +94,7 @@ %% test std_limo and 1st level GLM -studyfullname = fullfile(studypath, 'derivatives_integration', [ studyName '.study' ]); +studyfullname = fullfile(outputdir, [ studyName '.study' ]); tic if exist(studyfullname,'file') @@ -120,29 +123,19 @@ 'variable1','type','values1',{'famous_new','famous_second_early','famous_second_late','scrambled_new','scrambled_second_early','scrambled_second_late','unfamiliar_new','unfamiliar_second_early','unfamiliar_second_late'},... 'vartype1','categorical','subjselect',{'sub-002','sub-003','sub-004','sub-005','sub-006','sub-007','sub-008','sub-009','sub-010','sub-011','sub-012','sub-013','sub-014','sub-015','sub-016','sub-017','sub-018','sub-019'}); [STUDY, EEG] = pop_savestudy( STUDY, EEG, 'savemode','resave'); - - % cleanup previous version - [~,limo_rootfiles,~]=fileparts(STUDY.filename); - limo_rootfiles = fullfile(root,['LIMO_' limo_rootfiles]); - if exist([limo_rootfiles filesep 'limo_batch_report'],'dir') - rmdir([limo_rootfiles filesep 'limo_batch_report'],'s') - end - for sub = 1:length(STUDY.subject) - if exist(fullfile(root,[STUDY.subject{1} filesep 'eeg' filesep 'FaceRepetition_GLM_Channels_Time_OLS']),'dir') - rmdir(fullfile(root,[STUDY.subject{1} filesep 'eeg' filesep 'FaceRepetition_GLM_Channels_Time_OLS']),'s') - end - end - + % compute 1st model with OLS - [STUDY, ~, Model1_files] = pop_limo(STUDY, ALLEEG, 'method','OLS','measure','daterp','timelim',[-50 650],'erase','on','splitreg','off','interaction','off'); + [STUDY, ~, Model1_files] = pop_limo(STUDY, ALLEEG, 'method','OLS','measure','daterp','timelim',[-50 650],'erase','on','splitreg','off','interaction','off','verbose','noGUI'); contrast.LIMO_files = Model1_files.mat; contrast.mat = [1 1 1 -1 -1 -1 0 0 0 0 ; 0 0 0 1 1 1 -1 -1 -1 0]; confiles = limo_batch('contrast only',[],contrast,STUDY); Model1_files.con = confiles.con; + list1 = limo_test_lists(Model1_files, STUDY, fullfile(outputdir, 'model1_lists')); clear confiles limotest{1} = 'categorical design + contrasts with OLS estimates successful'; catch err - fprintf('%s\n',err.message) + failures{1} = err; + fprintf('%s\n',getReport(err, 'extended', 'hyperlinks', 'off')) limotest{1} = sprintf('categorical design + contrasts with OLS estimates failed \n%s',err.message); end @@ -153,316 +146,24 @@ 'variable2','time_dist','values2',[],'vartype2','continuous',... 'subjselect',{'sub-002','sub-003','sub-004','sub-005','sub-006','sub-007','sub-008','sub-009','sub-010','sub-011','sub-012','sub-013','sub-014','sub-015','sub-016','sub-017','sub-018','sub-019'}); [STUDY, EEG] = pop_savestudy( STUDY, EEG, 'savemode','resave'); - - % cleanup previous version - for sub = 1:length(STUDY.subject) - if exist(fullfile(root,[STUDY.subject{1} filesep 'eeg' filesep 'Face_time_GLM_Channels_Time_WLS']),'dir') - rmdir(fullfile(root,[STUDY.subject{1} filesep 'eeg' filesep 'Face_time_GLM_Channels_Time_WLS']),'s') - end - end - + % compute 1st model with WLS - [STUDY, ~, Model2_files] = pop_limo(STUDY, ALLEEG, 'method','WLS','measure','daterp','timelim',[-50 650],'erase','on','splitreg','on','interaction','off'); + [STUDY, ~, Model2_files] = pop_limo(STUDY, ALLEEG, 'method','WLS','measure','daterp','timelim',[-50 650],'erase','on','splitreg','on','interaction','off','verbose','noGUI'); contrast.LIMO_files = Model2_files.mat; contrast.mat = [0 0 0 -1 0 1]; confiles = limo_batch('contrast only',[],contrast); % do not pass STUDY argument, should still figure it out Model2_files.con = confiles.con; + list2 = limo_test_lists(Model2_files, STUDY, fullfile(outputdir, 'model2_lists')); clear confiles limotest{2} = 'mixed design with WLS estimates + contrast successful'; catch err - fprintf('%s\n',err.message) + failures{2} = err; + fprintf('%s\n',getReport(err, 'extended', 'hyperlinks', 'off')) limotest{2} = sprintf('mixed design with WLS estimates + contrast failed \n%s',err.message); end -%% 2nd level analyses -cd(root); mkdir('2nd_level_tests'); cd('2nd_level_tests'); -channel_vector = limo_best_electrodes(fullfile(limo_rootfiles, 'LIMO_files_Face_time_GLM_Channels_Time_WLS.txt')); -save('virtual_electrode','channel_vector') - -% --------------------------------------------------------------------- -% one sample t-test -try - % one sample t-test whole brain with a cell array of con files as input - cd(fullfile(root,'2nd_level_tests')); - mkdir('one_sample'); cd('one_sample') - LIMOPath = limo_random_select('one sample t-test',STUDY.limo.chanloc,... - 'LIMOfiles',Model2_files.con,... - 'analysis_type','Full scalp analysis', 'type','Channels','nboot',101,'tfce',1); - - % one sample t-test channel 50 file list of con files - cd(fullfile(root,'2nd_level_tests')); - mkdir('one_sample50'); cd('one_sample50') - LIMOPath = limo_random_select('one sample t-test',STUDY.limo.chanloc,... - 'LIMOfiles',[limo_rootfiles filesep 'con_1_files_' STUDY.design(2).name '_GLM_Channels_Time_WLS.txt'],... - 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels','nboot',101,'tfce',1); - - % one sample t-test virtual channel - array of Betas - cd(fullfile(root,'2nd_level_tests')); - mkdir('one_sampleOPT'); cd('one_sampleOPT') - LIMOPath = limo_random_select('one sample t-test',STUDY.limo.chanloc,... - 'LIMOfiles',Model2_files.Beta, 'analysis_type','1 channel/component only', 'Channel',channel_vector, ... - 'type','Channels','parameter',{[1 3 7]},'nboot',101,'tfce',1); - limotest{3} = 'one sample t-tests successful'; -catch err - fprintf('%s\n',err.message) - limotest{3} = sprintf('one sample t-tests failed \n%s',err.message); -end - -% --------------------------------------------------------------------- -% regression -try - % regression (calls similar routines as one sample) - % regression whole brain with an array of con files as input and a matrix as regressor - cd(fullfile(root,'2nd_level_tests')); - mkdir('regression'); cd('regression') - LIMOPath = limo_random_select('regression',STUDY.limo.chanloc,... - 'LIMOfiles',Model2_files.con,'regressor_file',randi(length(Model2_files.con),length(Model2_files.con),2),... - 'analysis_type','Full scalp analysis', 'type','Channels','zscore','yes','skip design check','yes','nboot',101,'tfce',1); - - % regression channel 50 file list of con files as input and a file as regressor - cd(fullfile(root,'2nd_level_tests')); - mkdir('regression50'); cd('regression50') - randomreg = randn(length(Model2_files.con),1); save('reg.mat','randomreg'); - LIMOPath = limo_random_select('regression',STUDY.limo.chanloc,'regressor_file',[pwd filesep 'reg.mat'],... - 'LIMOfiles',[limo_rootfiles filesep 'con_1_files_' STUDY.design(2).name '_GLM_Channels_Time_WLS.txt'],... - 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels','zscore','yes','skip design check','yes','nboot',101,'tfce',1); - - % regression virtual channel - list of Betas files, select a parameter, load optimized channel file - cd(fullfile(root,'2nd_level_tests')); - mkdir('regressionOPT'); cd('regressionOPT') - LIMOPath = limo_random_select('regression',STUDY.limo.chanloc,... - 'LIMOfiles',[limo_rootfiles filesep 'Beta_files_' STUDY.design(2).name '_GLM_Channels_Time_WLS.txt'], ... - 'parameter',3, 'regressor_file',randi(length(Model2_files.con),length(Model2_files.con),2), ... - 'analysis_type','1 channel/component only', 'type','Channels', ... - 'Channel',fullfile(root,['2nd_level_tests' filesep 'virtual_electrode.mat']), ... - 'zscore','yes','skip design check','yes','nboot',101,'tfce',1); - limotest{4} = 'regressions successful'; -catch err - fprintf('%s\n',err.message) - limotest{4} = sprintf('regressions failed \n%s',err.message); -end - -% --------------------------------------------------------------------- -% paired t-test -try - % paired t-test whole brain with a cell array of con files as input - clear data - for N=length(STUDY.subject):-1:1 - data{1,N} = Model1_files.con{N}(1); - data{2,N} = Model1_files.con{N}(2); - end - cd(fullfile(root,'2nd_level_tests')); - mkdir('paired_t-test'); cd('paired_t-test') - LIMOPath = limo_random_select('paired t-test',STUDY.limo.chanloc,... - 'LIMOfiles',data,'analysis_type','Full scalp analysis', 'type','Channels','nboot',101,'tfce',1); - - % paired t-test channel 50 with file list of con files - cd(fullfile(root,'2nd_level_tests')); - mkdir('paired_t-test50'); cd('paired_t-test50') - datafiles = {[limo_rootfiles filesep 'con_1_files_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt'], ... - [limo_rootfiles filesep 'con_2_files_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']}; - LIMOPath = limo_random_select('paired t-test',STUDY.limo.chanloc,'LIMOfiles',datafiles,... - 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels','nboot',101,'tfce',1); - - % paired t-test virtual channel with file list of Betas - cd(fullfile(root,'2nd_level_tests')); - mkdir('paired_t-testOPT'); cd('paired_t-testOPT') - LIMOPath = limo_random_select('paired t-test',STUDY.limo.chanloc,... - 'LIMOfiles',[limo_rootfiles filesep 'Beta_files_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt'], ... - 'analysis_type','1 channel/component only', 'Channel',channel_vector, ... - 'type','Channels','parameter',[1 4],'nboot',101,'tfce',1); - limotest{5} = 'paired t-test successful'; -catch err - fprintf('%s\n',err.message) - limotest{5} = sprintf('paired t-test failed \n%s',err.message); -end - -% --------------------------------------------------------------------- -% two samples t-test -try - % two-samples t-test whole brain with a cell array of con files as input - cd(fullfile(root,'2nd_level_tests')); - mkdir('two-samples_t-test'); cd('two-samples_t-test') - LIMOPath = limo_random_select('two-samples t-test',STUDY.limo.chanloc,... - 'LIMOfiles',data,'analysis_type','Full scalp analysis', 'type','Channels','nboot',101,'tfce',1); - - % two-samples t-test channel 50 with file list of con files - cd(fullfile(root,'2nd_level_tests')); - mkdir('two-samples_t-test50'); cd('two-samples_t-test50') - LIMOPath = limo_random_select('two-samples t-test',STUDY.limo.chanloc,'LIMOfiles',datafiles,... - 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels','nboot',101,'tfce',1); - - % two-samples t-test virtual channel with file list of Betas - cd(fullfile(root,'2nd_level_tests')); - mkdir('two-samples_t-testOPT'); cd('two-samples_t-testOPT'); clear Bfiles - Bfiles{1} = [limo_rootfiles filesep 'Beta_files_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']; - Bfiles{2} = [limo_rootfiles filesep 'Beta_files_' STUDY.design(2).name '_GLM_Channels_Time_WLS.txt']; - LIMOPath = limo_random_select('two-samples t-test',STUDY.limo.chanloc,... - 'LIMOfiles',Bfiles, 'analysis_type','1 channel/component only', 'Channel',repmat(channel_vector,[2,1]),... - 'type','Channels','parameter',[1 4],'nboot',101,'tfce',1); - limotest{6} = 'two samples t-test successful'; -catch err - fprintf('%s\n',err.message) - limotest{6} = sprintf('two samples t-test failed \n%s',err.message); -end - -% --------------------------------------------------------------------- -% 1-way ANOVA + contrast -try - % N-Ways ANOVA whole brain with a cell array of con files as input with empty cells - clear data - index = find(arrayfun(@(x) contains(x.group,'1'), STUDY.datasetinfo)); - for s=1:length(index); data{1,s} = Model1_files.con{index(s)}(1); end - index = find(arrayfun(@(x) contains(x.group,'2'), STUDY.datasetinfo)); - for s=1:length(index); data{2,s} = Model1_files.con{index(s)}(1); end - index = find(arrayfun(@(x) contains(x.group,'3'), STUDY.datasetinfo)); - for s=1:length(index); data{3,s} = Model1_files.con{index(s)}(1); end - cd(fullfile(root,'2nd_level_tests')); - mkdir('N-Ways ANOVA'); cd('N-Ways ANOVA') - LIMOPath = limo_random_select('N-Ways ANOVA',STUDY.limo.chanloc,'LIMOfiles',data',... - 'analysis_type','Full scalp analysis', 'type','Channels','nboot',101,'tfce',1,'skip design check','yes'); - - % N-Ways ANOVA channel 50 with file list of con files - % con per group files already exist split according to STUDY - datafiles = {fullfile(limo_rootfiles, ['con_1_files_Gp1_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']), ... - fullfile(limo_rootfiles, ['con_1_files_Gp2_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']),... - fullfile(limo_rootfiles, ['con_1_files_Gp3_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt'])}; - cd(fullfile(root,'2nd_level_tests')); - mkdir('N-Ways ANOVA50'); cd('N-Ways ANOVA50') - LIMOPath = limo_random_select('N-Ways ANOVA',STUDY.limo.chanloc,'LIMOfiles',datafiles,... - 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels',... - 'nboot',101,'tfce',1,'skip design check','yes'); - - % N-Ways ANOVA virtual channel with file list of Betas and input parameters - cd(fullfile(root,'2nd_level_tests')); - mkdir('N-Ways ANOVAOPT'); cd('N-Ways ANOVAOPT') - for g=3:-1:1 - Bfiles{g} = [limo_rootfiles filesep 'Beta_files_Gp' num2str(g) '_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']; - end - LIMOPath = limo_random_select('N-Ways ANOVA',STUDY.limo.chanloc,... - 'LIMOfiles',Bfiles, 'analysis_type','1 channel/component only', ... - 'Channel',fullfile(root,['2nd_level_tests' filesep 'virtual_electrode.mat']), ... - 'type','Channels','parameter',{[1;1;1]},'nboot',101,'tfce',1,'skip design check','yes'); - limotest{7} = '1-way ANOVA successful'; -catch err - fprintf('%s\n',err.message) - limotest{7} = sprintf('1-way ANOVA failed \n%s',err.message); +% Keep first level results for independently rerunning the second level cases. +preparationFile = fullfile(outputdir, 'first_level_results.mat'); +save(preparationFile, 'STUDY', 'Model1_files', 'Model2_files', 'list1', 'list2', 'limotest', 'failures'); +limotest = limo_test_second_level(preparationFile); end - -% --------------------------------------------------------------------- -% 1-way ANCOVA + contrast -try - % N-Ways ANCOVA whole brain with a cell array of con files as input with empty cells - clear data - index = find(arrayfun(@(x) contains(x.group,'1'), STUDY.datasetinfo)); - for s=1:length(index); data{1,s} = Model1_files.con{index(s)}(1); end - index = find(arrayfun(@(x) contains(x.group,'2'), STUDY.datasetinfo)); - for s=1:length(index); data{2,s} = Model1_files.con{index(s)}(1); end - index = find(arrayfun(@(x) contains(x.group,'3'), STUDY.datasetinfo)); - for s=1:length(index); data{3,s} = Model1_files.con{index(s)}(1); end - cd(fullfile(root,'2nd_level_tests')); - mkdir('ANCOVA'); cd('ANCOVA') - LIMOPath = limo_random_select('ANCOVA',STUDY.limo.chanloc,'LIMOfiles',data,... % transpose data = wrong but let limo fix it - 'analysis_type','Full scalp analysis', 'type','Channels',... - 'regressor_file', randn(18,2), 'nboot',101,'tfce',1,'skip design check','yes'); - limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'Betas.mat'], ... - [pwd filesep 'LIMO.mat'],'T',1,[0 0 0 1 -1 0]); % contrast - limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'H0' filesep 'H0_Betas.mat'], ... - [pwd filesep 'LIMO.mat'],'T',2,[0 0 0 1 -1 0]); % boostrap / tfce - - % N-Ways ANCOVA channel 50 with file list of con files - cd(fullfile(root,'2nd_level_tests')); - mkdir('ANCOVA50'); cd('ANCOVA50') - LIMOPath = limo_random_select('ANCOVA',STUDY.limo.chanloc,'LIMOfiles',datafiles(1:2),... - 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels',... - 'regressor_file', randn(13,2),'nboot',101,'tfce',1,'skip design check','yes'); - limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'Betas.mat'], ... - [pwd filesep 'LIMO.mat'],'T',1,[0 0 1 -1 0]); % contrast - limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'H0' filesep 'H0_Betas.mat'], ... - [pwd filesep 'LIMO.mat'],'T',2,[0 0 1 -1 0]); % boostrap / tfce - - % N-Ways ANCOVA virtual channel with file list of Betas and input parameters - cd(fullfile(root,'2nd_level_tests')); - mkdir('ANCOVAOPT'); cd('ANCOVAOPT') - LIMOPath = limo_random_select('ANCOVA',STUDY.limo.chanloc,... - 'LIMOfiles',Bfiles, 'analysis_type','1 channel/component only', 'Channel',repmat(channel_vector,[2,1]),... - 'regressor_file', randn(18,2),'type','Channels','parameter',[1 4 1],'nboot',101,'tfce',1,'skip design check','yes'); - limotest{8} = 'ANCOVA + contrast successful'; -catch err - fprintf('%s\n',err.message) - limotest{8} = sprintf('1-way ANCOVA + contrast failed \n%s',err.message); -end - -% --------------------------------------------------------------------- -% Repeated measures ANOVA + contrast -try - % whole brain with Beta files as input - cd(fullfile(root,'2nd_level_tests')); - mkdir('Rep-ANOVA'); cd('Rep-ANOVA') - limo_random_select('Repeated Measures ANOVA',STUDY.limo.chanloc,'LIMOfiles',... - {[limo_rootfiles filesep 'Beta_files_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']},... - 'analysis_type','Full scalp analysis','parameters',{[1 2 3],[4 5 6],[7 8 9]},... - 'factor names',{'face','repetition'},'type','Channels','nboot',101,'tfce',1,'skip design check','yes'); - limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'LIMO.mat'],... - 3,[1 1 1 -2 -2 -2 1 1 1]); % contrast - limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'LIMO.mat'],... - 4,[1 1 1 -2 -2 -2 1 1 1]); % boostrap / tfce - - % whole brain with Beta files as input split by groups - cd(fullfile(root,'2nd_level_tests')); - mkdir('GpRep-ANOVA'); cd('GpRep-ANOVA') - limo_random_select('Repeated Measures ANOVA',STUDY.limo.chanloc,'LIMOfiles',... - {[limo_rootfiles filesep 'Beta_files_Gp1_' STUDY.design(2).name '_GLM_Channels_Time_WLS.txt']; - [limo_rootfiles filesep 'Beta_files_Gp2_' STUDY.design(2).name '_GLM_Channels_Time_WLS.txt']; - [limo_rootfiles filesep 'Beta_files_Gp3_' STUDY.design(2).name '_GLM_Channels_Time_WLS.txt']},... - 'analysis_type','Full scalp analysis','parameters',{[1 2 3]},... % in theory {[1 2 3];[1 2 3];[1 2 3]} but it's taken care of - 'factor names',{'face'},'type','Channels','nboot',101,'tfce',1,'skip design check','yes'); - limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'LIMO.mat'],... - 3,[1 -2 1]); % contrast - limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'LIMO.mat'],... - 4,[1 -2 1]); % boostrap / tfce - - % channel 50 with con files as input - cd(fullfile(root,'2nd_level_tests')); - clear datafiles % spurious design but we need enough subjects to run - datafiles{1,1} = fullfile(limo_rootfiles, ['con_1_files_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']); - datafiles{1,2} = fullfile(limo_rootfiles, ['con_1_files_' STUDY.design(2).name '_GLM_Channels_Time_WLS.txt']); - datafiles{1,3} = fullfile(limo_rootfiles, ['con_2_files_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']); - mkdir('Rep-ANOVA50'); cd('Rep-ANOVA50') - limo_random_select('Repeated Measures ANOVA',STUDY.limo.chanloc,'LIMOfiles',datafiles,... - 'analysis_type','1 channel/component only', 'Channel',50, 'factor names',{'face'},... - 'parameters',{[1 1 1]},'type','Channels','nboot',101,'tfce',1,'skip design check','yes'); - - % also use gp + con files + optimized channel - cd(fullfile(root,'2nd_level_tests')); - clear datafiles; datafiles = cell(3,2); - datafiles{1,1} = fullfile(limo_rootfiles, ['con_1_files_Gp1_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']); - datafiles{1,2} = fullfile(limo_rootfiles, ['con_2_files_Gp1_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']); - datafiles{2,1} = fullfile(limo_rootfiles, ['con_1_files_Gp2_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']); - datafiles{2,2} = fullfile(limo_rootfiles, ['con_2_files_Gp2_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']); - datafiles{3,1} = fullfile(limo_rootfiles, ['con_1_files_Gp3_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']); - datafiles{3,2} = fullfile(limo_rootfiles, ['con_2_files_Gp3_' STUDY.design(1).name '_GLM_Channels_Time_OLS.txt']); - mkdir('GpRep-ANOVAOPT'); cd('GpRep-ANOVAOPT') - limo_random_select('Repeated Measures ANOVA',STUDY.limo.chanloc,'LIMOfiles',datafiles,... - 'analysis_type','1 channel/component only', 'Channel',channel_vector, 'factor names',{'face'},... - 'parameters',{[1 1];[1 1];[1 1]},'type','Channels','nboot',101,'tfce',1,'skip design check','yes'); - limotest{9} = 'Repeated measures ANOVA + contrast successful'; -catch err - fprintf('%s\n',err.message) - limotest{9} = sprintf('Repeated measures ANOVA + contrast failed \n%s',err.message); -end - -% --------------------------------------------------------------------- -cd(root);toc -limotest' -if all(contains(limotest,'successful')) - disp('deleting all created files - test successful') - try mdir('2nd_level_tests','s'); end - try rmdir(limo_rootfiles,'s'); end -else - error('test failure - files were not deleted from drive') -end - - - - diff --git a/unittesting_limo/limo_test_lists.m b/unittesting_limo/limo_test_lists.m new file mode 100644 index 0000000..34289b3 --- /dev/null +++ b/unittesting_limo/limo_test_lists.m @@ -0,0 +1,32 @@ +function lists = limo_test_lists(files, STUDY, folder) +% Build test input lists from returned references, not plugin filename guesses. +assert(numel(files.mat) == numel(STUDY.datasetinfo), 'Missing first level subjects.'); +mkdir(folder); +lists.mat = writeList(fullfile(folder, 'LIMO_files.txt'), files.mat); +lists.beta = writeList(fullfile(folder, 'Beta_files.txt'), files.Beta); +if isfield(files, 'con') + for contrast = 1:numel(files.con{1}) + paths = cellfun(@(entry) entry{contrast}, files.con, 'UniformOutput', false); + lists.con{contrast} = writeList(fullfile(folder, sprintf('con_%d_files.txt', contrast)), paths); + end +end +groups = unique({STUDY.datasetinfo.group}, 'stable'); +for group = 1:numel(groups) + selected = strcmp({STUDY.datasetinfo.group}, groups{group}); + lists.group_beta{group} = writeList(fullfile(folder, sprintf('Beta_group_%d.txt', group)), files.Beta(selected)); + if isfield(files, 'con') + for contrast = 1:numel(files.con{1}) + paths = cellfun(@(entry) entry{contrast}, files.con(selected), 'UniformOutput', false); + lists.group_con{group, contrast} = writeList(fullfile(folder, sprintf('con_%d_group_%d.txt', contrast, group)), paths); + end + end +end +end + +function file = writeList(file, paths) +assert(all(cellfun(@isfile, paths)), 'A returned LIMO output file does not exist.'); +handle = fopen(file, 'w'); +assert(handle ~= -1, 'Cannot write LIMO input list %s.', file); +cleanup = onCleanup(@() fclose(handle)); %#ok +fprintf(handle, '%s\n', paths{:}); +end diff --git a/unittesting_limo/limo_test_output.m b/unittesting_limo/limo_test_output.m new file mode 100644 index 0000000..a6bdaad --- /dev/null +++ b/unittesting_limo/limo_test_output.m @@ -0,0 +1,16 @@ +function [studypath, outputdir, cleanup] = limo_test_output(studypath, outputdir) +% All LIMO test products belong in a new directory. Never delete source data. +assert(isfile(fullfile(studypath, 'participants.tsv')), 'LIMO test requires the local ds002718 dataset.'); +[~, attributes] = fileattrib(studypath); +studypath = attributes.Name; +if isempty(outputdir) + outputdir = tempname; +end +assert(~isfolder(outputdir), 'Choose a new LIMO output directory. Existing results are preserved.'); +mkdir(outputdir); +[~, attributes] = fileattrib(outputdir); +outputdir = attributes.Name; +oldFolder = pwd; +cleanup = onCleanup(@() cd(oldFolder)); +fprintf('LIMO test outputs retained in %s\n', outputdir); +end diff --git a/unittesting_limo/limo_test_second_level.m b/unittesting_limo/limo_test_second_level.m new file mode 100644 index 0000000..3b9b197 --- /dev/null +++ b/unittesting_limo/limo_test_second_level.m @@ -0,0 +1,349 @@ +function limotest = limo_test_second_level(preparationFile) +% Rerun second level integration cases from validated first level results. +prepared = load(preparationFile); +STUDY = prepared.STUDY; +Model1_files = prepared.Model1_files; +Model2_files = prepared.Model2_files; +list1 = prepared.list1; +list2 = prepared.list2; +limotest = prepared.limotest; +failures = prepared.failures; +oldFolder = pwd; +oldRandom = rng; +cleanup = onCleanup(@() restoreState(oldFolder, oldRandom)); %#ok +rng(0); +tic; + +%% 2nd level analyses +% Every rerun gets fresh outputs, including its bootstrap files. +root = tempname(fileparts(preparationFile)); +mkdir(root); +fprintf('Second level outputs retained in %s\n', root); +cd(root); mkdir('2nd_level_tests'); cd('2nd_level_tests'); +channel_vector = []; +if ~isempty(list2) + channel_vector = limo_best_electrodes(list2.mat); + save('virtual_electrode','channel_vector'); +elseif ~isempty(list1) + channel_vector = limo_best_electrodes(list1.mat); + save('virtual_electrode','channel_vector'); +end + +% --------------------------------------------------------------------- +% one sample t-test +try + requireModels(list2); + % one sample t-test whole brain with a cell array of con files as input + cd(fullfile(root,'2nd_level_tests')); + mkdir('one_sample'); cd('one_sample') + LIMOPath = limo_random_select('one sample t-test',STUDY.limo.chanloc,... + 'LIMOfiles',Model2_files.con,... + 'analysis_type','Full scalp analysis', 'type','Channels','nboot',101,'tfce',1); + + % one sample t-test channel 50 file list of con files + cd(fullfile(root,'2nd_level_tests')); + mkdir('one_sample50'); cd('one_sample50') + LIMOPath = limo_random_select('one sample t-test',STUDY.limo.chanloc,... + 'LIMOfiles',list2.con{1},... + 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels','nboot',101,'tfce',1); + + % one sample t-test virtual channel - array of Betas + cd(fullfile(root,'2nd_level_tests')); + mkdir('one_sampleOPT'); cd('one_sampleOPT') + LIMOPath = limo_random_select('one sample t-test',STUDY.limo.chanloc,... + 'LIMOfiles',Model2_files.Beta, 'analysis_type','1 channel/component only', 'Channel',channel_vector, ... + 'type','Channels','parameter',{[1 3 7]},'nboot',101,'tfce',1); + limotest{3} = 'one sample t-tests successful'; +catch err + failures{3} = err; + fprintf('%s\n',getReport(err, 'extended', 'hyperlinks', 'off')) + limotest{3} = sprintf('one sample t-tests failed \n%s',err.message); +end + +% --------------------------------------------------------------------- +% regression +try + requireModels(list2); + % regression (calls similar routines as one sample) + % regression whole brain with an array of con files as input and a matrix as regressor + cd(fullfile(root,'2nd_level_tests')); + mkdir('regression'); cd('regression') + LIMOPath = limo_random_select('regression',STUDY.limo.chanloc,... + 'LIMOfiles',Model2_files.con,'regressor_file',randi(length(Model2_files.con),length(Model2_files.con),2),... + 'analysis_type','Full scalp analysis', 'type','Channels','zscore','yes','skip design check','yes','nboot',101,'tfce',1); + + % regression channel 50 file list of con files as input and a file as regressor + cd(fullfile(root,'2nd_level_tests')); + mkdir('regression50'); cd('regression50') + randomreg = randn(length(Model2_files.con),1); save('reg.mat','randomreg'); + LIMOPath = limo_random_select('regression',STUDY.limo.chanloc,'regressor_file',[pwd filesep 'reg.mat'],... + 'LIMOfiles',list2.con{1},... + 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels','zscore','yes','skip design check','yes','nboot',101,'tfce',1); + + % regression virtual channel - list of Betas files, select a parameter, load optimized channel file + cd(fullfile(root,'2nd_level_tests')); + mkdir('regressionOPT'); cd('regressionOPT') + LIMOPath = limo_random_select('regression',STUDY.limo.chanloc,... + 'LIMOfiles',list2.beta, ... + 'parameter',3, 'regressor_file',randi(length(Model2_files.con),length(Model2_files.con),2), ... + 'analysis_type','1 channel/component only', 'type','Channels', ... + 'Channel',fullfile(root,['2nd_level_tests' filesep 'virtual_electrode.mat']), ... + 'zscore','yes','skip design check','yes','nboot',101,'tfce',1); + limotest{4} = 'regressions successful'; +catch err + failures{4} = err; + fprintf('%s\n',getReport(err, 'extended', 'hyperlinks', 'off')) + limotest{4} = sprintf('regressions failed \n%s',err.message); +end + +% --------------------------------------------------------------------- +% paired t-test +try + requireModels(list1); + % paired t-test whole brain with a cell array of con files as input + clear data + for N=length(STUDY.subject):-1:1 + data{1,N} = Model1_files.con{N}(1); + data{2,N} = Model1_files.con{N}(2); + end + cd(fullfile(root,'2nd_level_tests')); + mkdir('paired_t-test'); cd('paired_t-test') + LIMOPath = limo_random_select('paired t-test',STUDY.limo.chanloc,... + 'LIMOfiles',data,'analysis_type','Full scalp analysis', 'type','Channels','nboot',101,'tfce',1); + + % paired t-test channel 50 with file list of con files + cd(fullfile(root,'2nd_level_tests')); + mkdir('paired_t-test50'); cd('paired_t-test50') + datafiles = {list1.con{1}, ... + list1.con{2}}; + LIMOPath = limo_random_select('paired t-test',STUDY.limo.chanloc,'LIMOfiles',datafiles,... + 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels','nboot',101,'tfce',1); + + % paired t-test virtual channel with file list of Betas + cd(fullfile(root,'2nd_level_tests')); + mkdir('paired_t-testOPT'); cd('paired_t-testOPT') + LIMOPath = limo_random_select('paired t-test',STUDY.limo.chanloc,... + 'LIMOfiles',list1.beta, ... + 'analysis_type','1 channel/component only', 'Channel',channel_vector, ... + 'type','Channels','parameter',[1 4],'nboot',101,'tfce',1); + limotest{5} = 'paired t-test successful'; +catch err + failures{5} = err; + fprintf('%s\n',getReport(err, 'extended', 'hyperlinks', 'off')) + limotest{5} = sprintf('paired t-test failed \n%s',err.message); +end + +% --------------------------------------------------------------------- +% two samples t-test +try + requireModels(list1, list2); + data = cell(2, numel(Model1_files.con)); + for subject = 1:numel(Model1_files.con) + data{1,subject} = Model1_files.con{subject}(1); + data{2,subject} = Model1_files.con{subject}(2); + end + datafiles = list1.con(1:2); + % two-samples t-test whole brain with a cell array of con files as input + cd(fullfile(root,'2nd_level_tests')); + mkdir('two-samples_t-test'); cd('two-samples_t-test') + LIMOPath = limo_random_select('two-samples t-test',STUDY.limo.chanloc,... + 'LIMOfiles',data,'analysis_type','Full scalp analysis', 'type','Channels','nboot',101,'tfce',1); + + % two-samples t-test channel 50 with file list of con files + cd(fullfile(root,'2nd_level_tests')); + mkdir('two-samples_t-test50'); cd('two-samples_t-test50') + LIMOPath = limo_random_select('two-samples t-test',STUDY.limo.chanloc,'LIMOfiles',datafiles,... + 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels','nboot',101,'tfce',1); + + % two-samples t-test virtual channel with file list of Betas + cd(fullfile(root,'2nd_level_tests')); + mkdir('two-samples_t-testOPT'); cd('two-samples_t-testOPT'); clear Bfiles + Bfiles{1} = list1.beta; + Bfiles{2} = list2.beta; + LIMOPath = limo_random_select('two-samples t-test',STUDY.limo.chanloc,... + 'LIMOfiles',Bfiles, 'analysis_type','1 channel/component only', 'Channel',repmat(channel_vector,[2,1]),... + 'type','Channels','parameter',[1 4],'nboot',101,'tfce',1); + limotest{6} = 'two samples t-test successful'; +catch err + failures{6} = err; + fprintf('%s\n',getReport(err, 'extended', 'hyperlinks', 'off')) + limotest{6} = sprintf('two samples t-test failed \n%s',err.message); +end + +% --------------------------------------------------------------------- +% 1-way ANOVA + contrast +try + requireModels(list1); + % N-Ways ANOVA whole brain with a cell array of con files as input with empty cells + clear data + index = find(arrayfun(@(x) contains(x.group,'1'), STUDY.datasetinfo)); + for s=1:length(index); data{1,s} = Model1_files.con{index(s)}(1); end + index = find(arrayfun(@(x) contains(x.group,'2'), STUDY.datasetinfo)); + for s=1:length(index); data{2,s} = Model1_files.con{index(s)}(1); end + index = find(arrayfun(@(x) contains(x.group,'3'), STUDY.datasetinfo)); + for s=1:length(index); data{3,s} = Model1_files.con{index(s)}(1); end + cd(fullfile(root,'2nd_level_tests')); + mkdir('N-Ways ANOVA'); cd('N-Ways ANOVA') + LIMOPath = limo_random_select('N-Ways ANOVA',STUDY.limo.chanloc,'LIMOfiles',data',... + 'analysis_type','Full scalp analysis', 'type','Channels','nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + + % N-Ways ANOVA channel 50 with file list of con files + % con per group files already exist split according to STUDY + datafiles = {list1.group_con{1,1}, ... + list1.group_con{2,1},... + list1.group_con{3,1}}; + cd(fullfile(root,'2nd_level_tests')); + mkdir('N-Ways ANOVA50'); cd('N-Ways ANOVA50') + LIMOPath = limo_random_select('N-Ways ANOVA',STUDY.limo.chanloc,'LIMOfiles',datafiles,... + 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels',... + 'nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + + % N-Ways ANOVA virtual channel with file list of Betas and input parameters + cd(fullfile(root,'2nd_level_tests')); + mkdir('N-Ways ANOVAOPT'); cd('N-Ways ANOVAOPT') + for g=3:-1:1 + Bfiles{g} = list1.group_beta{g}; + end + LIMOPath = limo_random_select('N-Ways ANOVA',STUDY.limo.chanloc,... + 'LIMOfiles',Bfiles, 'analysis_type','1 channel/component only', ... + 'Channel',fullfile(root,['2nd_level_tests' filesep 'virtual_electrode.mat']), ... + 'type','Channels','parameter',[1 1 1],'nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + limotest{7} = '1-way ANOVA successful'; +catch err + failures{7} = err; + fprintf('%s\n',getReport(err, 'extended', 'hyperlinks', 'off')) + limotest{7} = sprintf('1-way ANOVA failed \n%s',err.message); +end + +% --------------------------------------------------------------------- +% 1-way ANCOVA + contrast +try + requireModels(list1); + datafiles = list1.group_con(:,1)'; + Bfiles = list1.group_beta; + % N-Ways ANCOVA whole brain with a cell array of con files as input with empty cells + clear data + index = find(arrayfun(@(x) contains(x.group,'1'), STUDY.datasetinfo)); + for s=1:length(index); data{1,s} = Model1_files.con{index(s)}(1); end + index = find(arrayfun(@(x) contains(x.group,'2'), STUDY.datasetinfo)); + for s=1:length(index); data{2,s} = Model1_files.con{index(s)}(1); end + index = find(arrayfun(@(x) contains(x.group,'3'), STUDY.datasetinfo)); + for s=1:length(index); data{3,s} = Model1_files.con{index(s)}(1); end + cd(fullfile(root,'2nd_level_tests')); + mkdir('ANCOVA'); cd('ANCOVA') + LIMOPath = limo_random_select('ANCOVA',STUDY.limo.chanloc,'LIMOfiles',data,... % transpose data = wrong but let limo fix it + 'analysis_type','Full scalp analysis', 'type','Channels',... + 'regressor_file', randn(18,2), 'nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'Betas.mat'], ... + [pwd filesep 'LIMO.mat'],'T',1,[0 0 0 1 -1 0]); % contrast + limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'H0' filesep 'Betas_desc-H0.mat'], ... + [pwd filesep 'LIMO.mat'],'T',2,[0 0 0 1 -1 0]); % boostrap / tfce + + % N-Ways ANCOVA channel 50 with file list of con files + cd(fullfile(root,'2nd_level_tests')); + mkdir('ANCOVA50'); cd('ANCOVA50') + LIMOPath = limo_random_select('ANCOVA',STUDY.limo.chanloc,'LIMOfiles',datafiles(1:2),... + 'analysis_type','1 channel/component only', 'Channel',50,'type','Channels',... + 'regressor_file', randn(13,2),'nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'Betas.mat'], ... + [pwd filesep 'LIMO.mat'],'T',1,[0 0 1 -1 0]); % contrast + limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'H0' filesep 'Betas_desc-H0.mat'], ... + [pwd filesep 'LIMO.mat'],'T',2,[0 0 1 -1 0]); % boostrap / tfce + + % N-Ways ANCOVA virtual channel with file list of Betas and input parameters + cd(fullfile(root,'2nd_level_tests')); + mkdir('ANCOVAOPT'); cd('ANCOVAOPT') + LIMOPath = limo_random_select('ANCOVA',STUDY.limo.chanloc,... + 'LIMOfiles',Bfiles, 'analysis_type','1 channel/component only', 'Channel',channel_vector,... + 'regressor_file', randn(18,2),'type','Channels','parameter',[1 4 1],'nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + limotest{8} = 'ANCOVA + contrast successful'; +catch err + failures{8} = err; + fprintf('%s\n',getReport(err, 'extended', 'hyperlinks', 'off')) + limotest{8} = sprintf('1-way ANCOVA + contrast failed \n%s',err.message); +end + +% --------------------------------------------------------------------- +% Repeated measures ANOVA + contrast +try + requireModels(list1, list2); + % whole brain with Beta files as input + cd(fullfile(root,'2nd_level_tests')); + mkdir('Rep-ANOVA'); cd('Rep-ANOVA') + limo_random_select('Repeated Measures ANOVA',STUDY.limo.chanloc,'LIMOfiles',... + {list1.beta},... + 'analysis_type','Full scalp analysis','parameters',{[1 2 3],[4 5 6],[7 8 9]},... + 'factor names',{'face','repetition'},'type','Channels','nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'LIMO.mat'],... + 3,[1 1 1 -2 -2 -2 1 1 1]); % contrast + limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'LIMO.mat'],... + 4,[1 1 1 -2 -2 -2 1 1 1]); % boostrap / tfce + + % whole brain with Beta files as input split by groups + cd(fullfile(root,'2nd_level_tests')); + mkdir('GpRep-ANOVA'); cd('GpRep-ANOVA') + limo_random_select('Repeated Measures ANOVA',STUDY.limo.chanloc,'LIMOfiles',... + {list2.group_beta{1}; + list2.group_beta{2}; + list2.group_beta{3}},... + 'analysis_type','Full scalp analysis','parameters',{[1 2 3]},... % in theory {[1 2 3];[1 2 3];[1 2 3]} but it's taken care of + 'factor names',{'face'},'type','Channels','nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'LIMO.mat'],... + 3,[1 -2 1]); % contrast + limo_contrast([pwd filesep 'Yr.mat'], [pwd filesep 'LIMO.mat'],... + 4,[1 -2 1]); % boostrap / tfce + + % channel 50 with con files as input + cd(fullfile(root,'2nd_level_tests')); + clear datafiles % spurious design but we need enough subjects to run + datafiles{1,1} = list1.con{1}; + datafiles{1,2} = list2.con{1}; + datafiles{1,3} = list1.con{2}; + mkdir('Rep-ANOVA50'); cd('Rep-ANOVA50') + limo_random_select('Repeated Measures ANOVA',STUDY.limo.chanloc,'LIMOfiles',datafiles,... + 'analysis_type','1 channel/component only', 'Channel',50, 'factor names',{'face'},... + 'parameters',{[1 1 1]},'type','Channels','nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + + % also use gp + con files + optimized channel + cd(fullfile(root,'2nd_level_tests')); + clear datafiles; datafiles = cell(3,2); + datafiles{1,1} = list1.group_con{1,1}; + datafiles{1,2} = list1.group_con{1,2}; + datafiles{2,1} = list1.group_con{2,1}; + datafiles{2,2} = list1.group_con{2,2}; + datafiles{3,1} = list1.group_con{3,1}; + datafiles{3,2} = list1.group_con{3,2}; + mkdir('GpRep-ANOVAOPT'); cd('GpRep-ANOVAOPT') + limo_random_select('Repeated Measures ANOVA',STUDY.limo.chanloc,'LIMOfiles',datafiles,... + 'analysis_type','1 channel/component only', 'Channel',channel_vector, 'factor names',{'face'},... + 'parameters',{[1 1];[1 1];[1 1]},'type','Channels','nboot',101,'tfce',1,'skip design check','yes','zscore','yes'); + limotest{9} = 'Repeated measures ANOVA + contrast successful'; +catch err + failures{9} = err; + fprintf('%s\n',getReport(err, 'extended', 'hyperlinks', 'off')) + limotest{9} = sprintf('Repeated measures ANOVA + contrast failed \n%s',err.message); +end + +% --------------------------------------------------------------------- +cd(root);toc +limotest' +save(fullfile(root, 'integration_results.mat'), 'limotest', 'failures'); +if ~all(contains(limotest,'successful')) + failure = MException('EEGLAB:LimoIntegrationFailed', 'LIMO integration failed. Outputs: %s', root); + for index = 1:numel(failures) + if ~isempty(failures{index}), failure = addCause(failure, failures{index}); end + end + throw(failure); +end +end + +function requireModels(varargin) +assert(all(~cellfun(@isempty, varargin)), 'EEGLAB:LimoPrerequisiteFailed', ... + 'Second level analysis requires successful first level models and contrasts. See the original failure stacks.'); +end + + +function restoreState(folder, random) +cd(folder); +rng(random); +end diff --git a/unittesting_limo/limo_wrapperTest.m b/unittesting_limo/limo_wrapperTest.m index e6314db..ed5a1d5 100644 --- a/unittesting_limo/limo_wrapperTest.m +++ b/unittesting_limo/limo_wrapperTest.m @@ -1,12 +1,12 @@ function tests = limo_wrapperTest tests = functiontests(localfunctions); -function limo_test1(~) -p = fileparts(fileparts(which('limo_wrapperTest.m'))); -cd(fullfile(p, 'ds002718')); -limo_preproc_stats_hw; +function setupOnce(testCase) +root = fileparts(fileparts(mfilename('fullpath'))); +testCase.applyFixture(matlab.unittest.fixtures.PathFixture(fullfile(root, 'unittesting_common'))); -function limo_test2(~) -p = fileparts(fileparts(which('limo_wrapperTest.m'))); -cd(fullfile(p, 'ds002718')); -limo_test_integration; \ No newline at end of file +function test_preprocessing(~) +eeglab_test_workspace('limo_preproc_stats_hw;'); + +function test_integration(~) +eeglab_test_workspace('limo_test_integration;'); diff --git a/unittesting_studyfunc/std_editset/test_std_editset.m b/unittesting_studyfunc/std_editset/test_std_editset.m index 5b9c660..96b24b3 100644 --- a/unittesting_studyfunc/std_editset/test_std_editset.m +++ b/unittesting_studyfunc/std_editset/test_std_editset.m @@ -1,8 +1,7 @@ function test_std_editset eeglab; -prj = matlab.project.currentProject(); -tstp = char(prj.RootFolder); +tstp = fileparts(fileparts(fileparts(mfilename('fullpath')))); [STUDY, ALLEEG] = std_editset( [], [], 'commands', ... {{'index' 1 'load' fullfile(tstp, 'unittesting_studyfunc', 'teststudy2','S02', 'Ignore.set') } ... {'index' 2 'load' fullfile(tstp, 'unittesting_studyfunc', 'teststudy2','S02', 'Probe.set' ) } ... diff --git a/unittesting_tutorial/tutorial2_wrapperTest.m b/unittesting_tutorial/tutorial2_wrapperTest.m index 91e87f3..02004d6 100644 --- a/unittesting_tutorial/tutorial2_wrapperTest.m +++ b/unittesting_tutorial/tutorial2_wrapperTest.m @@ -2,6 +2,13 @@ % not registered as a test by default tests = functiontests(localfunctions); +function setupOnce(testCase) +folder = fullfile(fileparts(which('eeglab')), 'tutorial_scripts'); +testCase.applyFixture(matlab.unittest.fixtures.PathFixture(folder)); + +function setup(testCase) +testCase.addTeardown(@cd, pwd); + function test_bids_process_face_experiment(~) %p = fileparts(which('tutorial2_wrapperTest.m')); %cd(fullfile(p, '..', 'ds002718')); diff --git a/unittesting_tutorial/tutorial_wrapperTest.m b/unittesting_tutorial/tutorial_wrapperTest.m index 3014b08..7ec10c9 100644 --- a/unittesting_tutorial/tutorial_wrapperTest.m +++ b/unittesting_tutorial/tutorial_wrapperTest.m @@ -1,6 +1,13 @@ function tests = tutorial_wrapperTest tests = functiontests(localfunctions); +function setupOnce(testCase) +folder = fullfile(fileparts(which('eeglab')), 'tutorial_scripts'); +testCase.applyFixture(matlab.unittest.fixtures.PathFixture(folder)); + +function setup(testCase) +testCase.addTeardown(@cd, pwd); + function test_eeglab_history(~) eeglab_history