Skip to content

Commit c7c428c

Browse files
authored
Merge pull request #1708 from rciric/confmeta
ENH: Confounds metadata
2 parents 243b227 + b39bd20 commit c7c428c

File tree

5 files changed

+56
-10
lines changed

5 files changed

+56
-10
lines changed

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ RUN mkdir -p /opt/ICA-AROMA && \
112112
curl -sSL "https://github.com/maartenmennes/ICA-AROMA/archive/v0.4.4-beta.tar.gz" \
113113
| tar -xzC /opt/ICA-AROMA --strip-components 1 && \
114114
chmod +x /opt/ICA-AROMA/ICA_AROMA.py
115-
ENV PATH=/opt/ICA-AROMA:$PATH
115+
ENV PATH="/opt/ICA-AROMA:$PATH" \
116+
AROMA_VERSION="0.4.4-beta"
116117

117118
# Installing and setting up miniconda
118119
RUN curl -sSLO https://repo.continuum.io/miniconda/Miniconda3-4.5.11-Linux-x86_64.sh && \

fmriprep/interfaces/confounds.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class ICAConfoundsOutputSpec(TraitedSpec):
107107
File(exists=True, desc='output confounds file extracted from ICA-AROMA'))
108108
aroma_noise_ics = File(exists=True, desc='ICA-AROMA noise components')
109109
melodic_mix = File(exists=True, desc='melodic mix file')
110+
aroma_metadata = File(exists=True, desc='tabulated ICA-AROMA metadata')
110111

111112

112113
class ICAConfounds(SimpleInterface):
@@ -116,8 +117,12 @@ class ICAConfounds(SimpleInterface):
116117
output_spec = ICAConfoundsOutputSpec
117118

118119
def _run_interface(self, runtime):
119-
aroma_confounds, motion_ics_out, melodic_mix_out = _get_ica_confounds(
120-
self.inputs.in_directory, self.inputs.skip_vols, newpath=runtime.cwd)
120+
(aroma_confounds,
121+
motion_ics_out,
122+
melodic_mix_out,
123+
aroma_metadata) = _get_ica_confounds(self.inputs.in_directory,
124+
self.inputs.skip_vols,
125+
newpath=runtime.cwd)
121126

122127
if self.inputs.err_on_aroma_warn and aroma_confounds is None:
123128
raise RuntimeError('ICA-AROMA failed')
@@ -126,6 +131,7 @@ def _run_interface(self, runtime):
126131

127132
self._results['aroma_noise_ics'] = motion_ics_out
128133
self._results['melodic_mix'] = melodic_mix_out
134+
self._results['aroma_metadata'] = aroma_metadata
129135
return runtime
130136

131137

@@ -218,10 +224,12 @@ def _get_ica_confounds(ica_out_dir, skip_vols, newpath=None):
218224
# load the txt files from ICA-AROMA
219225
melodic_mix = os.path.join(ica_out_dir, 'melodic.ica/melodic_mix')
220226
motion_ics = os.path.join(ica_out_dir, 'classified_motion_ICs.txt')
227+
aroma_metadata = os.path.join(ica_out_dir, 'classification_overview.txt')
221228

222229
# Change names of motion_ics and melodic_mix for output
223230
melodic_mix_out = os.path.join(newpath, 'MELODICmix.tsv')
224231
motion_ics_out = os.path.join(newpath, 'AROMAnoiseICs.csv')
232+
aroma_metadata_out = os.path.join(newpath, 'classification_overview.tsv')
225233

226234
# copy metion_ics file to derivatives name
227235
shutil.copyfile(motion_ics, motion_ics_out)
@@ -238,18 +246,27 @@ def _get_ica_confounds(ica_out_dir, skip_vols, newpath=None):
238246
# save melodic_mix_arr
239247
np.savetxt(melodic_mix_out, melodic_mix_arr, delimiter='\t')
240248

