diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 818bac6c..804e021a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -121,6 +121,7 @@ jobs: run: | cd demos/openneuro/ make data_ds000114_mriqc + make data_ds000114_fmriprep - name: Prepare test data unix run: | diff --git a/.github/workflows/tests_octave.yml b/.github/workflows/tests_octave.yml index 92a46c57..0de29b25 100644 --- a/.github/workflows/tests_octave.yml +++ b/.github/workflows/tests_octave.yml @@ -94,6 +94,12 @@ jobs: unzip download mv moae_fmriprep fmriprep + - name: Get data for testing QA + run: | + cd demos/openneuro/ + make data_ds000114_mriqc + make data_ds000114_fmriprep + - name: Prepare test data run: | cd tests diff --git a/docs/source/API.rst b/docs/source/API.rst index 4f16e8e3..d226f4f4 100644 --- a/docs/source/API.rst +++ b/docs/source/API.rst @@ -33,7 +33,6 @@ workflows .. autofunction:: src.workflows.bidsCheckVoxelSize .. autofunction:: src.workflows.bidsCopyInputFolder .. autofunction:: src.workflows.bidsInverseNormalize -.. autofunction:: src.workflows.bidsQAbidspm .. autofunction:: src.workflows.bidsRename .. autofunction:: src.workflows.bidsReport @@ -42,6 +41,13 @@ roi .. autofunction:: src.workflows.roi.bidsCreateROI .. autofunction:: src.workflows.roi.bidsRoiBasedGLM +QA +-- +.. autofunction:: src.workflows.QA.bidsQA +.. autofunction:: src.workflows.QA.bidsQAbidspm +.. autofunction:: src.workflows.QA.bidsQAmriqc +.. autofunction:: src.workflows.QA.bidsQApreproc + lesion ------ .. autofunction:: src.workflows.lesion.bidsLesionAbnormalitiesDetection @@ -103,7 +109,6 @@ stats .. autofunction:: src.batches.stats.setBatchSubjectLevelContrasts .. autofunction:: src.batches.stats.setBatchSubjectLevelGLMSpec .. autofunction:: src.batches.stats.setBatchSubjectLevelResults -.. autofunction:: src.batches.stats.setBatchTwoSampleTTest preproc ------- @@ -157,7 +162,6 @@ QA .. autofunction:: src.QA.computeFDandRMS .. autofunction:: src.QA.computeRobustOutliers .. autofunction:: src.QA.createDesignMatrix -.. autofunction:: src.QA.mriqcQA .. autofunction:: src.QA.plotConfounds .. autofunction:: src.QA.plotEvents .. autofunction:: src.QA.plotRoiTimeCourse @@ -204,6 +208,9 @@ bidspm data ---- +__pycache__ +----------- + cli === .. autofunction:: src.cli.baseInputParser diff --git a/src/workflows/QA/bidsQA.m b/src/workflows/QA/bidsQA.m index 13e283b3..79215282 100644 --- a/src/workflows/QA/bidsQA.m +++ b/src/workflows/QA/bidsQA.m @@ -1,10 +1,25 @@ -function bidsQA(opt, varargin) +function filename = bidsQA(opt, varargin) % % Run QA on a BIDS dataset. % + % Find outliers in MRIQC output + % or to view number of outlier timepoints + % (for a given metric and threshold) in each functional run + % + % + % USAGE:: + % + % figurePath = bidsQA(opt, varargin); + % + % :param opt: Options chosen for the analysis. + % See :func:`checkOptions`. + % :type opt: structure + % % (C) Copyright 2024 Remi Gau + filename = ''; + dsDesc = bids.util.jsondecode(fullfile(opt.dir.input, 'dataset_description.json')); switch dsDesc.DatasetType @@ -31,7 +46,7 @@ function bidsQA(opt, varargin) bidsQAmriqc(opt, 'bold'); elseif any(ismember({'fMRIPrep', 'bidspm'}, pipelines)) - bidsQApreproc(opt, varargin{:}); + filename = bidsQApreproc(opt, varargin{:}); end end end diff --git a/src/workflows/QA/bidsQAmriqc.m b/src/workflows/QA/bidsQAmriqc.m index 738b3b3d..dc3c3084 100644 --- a/src/workflows/QA/bidsQAmriqc.m +++ b/src/workflows/QA/bidsQAmriqc.m @@ -1,4 +1,4 @@ -function mriqcQA(opt, suffix) +function bidsQAmriqc(opt, suffix) % % uses the report from MRIQC (bold and T1) identify outliers using % robust statistics (interquartile range). @@ -8,7 +8,7 @@ function mriqcQA(opt, suffix) % % USAGE:: % - % mriqcQA(opt, suffix); + % bidsQAmriqc(opt, suffix); % % :type opt: structure % :param opt: Options chosen for the analysis. @@ -18,8 +18,8 @@ function mriqcQA(opt, suffix) % % opt.dir.mriqc = '/home/remi/gin/dataset/derivatives/mriqc'; % - % mriqcQA(opt, 'T1w'); - % mriqcQA(opt, 'bold'); + % bidsQAmriqc(opt, 'T1w'); + % bidsQAmriqc(opt, 'bold'); % % Dependencies (in case you want to use it as standalone): % diff --git a/src/workflows/QA/bidsQApreproc.m b/src/workflows/QA/bidsQApreproc.m index c89ab61b..d3b012c3 100644 --- a/src/workflows/QA/bidsQApreproc.m +++ b/src/workflows/QA/bidsQApreproc.m @@ -1,11 +1,25 @@ -function bidsQApreproc(opt, varargin) +function filename = bidsQApreproc(opt, varargin) % % Use confounds preprocessed datasets to estimate % the number of timepoints with values superior to threshold. % + % Plots the proportion of timepoints per run + % (to identify runs with that goes above a limit). + % + % USAGE:: + % + % figurePath = bidsQApreproc(opt, 'metric', metric, 'threshold', threshold); + % + % :param opt: Options chosen for the analysis. + % See :func:`checkOptions`. + % :type opt: structure + % + % :param metric: default = 'framewise_displacement' + % :type metric: char + % + % :param threshold: default = 0.2 + % :param threshold: numeric % - % plots the proportion of timepoints per run - % (to identify runs with that goes above a limit) % (C) Copyright 2024 bidspm developers @@ -81,6 +95,9 @@ function bidsQApreproc(opt, varargin) plotFigure(df, metric, threshold); + output_path = fullfile(opt.dir.output, 'reports'); + filename = printFigure(output_path, metric); + end function plotFigure(df, metric, threshold) @@ -128,3 +145,16 @@ function plotFigure(df, metric, threshold) 'fontsize', 8, 'tickdir', 'out'); end + +function filename = printFigure(outputPath, metric) + bids.util.mkdir(outputPath); + + filename = regexprep(['outliers_', metric, '.png'], ' - ', '_'); + filename = regexprep(filename, ' ', '-'); + filename = regexprep(filename, '[\(\)]', ''); + filename = fullfile(outputPath, filename); + + print(filename, '-dpng'); + fprintf('Figure saved:\n\t%s\n', filename); + +end diff --git a/tests/tests_slow/tests_workflows/QA/test_bidsQA.m b/tests/tests_slow/tests_workflows/QA/test_bidsQA.m new file mode 100644 index 00000000..7dabedc2 --- /dev/null +++ b/tests/tests_slow/tests_workflows/QA/test_bidsQA.m @@ -0,0 +1,100 @@ +function test_suite = test_bidsQA %#ok<*STOUT> + + % (C) Copyright 2024 bidspm developers + + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions = localfunctions(); %#ok<*NASGU> + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; +end + +function test_bidsQA_raw() + + % markTestAs('slow'); + % + % opt = setOptions('vislocalizer'); + % + % expectedOutput = fullfile(opt.dir.output, ... + % 'reports', ... + % 'bidspm-raw_split_by-task.png'); + % + % if exist(expectedOutput, 'file') + % delete(expectedOutput) + % end + % + % bidsQA(opt) + % + % assertEqual(exist(expectedOutput, 'file'), 2) + +end + +function test_bidsQA_mriqc() + + % markTestAs('slow'); + % + % if ispc() + % moxunit_throw_test_skipped_exception('requires datalad setup'); + % end + % + % ds000114mriqc = spm_file(fullfile(getTestDir(), ... + % '..', 'demos', 'openneuro', ... + % 'inputs', 'ds000114-mriqc'), 'cpath'); + % + % opt.dir.input = ds000114mriqc; + % + % bidsQA(opt); + +end + +function test_bidsQA_bidspm() + + markTestAs('slow'); + + if ispc() + moxunit_throw_test_skipped_exception('requires datalad setup'); + end + + opt = setOptions('vislocalizer'); + + opt.dir.input = opt.dir.preproc; + + opt.dir.output = ''; + opt.dir.derivatives = tempName(); + + opt = checkOptions(opt); + + threshold = 0.0001; + metric = 'rot_x'; + + filename = bidsQA(opt, 'metric', metric, 'threshold', threshold); + + assertEqual(exist(filename, 'file'), 2); + +end + +function test_bidsQA_fmriprep() + + markTestAs('slow'); + + ds000114fmriprep = spm_file(fullfile(getTestDir(), ... + '..', 'demos', 'openneuro', ... + 'inputs', 'ds000114-fmriprep'), 'cpath'); + + opt.dir.input = ds000114fmriprep; + opt.dir.preproc = ds000114fmriprep; + opt.dir.derivatives = tempName(); + opt.pipeline.type = 'preproc'; + opt.verbosity = 0; + + opt.taskName = {'overtverbgeneration', 'linebisection'}; + + opt = checkOptions(opt); + + opt.subjects = {'01', '02', '03', '10'}; + + filename = bidsQA(opt); + + assertEqual(exist(filename, 'file'), 2); + +end diff --git a/tests/tests_workflows/QA/test_bidsQA.m b/tests/tests_workflows/QA/test_bidsQA.m deleted file mode 100644 index e53d749e..00000000 --- a/tests/tests_workflows/QA/test_bidsQA.m +++ /dev/null @@ -1,80 +0,0 @@ -function test_suite = test_bidsQA %#ok<*STOUT> - - % (C) Copyright 2024 bidspm developers - - try % assignment of 'localfunctions' is necessary in Matlab >= 2016 - test_functions = localfunctions(); %#ok<*NASGU> - catch % no problem; early Matlab versions can use initTestSuite fine - end - initTestSuite; -end - -function test_bidsQA_raw() - - % opt = setOptions('vislocalizer'); - % - % expectedOutput = fullfile(opt.dir.output, ... - % 'reports', ... - % 'bidspm-raw_split_by-task.png'); - % - % if exist(expectedOutput, 'file') - % delete(expectedOutput) - % end - % - % bidsQA(opt) - % - % assertEqual(exist(expectedOutput, 'file'), 2) - -end - -function test_bidsQA_mriqc() - - ds000114mriqc = spm_file(fullfile(getTestDir(), ... - '..', 'demos', 'openneuro', ... - 'inputs', 'ds000114-mriqc'), 'cpath'); - - opt.dir.input = ds000114mriqc; - - bidsQA(opt); - -end - -function test_bidsQA_bidspm() - - % opt = setOptions('vislocalizer'); - % - % opt.dir.input = opt.dir.preproc; - % - % opt.dir.output = ''; - % opt.dir.derivatives = tempName(); - % - % opt = checkOptions(opt); - % - % threshold = 0.0001; - % metric = 'rot_x'; - % - % bidsQA(opt, 'metric', metric, 'threshold', threshold); - -end - -function test_bidsQA_fmriprep() - - ds000114fmriprep = spm_file(fullfile(getTestDir(), ... - '..', 'demos', 'openneuro', ... - 'inputs', 'ds000114-fmriprep'), 'cpath'); - - opt.dir.input = ds000114fmriprep; - opt.dir.preproc = ds000114fmriprep; - opt.dir.derivatives = tempName(); - opt.pipeline.type = 'preproc'; - opt.verbosity = 0; - - opt.taskName = {'overtverbgeneration', 'linebisection'}; - - opt = checkOptions(opt); - - opt.subjects = {'01', '02', '03', '10'}; - - bidsQA(opt); - -end diff --git a/tests/tests_workflows/QA/test_bidsQAmriqc.m b/tests/tests_workflows/QA/test_bidsQAmriqc.m deleted file mode 100644 index 9846a736..00000000 --- a/tests/tests_workflows/QA/test_bidsQAmriqc.m +++ /dev/null @@ -1,16 +0,0 @@ -function test_suite = test_bidsQAmriqc %#ok<*STOUT> - % (C) Copyright 2022 bidspm developers - try % assignment of 'localfunctions' is necessary in Matlab >= 2016 - test_functions = localfunctions(); %#ok<*NASGU> - catch % no problem; early Matlab versions can use initTestSuite fine - end - initTestSuite; -end - -function test_mriqc_basic() - - opt.dir.mriqc = fullfile(getTestDataDir, 'tsv_files'); - - bidsQAmriqc(opt, 'T1w'); - -end