Skip to content

Commit

Permalink
[ENH] add QA workflow to summarize QA of functional data (#1299)
Browse files Browse the repository at this point in the history
* start adding QA workflow

* start adding QA workflow

* update changelog

* move files to slow

* activate codecov tokens

* update doc

* fix set up
  • Loading branch information
Remi-Gau authored Jul 31, 2024
1 parent 64b14da commit 8de2af8
Show file tree
Hide file tree
Showing 19 changed files with 413 additions and 62 deletions.
9 changes: 8 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ jobs:
unzip download
mv moae_fmriprep fmriprep
- name: Get data for testing QA
if: matrix.os == 'ubuntu-latest'
run: |
cd demos/openneuro/
make data_ds000114_mriqc
make data_ds000114_fmriprep
- name: Prepare test data unix
run: |
cd tests
Expand Down Expand Up @@ -156,7 +163,7 @@ jobs:
flags: ${{ matrix.os }}_matlab-${{ matrix.matlab }}_${{ matrix.mode }}
name: codecov-matlab
fail_ci_if_error: false
# token: ${{ secrets.CODECOV_TOKEN }} # not required but might help API rate limits
token: ${{ secrets.CODECOV_TOKEN }}

- name: Run system tests MATLAB ${{ matrix.script }}
if: matrix.test_type == 'system'
Expand Down
15 changes: 13 additions & 2 deletions .github/workflows/tests_octave.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
- name: Install dependencies
run: |
sudo apt-get -y -qq update
sudo apt-get -y install unzip wget
sudo apt-get -y install unzip wget git-annex
- name: Install Node
uses: actions/setup-node@v4
Expand All @@ -79,6 +79,11 @@ jobs:
submodules: recursive
fetch-depth: 0

- name: Install datalad
run: |
python -m pip install --upgrade pip setuptools
pip install datalad
- name: Install validators
run: make install

Expand All @@ -94,6 +99,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
Expand Down Expand Up @@ -149,7 +160,7 @@ jobs:
flags: octave
name: codecov-octave_${{ matrix.mode }}
fail_ci_if_error: false
# token: ${{ secrets.CODECOV_TOKEN }} # not required but might help API rate limits
# token: ${{ secrets.CODECOV_TOKEN }}

- name: Run system tests octave ${{ matrix.script }}
if: matrix.test_type == 'system'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests_windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ jobs:
flags: ${{ matrix.os }}_matlab-${{ matrix.matlab }}_${{ matrix.mode }}
name: codecov-matlab
fail_ci_if_error: false
# token: ${{ secrets.CODECOV_TOKEN }} # not required but might help API rate limits
# token: ${{ secrets.CODECOV_TOKEN }}

- name: Run system tests MATLAB ${{ matrix.script }}
if: matrix.test_type == 'system'
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

* [ENH] add QA workflow `bidsQA` to find outliers in MRIQC output or to view number of outlier timepoints (for a given metric and threshold) in each functional run #1297 by @Remi-Gau
* [ENH] add support for one-way ANOVA across groups at the group level #1296 by @Remi-Gau
* [ENH] allow for 2 sample T-Test, within group T-Test and one-way ANOVA to ne more flexible with respect to what praticipants.tsv column to use to allocate subjects in each group #1296 by @Remi-Gau
* [ENH] make `addConfoundsToDesignMatrix` a method of `BidsModel` #1294 by @Remi-Gau
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ then bidspm has some automated workflows to perform amongst other things:
All (well almost all) preprocessed outputs are saved as BIDS derivatives
with BIDS compliant filenames.

### Quality control:
### Quality control

- anatomical data (work in progress)
- functional data (work in progress)
Expand Down
30 changes: 20 additions & 10 deletions demos/openneuro/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,40 @@ clean:

data: data_ds000001 data_ds000114 data_ds001168

data_ds000001:
data_ds000001_fmriprep:
cd inputs && datalad install ///openneuro-derivatives/ds000001-fmriprep
cd inputs/ds000001-fmriprep && datalad get sub-*/func/*tsv -J 12
cd inputs/ds000001-fmriprep && datalad get sub-*/func/*json -J 12

data_ds000001: data_ds000001_fmriprep
mkdir -p inputs
cd inputs && datalad install ///openneuro/ds000001
cd inputs && datalad install ///openneuro-derivatives/ds000001-fmriprep
cd inputs/ds000001 && datalad get sub-0[1-5] -J 3
cd inputs/ds000001-fmriprep && datalad get sub-0[1-5]/func/*tsv -J 12
cd inputs/ds000001-fmriprep && datalad get sub-0[1-5]/func/*json -J 12
cd inputs/ds000001-fmriprep && datalad get sub-0[1-5]/anat/*MNI*desc-preproc*.nii.gz -J 12
cd inputs/ds000001-fmriprep && datalad get sub-0[1-5]/func/*MNI*desc-preproc*.nii.gz -J 12
cd inputs/ds000001-fmriprep && datalad get sub-0[1-3]/func/*MNI*desc-*bold.nii.gz -J 12

data_ds000114_raw:
mkdir -p inputs
cd inputs && datalad install ///openneuro/ds000114
cd inputs/ds000114 && datalad get sub-0[1-2]/ses-*/anat/*T1w*.nii.gz -J 12
cd inputs/ds000114 && datalad get sub-0[1-2]/ses-*/func/*linebisection* -J 12
cd inputs/ds000114 && datalad get sub-0[1-5]/ses-*/anat/*T1w*.nii.gz -J 12
cd inputs/ds000114 && datalad get sub-0[1-5]/ses-*/func/*bold* -J 12

data_ds000114:
data_ds000114_mriqc:
mkdir -p inputs
cd inputs && datalad install ///openneuro-derivatives/ds000114-mriqc
cd inputs/ds000114-mriqc && datalad get *.tsv

data_ds000114_fmriprep:
mkdir -p inputs
cd inputs && datalad install ///openneuro/ds000114
cd inputs && datalad install ///openneuro-derivatives/ds000114-fmriprep
cd inputs/ds000114-fmriprep && datalad get sub-0*/ses-*/func/*tsv -J 12
cd inputs/ds000114-fmriprep && datalad get sub-0*/ses-*/func/*json -J 12

data_ds000114: data_ds000114_fmriprep
mkdir -p inputs
cd inputs && datalad install ///openneuro/ds000114
cd inputs/ds000114-fmriprep && datalad get sub-0[1-2]/anat/*MNI*desc-preproc*.nii.gz -J 12
cd inputs/ds000114-fmriprep && datalad get sub-0[1-2]/ses-*/func/*tsv -J 12
cd inputs/ds000114-fmriprep && datalad get sub-0[1-2]/ses-*/func/*json -J 12
cd inputs/ds000114-fmriprep && datalad get sub-0[1-2]/ses-*/func/*MNI*_mask.nii.gz -J 12
cd inputs/ds000114-fmriprep && datalad get sub-0[1-2]/ses-*/func/*MNI*desc-preproc*bold.nii.gz -J 12

Expand Down
17 changes: 12 additions & 5 deletions demos/openneuro/ds000114_preproc_run.m
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
% Show how to use filter to only include one session to preprocess.

% (C) Copyright 2023 bidspm developers

clear;
Expand All @@ -6,24 +8,29 @@
addpath(fullfile(pwd, '..', '..'));
bidspm();

participant_label = {'01'};
participant_label = {'04', '05'};
TASK = 'linebisection';
session_to_select = {'test'};

root_dir = fileparts(mfilename('fullpath'));
bids_dir = fullfile(root_dir, 'inputs', 'ds000114');
output_dir = fullfile(root_dir, 'outputs', 'ds000114', 'derivatives');
preproc = fullfile(output_dir, 'bidspm-preproc');

%% Copy
% To make sure we still have copied all the data
bidspm(bids_dir, output_dir, 'subject', ...
'participant_label', participant_label, ...
'action', 'copy', ...
'task', TASK, ...
'skip_validation', true, ...
'verbosity', 3);

bids_filter_file = struct( ...
'bold', struct('modality', 'func', 'suffix', 'bold', 'ses', 'retest'), ...
't1w', struct('modality', 'anat', 'suffix', 'T1w'));
%% Set filter and preprocess
bids_filter_file = struct('bold', struct('modality', 'func', ...
'suffix', 'bold', ...
'ses', {session_to_select}), ...
't1w', struct('modality', 'anat', ...
'suffix', 'T1w'));
bidspm(bids_dir, output_dir, 'subject', ...
'participant_label', participant_label, ...
'action', 'preprocess', ...
Expand Down
13 changes: 10 additions & 3 deletions docs/source/API.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ workflows
.. autofunction:: src.workflows.bidsCheckVoxelSize
.. autofunction:: src.workflows.bidsCopyInputFolder
.. autofunction:: src.workflows.bidsInverseNormalize
.. autofunction:: src.workflows.bidsQApreproc
.. autofunction:: src.workflows.bidsRename
.. autofunction:: src.workflows.bidsReport

Expand All @@ -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
Expand Down Expand Up @@ -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
-------
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -204,6 +208,9 @@ bidspm
data
----

__pycache__
-----------

cli
===
.. autofunction:: src.cli.baseInputParser
Expand Down
9 changes: 5 additions & 4 deletions docs/source/QA.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ Quality control

The illustrations in this section mix what the files created by each workflow
and the functions and are called by it.
In this sense they are not pure DAGs (directed acyclic graphs) as the ``*.m`` files
mentioned in them already exist.
In this sense they are not pure DAGs (directed acyclic graphs)
as the ``*.m`` files mentioned in them already exist.


- :func:`anatQA`
- :func:`bidsQApreproc`
- :func:`bidsQA`
- :func:`bidsQAbidspm`

.. _fig_spatialPrepro-reports:
.. figure:: preprocessing/images/bidsSpatialPrepro/out_report.png
:align: center

workflows for QA as part of the spatial preprocessing workflow

- :func:`anatQA`
- :func:`computeDesignEfficiency`
- :func:`plotEvents`
21 changes: 12 additions & 9 deletions src/IO/getData.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@
%
% :param opt: Options chosen for the analysis.
% See :func:`checkOptions`.
%
% :type opt: structure
%
% :param bidsDir: the directory where the data is ; defaults to
% ``fullfile(opt.dataDir, '..', 'derivatives', 'bidspm')``
%
% :type bidsDir: char
%
% :param indexDependencies: Use ``'index_dependencies', true``
% in bids.layout.
%
% :type indexDependencies: logical
%
% :param layoutFilter: filter to pass to bids.layout. default = struct()
% :type layoutFilter: struct
%
%
% :return: opt
% :rtype: structure
%
Expand All @@ -38,6 +39,7 @@
addRequired(args, 'opt', @isstruct);
addRequired(args, 'bidsDir', isFolder);
addOptional(args, 'indexDependencies', true, @islogical);
addOptional(args, 'layoutFilter', struct(), @isstruct);

try
parse(args, varargin{:});
Expand All @@ -60,6 +62,8 @@
indexDependencies = false;
end

layoutFilter = args.Results.layoutFilter;

anatOnly = false;
if isfield(opt, 'anatOnly')
anatOnly = opt.anatOnly;
Expand All @@ -72,19 +76,18 @@

validationInputFile(bidsDir, 'dataset_description.json');

layout_filter = struct([]);
if ~isempty(opt.subjects{1}) && ~ismember('', opt.subjects)
layout_filter = struct('sub', {opt.subjects});
layoutFilter.sub = opt.subjects;
end

if anatOnly
layout_filter(1).modality = {'anat'};
layoutFilter(1).modality = {'anat'};
end

BIDS = bids.layout(bidsDir, ...
'use_schema', opt.useBidsSchema, ...
'verbose', opt.verbosity > 1, ...
'filter', layout_filter, ...
'filter', layoutFilter, ...
'index_dependencies', indexDependencies);

if strcmp(opt.pipeline.type, 'stats') && ~opt.pipeline.isBms
Expand All @@ -96,15 +99,15 @@

if isempty(fieldnames(tmp))
BIDS.raw = bids.layout(opt.dir.raw, ...
'filter', layout_filter, ...
'filter', layoutFilter, ...
'index_dependencies', indexDependencies);
else
BIDS.raw = tmp.BIDS;
end
else
BIDS.raw = bids.layout(opt.dir.raw, ...
'verbose', opt.verbosity > 1, ...
'filter', layout_filter);
'filter', layoutFilter);
end
end

Expand Down
10 changes: 8 additions & 2 deletions src/utils/setUpWorkflow.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
%
% USAGE::
%
% [BIDS, opt, group] = setUpWorkflow(opt, workflowName, bidsDir, indexData)
% [BIDS, opt] = setUpWorkflow(opt, workflowName, bidsDir, indexData, index_dependencies)
%
% :param opt: Options chosen for the analysis.
% See :func:`checkOptions`.
Expand All @@ -32,6 +32,10 @@
% in bids.layout. default = true
% :type index_dependencies: logical
%
% :param layoutFilter: filter to pass to bids.layout. default = struct()
% :type layoutFilter: struct
%
%
% :returns: ``BIDS`` layout returned by ``getData``, ``opt`` options checked
% :rtype: structure, structure
%
Expand All @@ -47,6 +51,7 @@
addOptional(args, 'bidsDir', '', @ischar);
addOptional(args, 'indexData', true, @islogical);
addOptional(args, 'indexDependencies', true, @islogical);
addOptional(args, 'layoutFilter', struct(), @isstruct);

parse(args, varargin{:});

Expand All @@ -55,6 +60,7 @@
bidsDir = args.Results.bidsDir;
indexData = args.Results.indexData;
indexDependencies = args.Results.indexDependencies;
layoutFilter = args.Results.layoutFilter;

if isempty(bidsDir)
bidsDir = opt.dir.input;
Expand All @@ -67,7 +73,7 @@
opt = loadAndCheckOptions(opt);

if indexData
[BIDS, opt] = getData(opt, bidsDir, indexDependencies);
[BIDS, opt] = getData(opt, bidsDir, indexDependencies, layoutFilter);
end

if strcmp(opt.pipeline.type, 'stats') && ...
Expand Down
Loading

0 comments on commit 8de2af8

Please sign in to comment.