249+
# process the metadata so that the IC column entries match the BIDS name of
250+
# the regressor
251+
aroma_metadata = pd.read_csv(aroma_metadata, sep='\t')
252+
aroma_metadata['IC'] = [
253+
'aroma_motion_{}'.format(name) for name in aroma_metadata['IC']]
254+
aroma_metadata.columns = [
255+
re.sub('[ |\-|\/]', '_', c) for c in aroma_metadata.columns]
256+
aroma_metadata.to_csv(aroma_metadata_out, sep='\t', index=False)
257+
241258
# Return dummy list of ones if no noise compnents were found
242259
if motion_ic_indices.size == 0:
243260
LOGGER.warning('No noise components were classified')
244-
return None, motion_ics_out, melodic_mix_out
261+
return None, motion_ics_out, melodic_mix_out, aroma_metadata_out
245262

246263
# the "good" ics, (e.g., not motion related)
247264
good_ic_arr = np.delete(melodic_mix_arr, motion_ic_indices, 1).T
248265

249266
# return dummy lists of zeros if no signal components were found
250267
if good_ic_arr.size == 0:
251268
LOGGER.warning('No signal components were classified')
252-
return None, motion_ics_out, melodic_mix_out
269+
return None, motion_ics_out, melodic_mix_out, aroma_metadata_out
253270

254271
# transpose melodic_mix_arr so x refers to the correct dimension
255272
aggr_confounds = np.asarray([melodic_mix_arr.T[x] for x in motion_ic_indices])
@@ -260,7 +277,7 @@ def _get_ica_confounds(ica_out_dir, skip_vols, newpath=None):
260277
columns=['aroma_motion_%02d' % (x + 1) for x in motion_ic_indices]).to_csv(
261278
aroma_confounds, sep="\t", index=None)
262279

263-
return aroma_confounds, motion_ics_out, melodic_mix_out
280+
return aroma_confounds, motion_ics_out, melodic_mix_out, aroma_metadata_out
264281

265282

266283
class FMRISummaryInputSpec(BaseInterfaceInputSpec):

fmriprep/workflows/bold/base.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
2323
from niworkflows.interfaces.cifti import GenerateCifti
24+
from niworkflows.interfaces.utils import DictMerge
2425

2526
from ...utils.meepi import combine_meepi_source
2627

@@ -875,10 +876,17 @@ def init_func_preproc_wf(
875876
function=_to_join),
876877
name='aroma_confounds')
877878

879+
mrg_conf_metadata = pe.Node(niu.Merge(2), name='merge_confound_metadata',
880+
run_without_submitting=True)
881+
mrg_conf_metadata2 = pe.Node(DictMerge(), name='merge_confound_metadata2',
882+
run_without_submitting=True)
878883
workflow.disconnect([
879884
(bold_confounds_wf, outputnode, [
880885
('outputnode.confounds_file', 'confounds'),
881886
]),
887+
(bold_confounds_wf, outputnode, [
888+
('outputnode.confounds_metadata', 'confounds_metadata'),
889+
]),
882890
])
883891
workflow.connect([
884892
(bold_std_trans_wf, ica_aroma_wf, [
@@ -893,13 +901,19 @@ def init_func_preproc_wf(
893901
('outputnode.skip_vols', 'inputnode.skip_vols')]),
894902
(bold_confounds_wf, join, [
895903
('outputnode.confounds_file', 'in_file')]),
904+
(bold_confounds_wf, mrg_conf_metadata,
905+
[('outputnode.confounds_metadata', 'in1')]),
896906
(ica_aroma_wf, join,
897907
[('outputnode.aroma_confounds', 'join_file')]),
908+
(ica_aroma_wf, mrg_conf_metadata,
909+
[('outputnode.aroma_metadata', 'in2')]),
910+
(mrg_conf_metadata, mrg_conf_metadata2, [('out', 'in_dicts')]),
898911
(ica_aroma_wf, outputnode,
899912
[('outputnode.aroma_noise_ics', 'aroma_noise_ics'),
900913
('outputnode.melodic_mix', 'melodic_mix'),
901914
('outputnode.nonaggr_denoised_file', 'nonaggr_denoised_file')]),
902915
(join, outputnode, [('out_file', 'confounds')]),
916+
(mrg_conf_metadata2, outputnode, [('out_dict', 'confounds_metadata')]),
903917
])
904918

905919
# SURFACES ##################################################################################

fmriprep/workflows/bold/confounds.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
.. autofunction:: init_ica_aroma_wf
1010
1111
"""
12+
from os import getenv
1213
from nipype.pipeline import engine as pe
1314
from nipype.interfaces import utility as niu, fsl
1415
from nipype.algorithms import confounds as nac
@@ -245,8 +246,9 @@ def init_bold_confs_wf(
245246
acompcor.inputs.repetition_time = metadata['RepetitionTime']
246247

247248
# Global and segment regressors
249+
signals_class_labels = ["csf", "white_matter", "global_signal"]
248250
mrg_lbl = pe.Node(niu.Merge(3), name='merge_rois', run_without_submitting=True)
249-
signals = pe.Node(SignalExtraction(class_labels=["csf", "white_matter", "global_signal"]),
251+
signals = pe.Node(SignalExtraction(class_labels=signals_class_labels),
250252
name="signals", mem_gb=mem_gb)
251253

252254
# Arrange confounds
@@ -270,8 +272,10 @@ def init_bold_confs_wf(
270272
TSV2JSON(index_column='component', output=None,
271273
additional_metadata={'Method': 'aCompCor'}, enforce_case=True),
272274
name='acc_metadata_fmt')
273-
mrg_conf_metadata = pe.Node(niu.Merge(2), name='merge_confound_metadata',
275+
mrg_conf_metadata = pe.Node(niu.Merge(3), name='merge_confound_metadata',
274276
run_without_submitting=True)
277+
mrg_conf_metadata.inputs.in3 = {label: {'Method': 'Mean'}
278+
for label in signals_class_labels}
275279
mrg_conf_metadata2 = pe.Node(DictMerge(), name='merge_confound_metadata2',
276280
run_without_submitting=True)
277281

@@ -648,7 +652,7 @@ def init_ica_aroma_wf(metadata, mem_gb, omp_nthreads,
648652

649653
outputnode = pe.Node(niu.IdentityInterface(
650654
fields=['aroma_confounds', 'aroma_noise_ics', 'melodic_mix',
651-
'nonaggr_denoised_file']), name='outputnode')
655+
'nonaggr_denoised_file', 'aroma_metadata']), name='outputnode')
652656

653657
select_std = pe.Node(KeySelect(
654658
fields=['bold_mask_std', 'bold_std']),
@@ -687,6 +691,13 @@ def _getusans_func(image, thresh):
687691
ica_aroma_confound_extraction = pe.Node(ICAConfounds(err_on_aroma_warn=err_on_aroma_warn),
688692
name='ica_aroma_confound_extraction')
689693

694+
ica_aroma_metadata_fmt = pe.Node(
695+
TSV2JSON(index_column='IC', output=None, enforce_case=True,
696+
additional_metadata={'Method': {
697+
'Name': 'ICA-AROMA',
698+
'Version': getenv('AROMA_VERSION', 'n/a')}}),
699+
name='ica_aroma_metadata_fmt')
700+
690701
ds_report_ica_aroma = pe.Node(
691702
DerivativesDataSink(desc='aroma', keep_dtype=True),
692703
name='ds_report_ica_aroma', run_without_submitting=True,
@@ -732,10 +743,13 @@ def _getbtthresh(medianval):
732743
(ica_aroma, ica_aroma_confound_extraction, [('out_dir', 'in_directory')]),
733744
(inputnode, ica_aroma_confound_extraction, [
734745
('skip_vols', 'skip_vols')]),
746+
(ica_aroma_confound_extraction, ica_aroma_metadata_fmt, [
747+
('aroma_metadata', 'in_file')]),
735748
# output for processing and reporting
736749
(ica_aroma_confound_extraction, outputnode, [('aroma_confounds', 'aroma_confounds'),
737750
('aroma_noise_ics', 'aroma_noise_ics'),
738751
('melodic_mix', 'melodic_mix')]),
752+
(ica_aroma_metadata_fmt, outputnode, [('output', 'aroma_metadata')]),
739753
(ica_aroma, add_non_steady_state, [
740754
('nonaggr_denoised_file', 'bold_cut_file')]),
741755
(select_std, add_non_steady_state, [

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ classifiers =
2020
[options]
2121
python_requires = >=3.5
2222
install_requires =
23-
niworkflows ~= 0.10.3
23+
niworkflows @ git+https://github.com/poldracklab/niworkflows.git@9226e01cf5a7f4c8691870a3f98249c481986e7c
2424
smriprep ~= 0.3.2
2525
templateflow ~= 0.4.1
2626
nibabel >=2.2.1

0 commit comments

Comments
 (0)