From a3c44a410efc44f45dd5ebdd69f7cdc057ebb039 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 5 Sep 2024 17:19:27 -0400 Subject: [PATCH 01/31] Work on memory issues. --- src/fmripost_rapidtide/interfaces/rapidtide.py | 2 +- src/fmripost_rapidtide/workflows/rapidtide.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index 3cd0ffc..37c838d 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -269,7 +269,7 @@ class _RapidtideOutputSpec(TraitedSpec): class Rapidtide(CommandLine): """Run the rapidtide command-line interface.""" - _cmd = 'rapidtide --noprogressbar' + _cmd = 'rapidtide --noprogressbar --spcalculation --memprofile' input_spec = _RapidtideInputSpec output_spec = _RapidtideOutputSpec diff --git a/src/fmripost_rapidtide/workflows/rapidtide.py b/src/fmripost_rapidtide/workflows/rapidtide.py index 79f6342..14efea8 100644 --- a/src/fmripost_rapidtide/workflows/rapidtide.py +++ b/src/fmripost_rapidtide/workflows/rapidtide.py @@ -95,8 +95,7 @@ def init_rapidtide_wf( workflow = Workflow(name=_get_wf_name(bold_file, 'rapidtide')) workflow.__postdesc__ = """\ -Automatic removal of motion artifacts using independent component analysis -[Rapidtide, @rapidtide] was performed on the *preprocessed BOLD on MNI152NLin6Asym space*. +Identification and removal of traveling wave artifacts was performed using rapidtide. """ inputnode = pe.Node( From e00778cbe32bf09dc11ee07a6e410132e7264c20 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Fri, 6 Sep 2024 09:24:48 -0400 Subject: [PATCH 02/31] Don't run GLM within rapidtide call. --- .../interfaces/rapidtide.py | 280 +++++++++++++++++- 1 file changed, 279 insertions(+), 1 deletion(-) diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index 37c838d..5b09683 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -269,7 +269,7 @@ class _RapidtideOutputSpec(TraitedSpec): class Rapidtide(CommandLine): """Run the rapidtide command-line interface.""" - _cmd = 'rapidtide --noprogressbar --spcalculation --memprofile' + _cmd = 'rapidtide --noprogressbar --spcalculation --noglm' input_spec = _RapidtideInputSpec output_spec = _RapidtideOutputSpec @@ -287,3 +287,281 @@ def _list_outputs(self): f'{outputname}_desc-lfofilterCleaned_bold.nii.gz', ) return outputs + + +class _RetroGLMInputSpec(CommandLineInputSpec): + in_file = File( + exists=True, + argstr='%s', + position=-2, + mandatory=True, + desc='File to denoise', + ) + outputname = traits.Str( + argstr='%s', + position=-1, + mandatory=True, + desc='Output name', + ) + # Set by the workflow + denoising = traits.Bool( + argstr='--denoising', + mandatory=False, + ) + brainmask = File( + exists=True, + argstr='--brainmask %s', + mandatory=False, + ) + graymattermask = File( + exists=True, + argstr='--graymattermask %s', + mandatory=False, + ) + whitemattermask = File( + exists=True, + argstr='--whitemattermask %s', + mandatory=False, + ) + datatstep = traits.Float( + argstr='--datatstep %f', + mandatory=False, + ) + padseconds = traits.Float( + argstr='--padseconds %f', + mandatory=False, + ) + globalmeaninclude = File( + exists=True, + argstr='--globalmeaninclude %s', + mandatory=False, + ) + motionfile = File( + exists=True, + argstr='--motionfile %s', + mandatory=False, + ) + corrmask = File( + exists=True, + argstr='--corrmask %s', + mandatory=False, + ) + refineinclude = File( + exists=True, + argstr='--refineinclude %s', + mandatory=False, + ) + offsetinclude = File( + exists=True, + argstr='--offsetinclude %s', + mandatory=False, + ) + # Set by the user + autosync = traits.Bool( + argstr='--autosync', + mandatory=False, + ) + filterband = traits.Enum( + 'vlf', + 'lfo', + 'resp', + 'cardiac', + 'hrv_ulf', + 'hrv_vlf', + 'hrv_lf', + 'hrv_hf', + 'hrv_vhf', + 'lfo_legacy', + argstr='--filterband %s', + mandatory=False, + ) + filterfreqs = traits.List( + traits.Float, + argstr='--filterfreqs %s', + mandatory=False, + minlen=2, + maxlen=2, + ) + filterstopfreqs = traits.List( + traits.Float, + argstr='--filterstopfreqs %s', + mandatory=False, + minlen=2, + maxlen=2, + ) + numnull = traits.Int( + argstr='--numnull %d', + mandatory=False, + ) + detrendorder = traits.Int( + argstr='--detrendorder %d', + mandatory=False, + ) + spatialfilt = traits.Float( + argstr='--spatialfilt %f', + mandatory=False, + ) + confoundfile = File( + exists=True, + argstr='--confoundfile %s', + mandatory=False, + ) + confoundpowers = traits.Int( + argstr='--confoundpowers %d', + mandatory=False, + ) + confoundderiv = traits.Bool( + argstr='--confoundderiv', + mandatory=False, + ) + globalsignalmethod = traits.Enum( + 'sum', + 'meanscale', + 'pca', + 'random', + argstr='--globalsignalmethod %s', + mandatory=False, + ) + globalpcacomponents = traits.Float( + argstr='--globalpcacomponents %f', + mandatory=False, + ) + numskip = traits.Int( + argstr='--numskip %d', + mandatory=False, + ) + numtozero = traits.Int( + argstr='--numtozero %d', + mandatory=False, + ) + timerange = traits.List( + traits.Int, + argstr='--timerange %s', + mandatory=False, + minlen=2, + maxlen=2, + ) + corrweighting = traits.Enum( + 'phat', + 'liang', + 'eckart', + 'regressor', + argstr='--corrweighting %s', + mandatory=False, + ) + simcalcrange = traits.List( + traits.Int, + argstr='--simcalcrange %s', + mandatory=False, + minlen=2, + maxlen=2, + ) + fixdelay = traits.Float( + argstr='--fixdelay %f', + mandatory=False, + ) + searchrange = traits.List( + traits.Int, + argstr='--searchrange %s', + mandatory=False, + minlen=2, + maxlen=2, + ) + sigmalimit = traits.Float( + argstr='--sigmalimit %f', + mandatory=False, + ) + bipolar = traits.Bool( + argstr='--bipolar', + mandatory=False, + ) + lagminthresh = traits.Float( + argstr='--lagminthresh %f', + mandatory=False, + ) + lagmaxthresh = traits.Float( + argstr='--lagmaxthresh %f', + mandatory=False, + ) + ampthresh = traits.Float( + argstr='--ampthresh %f', + mandatory=False, + ) + sigmathresh = traits.Float( + argstr='--sigmathresh %f', + mandatory=False, + ) + pcacomponents = traits.Float( + argstr='--pcacomponents %f', + mandatory=False, + ) + convergencethresh = traits.Float( + argstr='--convergencethresh %f', + mandatory=False, + ) + maxpasses = traits.Int( + argstr='--maxpasses %d', + mandatory=False, + ) + glmsourcefile = File( + exists=True, + argstr='--glmsourcefile %s', + mandatory=False, + ) + glmderivs = traits.Int( + argstr='--glmderivs %d', + mandatory=False, + ) + outputlevel = traits.Enum( + 'min', + 'less', + 'normal', + 'more', + 'max', + argstr='--outputlevel %s', + mandatory=False, + ) + territorymap = File( + exists=True, + argstr='--territorymap %s', + mandatory=False, + ) + autorespdelete = traits.Bool( + argstr='--autorespdelete', + mandatory=False, + ) + nprocs = traits.Int( + default=1, + usedefault=True, + argstr='--nprocs %d', + mandatory=False, + ) + + +class _RetroGLMOutputSpec(TraitedSpec): + delay_map = File(exists=True, desc='3D map of optimal delay times') + regressor_file = File(exists=True, desc='Time series of refined regressor') + denoised = File(exists=True, desc='Denoised time series') + + +class RetroGLM(CommandLine): + """Run the rapidtide command-line interface.""" + + _cmd = 'retroglm --noprogressbar --spcalculation --memprofile' + input_spec = _RetroGLMInputSpec + output_spec = _RetroGLMOutputSpec + + def _list_outputs(self): + outputs = self._outputs().get() + out_dir = os.getcwd() + outputname = self.inputs.outputname + outputs['delay_map'] = os.path.join(out_dir, f'{outputname}_desc-maxtime_map.nii.gz') + outputs['regressor_file'] = os.path.join( + out_dir, + f'{outputname}_desc-refinedmovingregressor_timeseries.tsv.gz', + ) + outputs['denoised'] = os.path.join( + out_dir, + f'{outputname}_desc-lfofilterCleaned_bold.nii.gz', + ) + return outputs From 52b702daeb05c80b2a7676f8ad85feabddf67ff9 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Fri, 6 Sep 2024 09:53:52 -0400 Subject: [PATCH 03/31] Update rapidtide.py --- src/fmripost_rapidtide/interfaces/rapidtide.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index 5b09683..d7dc59f 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -303,6 +303,13 @@ class _RetroGLMInputSpec(CommandLineInputSpec): mandatory=True, desc='Output name', ) + rapidtide_dir = traits.Directory( + exists=True, + argstr='%s', + position=-1, + mandatory=True, + desc='Directory containing the output of rapidtide', + ) # Set by the workflow denoising = traits.Bool( argstr='--denoising', From 1bde24801df8bfc7aab7707a60d564bf10953fb7 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Fri, 6 Sep 2024 13:28:35 -0400 Subject: [PATCH 04/31] Try tracking outputs. --- .../data/rapidtide_spec.yml | 118 ++++++++++++++++++ .../interfaces/rapidtide.py | 25 ++-- 2 files changed, 131 insertions(+), 12 deletions(-) create mode 100644 src/fmripost_rapidtide/data/rapidtide_spec.yml diff --git a/src/fmripost_rapidtide/data/rapidtide_spec.yml b/src/fmripost_rapidtide/data/rapidtide_spec.yml new file mode 100644 index 0000000..eec4487 --- /dev/null +++ b/src/fmripost_rapidtide/data/rapidtide_spec.yml @@ -0,0 +1,118 @@ +autocorr: + filename: _desc-autocorr_timeseries.nii.gz +autocorr_json: + filename: _desc-autocorr_timeseries.json +cleansimdistdata: + filename: _desc-cleansimdistdata_info.tsv.gz +cleansimdistdata_json: + filename: _desc-cleansimdistdata_info.json +confoundfiltercleaned: + filename: _desc-confoundfilterCleaned_bold.nii.gz +confoundfiltercleaned_json: + filename: _desc-confoundfilterCleaned_bold.json +confoundfilterr2hist: + filename: _desc-confoundfilterR2_hist.tsv.gz +confoundfilterr2hist_json: + filename: _desc-confoundfilterR2_hist.json +confoundfilterr2map: + filename: _desc-confoundfilterR2_map.nii.gz +confoundfilterr2map_json: + filename: _desc-confoundfilterR2_map.json +corrfitmask: + filename: _desc-corrfitmask_bold.nii.gz +corrfitmask_json: + filename: _desc-corrfitmask_bold.json +expandedconfounds: + filename: _desc-expandedconfounds_timeseries.tsv.gz +expandedconfounds_json: + filename: _desc-expandedconfounds_timeseries.json +formattedruntimings: + filename: _desc-formattedruntimings_info.tsv +globallaghist: + filename: _desc-globallag_hist.tsv.gz +globallaghist_json: + filename: _desc-globallag_hist.json +globalmeanmask: + filename: _desc-globalmean_mask.nii.gz +globalmeanmask_json: + filename: _desc-globalmean_mask.json +initialmovingregressor: + filename: _desc-initialmovingregressor_timeseries.tsv.gz +initialmovingregressor_json: + filename: _desc-initialmovingregressor_timeseries.json +lagtcgenerator: + filename: _desc-lagtcgenerator_timeseries.tsv.gz +lagtcgenerator_json: + filename: _desc-lagtcgenerator_timeseries.json +maxcorrhist: + filename: _desc-maxcorr_hist.tsv.gz +maxcorrhist_json: + filename: _desc-maxcorr_hist.json +maxcorrmap: + filename: _desc-maxcorr_map.nii.gz +maxcorrmap_json: + filename: _desc-maxcorr_map.json +maxcorrsqmap: + filename: _desc-maxcorrsq_map.nii.gz +maxcorrsqmap_json: + filename: _desc-maxcorrsq_map.json +maxtimehist: + filename: _desc-maxtime_hist.tsv.gz +maxtimehist_json: + filename: _desc-maxtime_hist.json +maxtimemap: + filename: _desc-maxtime_map.nii.gz +maxtimemap_json: + filename: _desc-maxtime_map.json +maxwidthhist: + filename: _desc-maxwidth_hist.tsv.gz +maxwidthhist_json: + filename: _desc-maxwidth_hist.json +maxwidthmap: + filename: _desc-maxwidth_map.nii.gz +maxwidthmap_json: + filename: _desc-maxwidth_map.json +meanmap: + filename: _desc-mean_map.nii.gz +meanmap_json: + filename: _desc-mean_map.json +movingregressor: + filename: _desc-movingregressor_timeseries.tsv.gz +movingregressor_json: + filename: _desc-movingregressor_timeseries.json +mtthist: + filename: _desc-MTT_hist.tsv.gz +mtthist_json: + filename: _desc-MTT_hist.json +mttmap: + filename: _desc-MTT_map.nii.gz +mttmap_json: + filename: _desc-MTT_map.json +nullsimfunchist: + filename: _desc-nullsimfunc_hist.tsv.gz +nullsimfunchist_json: + filename: _desc-nullsimfunc_hist.json +oversampledmovingregressor: + filename: _desc-oversampledmovingregressor_timeseries.tsv.gz +oversampledmovingregressor_json: + filename: _desc-oversampledmovingregressor_timeseries.json +preprocessedconfounds: + filename: _desc-preprocessedconfounds_timeseries.tsv.gz +preprocessedconfounds_json: + filename: _desc-preprocessedconfounds_timeseries.json +processedmask: + filename: _desc-processed_mask.nii.gz +processedmask_json: + filename: _desc-processed_mask.json +refinemask: + filename: _desc-refine_mask.nii.gz +refinemask_json: + filename: _desc-refine_mask.json +refinedmovingregressor: + filename: _desc-refinedmovingregressor_timeseries.tsv.gz +refinedmovingregressor_json: + filename: _desc-refinedmovingregressor_timeseries.json +timepercentilemap: + filename: _desc-timepercentile_map.nii.gz +timepercentilemap_json: + filename: _desc-timepercentile_map.json diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index d7dc59f..35c8408 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -2,6 +2,7 @@ import os +import yaml from nipype.interfaces.base import ( CommandLine, CommandLineInputSpec, @@ -10,6 +11,11 @@ traits, ) +from fmripost_rapidtide.data import load as load_data + +with open(load_data('rapidtide_spec.yml')) as f: + rapidtide_output_spec = yaml.safe_load(f) + class _RapidtideInputSpec(CommandLineInputSpec): in_file = File( @@ -261,9 +267,10 @@ class _RapidtideInputSpec(CommandLineInputSpec): class _RapidtideOutputSpec(TraitedSpec): - delay_map = File(exists=True, desc='3D map of optimal delay times') - regressor_file = File(exists=True, desc='Time series of refined regressor') - denoised = File(exists=True, desc='Denoised time series') + pass + +for name in rapidtide_output_spec.keys(): + _RapidtideOutputSpec.add_class_trait(name, File) class Rapidtide(CommandLine): @@ -277,15 +284,9 @@ def _list_outputs(self): outputs = self._outputs().get() out_dir = os.getcwd() outputname = self.inputs.outputname - outputs['delay_map'] = os.path.join(out_dir, f'{outputname}_desc-maxtime_map.nii.gz') - outputs['regressor_file'] = os.path.join( - out_dir, - f'{outputname}_desc-refinedmovingregressor_timeseries.tsv.gz', - ) - outputs['denoised'] = os.path.join( - out_dir, - f'{outputname}_desc-lfofilterCleaned_bold.nii.gz', - ) + for name, spec in rapidtide_output_spec.items(): + outputs[name] = os.path.join(out_dir, f'{outputname}_{spec["filename"]}') + return outputs From b97bff1ef8e9744f21b2ddf5ef2676d3851c06b7 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Fri, 6 Sep 2024 16:16:20 -0400 Subject: [PATCH 05/31] Run rapidtide on boldref-space data. --- src/fmripost_rapidtide/data/io_spec.json | 34 ++---- src/fmripost_rapidtide/utils/bids.py | 5 +- src/fmripost_rapidtide/workflows/base.py | 115 +++++++----------- src/fmripost_rapidtide/workflows/rapidtide.py | 23 ++-- 4 files changed, 70 insertions(+), 107 deletions(-) diff --git a/src/fmripost_rapidtide/data/io_spec.json b/src/fmripost_rapidtide/data/io_spec.json index 46dfd29..7d1c62e 100644 --- a/src/fmripost_rapidtide/data/io_spec.json +++ b/src/fmripost_rapidtide/data/io_spec.json @@ -16,15 +16,15 @@ } }, "derivatives": { - "bold_mni152nlin6asym": { + "bold_boldref": { "datatype": "func", "echo": null, "part": [ "mag", null ], - "res": "2", - "space": "MNI152NLin6Asym", + "res": null, + "space": null, "desc": "preproc", "suffix": "bold", "extension": [ @@ -32,15 +32,15 @@ ".nii" ] }, - "bold_mask_mni152nlin6asym": { + "bold_mask_boldref": { "datatype": "func", "echo": null, "part": [ "mag", null ], - "res": "2", - "space": "MNI152NLin6Asym", + "res": null, + "space": null, "desc": "brain", "suffix": "mask", "extension": [ @@ -48,16 +48,17 @@ ".nii" ] }, - "bold_mask_native": { + "boldref": { "datatype": "func", "echo": null, "part": [ "mag", null ], + "res": null, "space": null, - "desc": "brain", - "suffix": "mask", + "desc": "coreg", + "suffix": "boldref", "extension": [ ".nii.gz", ".nii" @@ -79,19 +80,6 @@ ".tsv" ] }, - "dseg_mni152nlin6asym": { - "datatype": "anat", - "task": null, - "run": null, - "res": "2", - "space": "MNI152NLin6Asym", - "desc": null, - "suffix": "dseg", - "extension": [ - ".nii.gz", - ".nii" - ] - }, "anat_dseg": { "datatype": "anat", "task": null, @@ -113,6 +101,7 @@ "from": "orig", "to": "boldref", "mode": "image", + "desc": ["hmc", null], "suffix": "xfm", "extension": ".txt" }, @@ -121,6 +110,7 @@ "from": "boldref", "to": ["anat", "T1w", "T2w"], "mode": "image", + "desc": ["coreg", null], "suffix": "xfm", "extension": ".txt" }, diff --git a/src/fmripost_rapidtide/utils/bids.py b/src/fmripost_rapidtide/utils/bids.py index c2b0302..ef95aac 100644 --- a/src/fmripost_rapidtide/utils/bids.py +++ b/src/fmripost_rapidtide/utils/bids.py @@ -137,6 +137,7 @@ def collect_derivatives( query['to'] = fieldmap_id item = layout.get(return_type='filename', **query) + if not item: derivs_cache[k] = None elif not allow_multiple and len(item) > 1: @@ -151,13 +152,13 @@ def collect_derivatives( spaces_found, bold_outputspaces, bold_mask_outputspaces = [], [], [] for space in spaces.references: # First try to find processed BOLD+mask files in the requested space - bold_query = {**entities, **spec['derivatives']['bold_mni152nlin6asym']} + bold_query = {**entities, **spec['derivatives']['bold_boldref']} bold_query['space'] = space.space bold_query = {**bold_query, **space.spec} bold_item = layout.get(return_type='filename', **bold_query) bold_outputspaces.append(bold_item[0] if bold_item else None) - mask_query = {**entities, **spec['derivatives']['bold_mask_mni152nlin6asym']} + mask_query = {**entities, **spec['derivatives']['bold_boldref']} mask_query['space'] = space.space mask_query = {**mask_query, **space.spec} mask_item = layout.get(return_type='filename', **mask_query) diff --git a/src/fmripost_rapidtide/workflows/base.py b/src/fmripost_rapidtide/workflows/base.py index 14de5e1..65961fb 100644 --- a/src/fmripost_rapidtide/workflows/base.py +++ b/src/fmripost_rapidtide/workflows/base.py @@ -198,14 +198,14 @@ def init_single_subject_wf(subject_id: str): spaces=None, ) # Patch standard-space BOLD files into 'bold' key - subject_data['bold'] = listify(subject_data['bold_mni152nlin6asym']) + subject_data['bold'] = listify(subject_data['bold_boldref']) - if not subject_data['bold_mni152nlin6asym']: + if not subject_data['bold_boldref']: task_id = config.execution.task_id raise RuntimeError( - f"No MNI152NLin6Asym:res-2 BOLD images found for participant {subject_id} and " + f"No boldref:res-native BOLD images found for participant {subject_id} and " f"task {task_id if task_id else ''}. " - "All workflows require MNI152NLin6Asym:res-2 BOLD images. " + "All workflows require boldref:res-native BOLD images. " f"Please check your BIDS filters: {config.execution.bids_filters}." ) @@ -292,6 +292,7 @@ def init_single_run_wf(bold_file): from nipype.interfaces import utility as niu from niworkflows.engine.workflows import LiterateWorkflow as Workflow + from fmripost_rapidtide.interfaces.misc import ApplyTransforms from fmripost_rapidtide.utils.bids import collect_derivatives, extract_entities from fmripost_rapidtide.workflows.outputs import init_func_fit_reports_wf from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_wf @@ -343,8 +344,8 @@ def init_single_run_wf(bold_file): raise ValueError('Motion parameters cannot be extracted from transforms yet.') else: - # Collect MNI152NLin6Asym:res-2 derivatives - # Only derivatives dataset was passed in, so we expected standard-space derivatives + # Collect boldref:res-native derivatives + # Only derivatives dataset was passed in, so we expected boldref-space derivatives functional_cache.update( collect_derivatives( raw_dataset=None, @@ -379,19 +380,31 @@ def init_single_run_wf(bold_file): rapidtide_wf.inputs.inputnode.confounds = functional_cache['confounds'] rapidtide_wf.inputs.inputnode.skip_vols = skip_vols - mni6_buffer = pe.Node(niu.IdentityInterface(fields=['bold', 'bold_mask']), name='mni6_buffer') + boldref_buffer = pe.Node( + niu.IdentityInterface(fields=['bold', 'bold_mask']), + name='boldref_buffer', + ) + + # Warp the dseg from anatomical space to boldref space + dseg_to_boldref = pe.Node( + ApplyTransforms( + interpolation='GenericLabel', + input_image=functional_cache['anat_dseg'], + reference_image=functional_cache['boldref'], + transforms=[functional_cache['boldref2anat']], + invert_transform_flags=[True], + ), + name='dseg_to_boldref', + ) - if ('bold_mni152nlin6asym' not in functional_cache) and ('bold_raw' in functional_cache): + if ('bold_boldref' not in functional_cache) and ('bold_raw' in functional_cache): # Resample to MNI152NLin6Asym:res-2, for rapidtide denoising from fmriprep.workflows.bold.apply import init_bold_volumetric_resample_wf from fmriprep.workflows.bold.stc import init_bold_stc_wf from niworkflows.interfaces.header import ValidateImage - from templateflow.api import get as get_template - - from fmripost_rapidtide.interfaces.misc import ApplyTransforms workflow.__desc__ += """\ -Raw BOLD series were resampled to MNI152NLin6Asym:res-2, for rapidtide denoising. +Raw BOLD series were resampled to boldref:res-native, for rapidtide denoising. """ validate_bold = pe.Node( @@ -418,17 +431,7 @@ def init_single_run_wf(bold_file): else: workflow.connect([(validate_bold, stc_buffer, [('out_file', 'bold_file')])]) - mni6_mask = str( - get_template( - 'MNI152NLin6Asym', - resolution=2, - desc='brain', - suffix='mask', - extension=['.nii', '.nii.gz'], - ) - ) - - bold_MNI6_wf = init_bold_volumetric_resample_wf( + bold_boldref_wf = init_bold_volumetric_resample_wf( metadata=bold_metadata, fieldmap_id=None, # XXX: Ignoring the field map for now omp_nthreads=omp_nthreads, @@ -436,71 +439,41 @@ def init_single_run_wf(bold_file): jacobian='fmap-jacobian' not in config.workflow.ignore, name='bold_MNI6_wf', ) - bold_MNI6_wf.inputs.inputnode.motion_xfm = functional_cache['hmc'] - bold_MNI6_wf.inputs.inputnode.boldref2fmap_xfm = functional_cache['boldref2fmap'] - bold_MNI6_wf.inputs.inputnode.boldref2anat_xfm = functional_cache['boldref2anat'] - bold_MNI6_wf.inputs.inputnode.anat2std_xfm = functional_cache['anat2mni152nlin6asym'] - bold_MNI6_wf.inputs.inputnode.resolution = '02' + bold_boldref_wf.inputs.inputnode.motion_xfm = functional_cache['hmc'] + bold_boldref_wf.inputs.inputnode.boldref2fmap_xfm = functional_cache['boldref2fmap'] + bold_boldref_wf.inputs.inputnode.resolution = 'native' # use mask as boldref? - bold_MNI6_wf.inputs.inputnode.bold_ref_file = functional_cache['bold_mask_native'] - bold_MNI6_wf.inputs.inputnode.target_mask = mni6_mask - bold_MNI6_wf.inputs.inputnode.target_ref_file = mni6_mask + bold_boldref_wf.inputs.inputnode.bold_ref_file = functional_cache['boldref'] + bold_boldref_wf.inputs.inputnode.target_mask = functional_cache['bold_mask_boldref'] + bold_boldref_wf.inputs.inputnode.target_ref_file = functional_cache['boldref'] workflow.connect([ # Resample BOLD to MNI152NLin6Asym, may duplicate bold_std_wf above # XXX: Ignoring the field map for now - # (inputnode, bold_MNI6_wf, [ + # (inputnode, bold_boldref_wf, [ # ('fmap_ref', 'inputnode.fmap_ref'), # ('fmap_coeff', 'inputnode.fmap_coeff'), # ('fmap_id', 'inputnode.fmap_id'), # ]), - (stc_buffer, bold_MNI6_wf, [('bold_file', 'inputnode.bold_file')]), - (bold_MNI6_wf, mni6_buffer, [('outputnode.bold_file', 'bold')]), + (stc_buffer, bold_boldref_wf, [('bold_file', 'inputnode.bold_file')]), + (bold_boldref_wf, boldref_buffer, [('outputnode.bold_file', 'bold')]), ]) # fmt:skip - # Warp the mask as well - mask_to_mni6 = pe.Node( - ApplyTransforms( - interpolation='GenericLabel', - input_image=functional_cache['bold_mask_native'], - reference_image=mni6_mask, - transforms=[ - functional_cache['anat2mni152nlin6asym'], - functional_cache['boldref2anat'], - ], - ), - name='mask_to_mni6', - ) - workflow.connect([(mask_to_mni6, mni6_buffer, [('output_image', 'bold_mask')])]) - - # And the dseg - dseg_to_mni6 = pe.Node( - ApplyTransforms( - interpolation='GenericLabel', - input_image=functional_cache['anat_dseg'], - reference_image=mni6_mask, - transforms=[functional_cache['anat2mni152nlin6asym']], - ), - name='mask_to_mni6', - ) - workflow.connect([(dseg_to_mni6, mni6_buffer, [('output_image', 'dseg')])]) - - elif 'bold_mni152nlin6asym' in functional_cache: + elif 'bold_boldref' in functional_cache: workflow.__desc__ += """\ -Preprocessed BOLD series in MNI152NLin6Asym:res-2 space were collected for rapidtide denoising. +Preprocessed BOLD series in boldref:res-native space were collected for rapidtide denoising. """ - mni6_buffer.inputs.bold = functional_cache['bold_mni152nlin6asym'] - mni6_buffer.inputs.bold_mask = functional_cache['bold_mask_mni152nlin6asym'] - mni6_buffer.inputs.dseg = functional_cache['dseg_mni152nlin6asym'] + boldref_buffer.inputs.bold = functional_cache['bold_boldref'] + boldref_buffer.inputs.bold_mask = functional_cache['bold_mask_boldref'] else: raise ValueError('No valid BOLD series found for rapidtide denoising.') workflow.connect([ - (mni6_buffer, rapidtide_wf, [ - ('bold', 'inputnode.bold_std'), - ('bold_mask', 'inputnode.bold_mask_std'), - ('dseg', 'inputnode.dseg_std'), + (dseg_to_boldref, rapidtide_wf, [('output_image', 'inputnode.dseg')]), + (boldref_buffer, rapidtide_wf, [ + ('bold', 'inputnode.bold'), + ('bold_mask', 'inputnode.bold_mask'), ]), ]) # fmt:skip @@ -509,7 +482,7 @@ def init_single_run_wf(bold_file): func_fit_reports_wf.inputs.inputnode.source_file = bold_file func_fit_reports_wf.inputs.inputnode.anat2std_xfm = functional_cache['anat2mni152nlin6asym'] func_fit_reports_wf.inputs.inputnode.anat_dseg = functional_cache['anat_dseg'] - workflow.connect([(mni6_buffer, func_fit_reports_wf, [('bold', 'inputnode.bold_mni6')])]) + workflow.connect([(boldref_buffer, func_fit_reports_wf, [('bold', 'inputnode.bold_mni6')])]) return workflow diff --git a/src/fmripost_rapidtide/workflows/rapidtide.py b/src/fmripost_rapidtide/workflows/rapidtide.py index 14efea8..4596f50 100644 --- a/src/fmripost_rapidtide/workflows/rapidtide.py +++ b/src/fmripost_rapidtide/workflows/rapidtide.py @@ -70,11 +70,11 @@ def init_rapidtide_wf( Inputs ------ - bold_std + bold BOLD series in template space - bold_mask_std + bold_mask BOLD series mask in template space - dseg_std + dseg Tissue segmentation in template space confounds fMRIPrep-formatted confounds file, which must include the following columns: @@ -101,9 +101,9 @@ def init_rapidtide_wf( inputnode = pe.Node( niu.IdentityInterface( fields=[ - 'bold_std', - 'bold_mask_std', - 'dseg_std', + 'bold', + 'bold_mask', + 'dseg', 'confounds', 'skip_vols', ], @@ -127,7 +127,7 @@ def init_rapidtide_wf( SplitDseg(), name='split_tissues', ) - workflow.connect([(inputnode, split_tissues, [('dseg_std', 'dseg')])]) + workflow.connect([(inputnode, split_tissues, [('dseg', 'dseg')])]) # Run the Rapidtide classifier # XXX: simcalcrange is converted to list of strings @@ -175,8 +175,8 @@ def init_rapidtide_wf( ) workflow.connect([ (inputnode, rapidtide, [ - ('bold_std', 'in_file'), - ('bold_mask_std', 'brainmask'), + ('bold', 'in_file'), + ('bold_mask', 'brainmask'), ('confounds', 'motionfile'), ('skip_vols', 'numskip'), ]), @@ -188,9 +188,8 @@ def init_rapidtide_wf( ('gm', 'offsetinclude'), # GM mask for offset calculation ]), (rapidtide, outputnode, [ - ('delay_map', 'delay_map'), - ('regressor_file', 'regressor_file'), - ('denoised', 'denoised'), + ('maxtimemap', 'delay_map'), + ('lagtcgenerator', 'regressor_file'), ]) ]) # fmt:skip From 9003dbcafe542eb7e03f21dd3b3e2e5083bbf7f5 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Fri, 6 Sep 2024 16:21:58 -0400 Subject: [PATCH 06/31] Write out delay map and regressor. --- src/fmripost_rapidtide/workflows/rapidtide.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/fmripost_rapidtide/workflows/rapidtide.py b/src/fmripost_rapidtide/workflows/rapidtide.py index 4596f50..b0c5518 100644 --- a/src/fmripost_rapidtide/workflows/rapidtide.py +++ b/src/fmripost_rapidtide/workflows/rapidtide.py @@ -91,6 +91,7 @@ def init_rapidtide_wf( from nipype.interfaces.base import Undefined from niworkflows.engine.workflows import LiterateWorkflow as Workflow + from fmripost_rapidtide.interfaces.bids import DerivativesDataSink from fmripost_rapidtide.interfaces.nilearn import SplitDseg workflow = Workflow(name=_get_wf_name(bold_file, 'rapidtide')) @@ -116,7 +117,6 @@ def init_rapidtide_wf( fields=[ 'delay_map', 'regressor_file', - 'denoised', ], ), name='outputnode', @@ -193,6 +193,29 @@ def init_rapidtide_wf( ]) ]) # fmt:skip - # Generate figures for report + ds_delay_map = pe.Node( + DerivativesDataSink( + source_file=bold_file, + compress=True, + desc='delay', + suffix='boldmap', + Units='s', + ), + name='ds_delay_map', + run_without_submitting=True, + ) + workflow.connect([(rapidtide, ds_delay_map, [('maxtimemap', 'in_file')])]) + + ds_regressor = pe.Node( + DerivativesDataSink( + source_file=bold_file, + desc='regressor', + suffix='timeseries', + extension='.tsv', + ), + name='ds_regressor', + run_without_submitting=True, + ) + workflow.connect([(rapidtide, ds_regressor, [('lagtcgenerator', 'in_file')])]) return workflow From 7e62346cbbfa9417d6adc3138575c0dc4c0e833e Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Fri, 6 Sep 2024 16:23:46 -0400 Subject: [PATCH 07/31] Fix filenames. --- .../data/rapidtide_spec.yml | 118 +++++++++--------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/src/fmripost_rapidtide/data/rapidtide_spec.yml b/src/fmripost_rapidtide/data/rapidtide_spec.yml index eec4487..4bb987b 100644 --- a/src/fmripost_rapidtide/data/rapidtide_spec.yml +++ b/src/fmripost_rapidtide/data/rapidtide_spec.yml @@ -1,118 +1,118 @@ autocorr: - filename: _desc-autocorr_timeseries.nii.gz + filename: desc-autocorr_timeseries.nii.gz autocorr_json: - filename: _desc-autocorr_timeseries.json + filename: desc-autocorr_timeseries.json cleansimdistdata: - filename: _desc-cleansimdistdata_info.tsv.gz + filename: desc-cleansimdistdata_info.tsv.gz cleansimdistdata_json: - filename: _desc-cleansimdistdata_info.json + filename: desc-cleansimdistdata_info.json confoundfiltercleaned: - filename: _desc-confoundfilterCleaned_bold.nii.gz + filename: desc-confoundfilterCleaned_bold.nii.gz confoundfiltercleaned_json: - filename: _desc-confoundfilterCleaned_bold.json + filename: desc-confoundfilterCleaned_bold.json confoundfilterr2hist: - filename: _desc-confoundfilterR2_hist.tsv.gz + filename: desc-confoundfilterR2_hist.tsv.gz confoundfilterr2hist_json: - filename: _desc-confoundfilterR2_hist.json + filename: desc-confoundfilterR2_hist.json confoundfilterr2map: - filename: _desc-confoundfilterR2_map.nii.gz + filename: desc-confoundfilterR2_map.nii.gz confoundfilterr2map_json: - filename: _desc-confoundfilterR2_map.json + filename: desc-confoundfilterR2_map.json corrfitmask: - filename: _desc-corrfitmask_bold.nii.gz + filename: desc-corrfitmask_bold.nii.gz corrfitmask_json: - filename: _desc-corrfitmask_bold.json + filename: desc-corrfitmask_bold.json expandedconfounds: - filename: _desc-expandedconfounds_timeseries.tsv.gz + filename: desc-expandedconfounds_timeseries.tsv.gz expandedconfounds_json: - filename: _desc-expandedconfounds_timeseries.json + filename: desc-expandedconfounds_timeseries.json formattedruntimings: - filename: _desc-formattedruntimings_info.tsv + filename: desc-formattedruntimings_info.tsv globallaghist: - filename: _desc-globallag_hist.tsv.gz + filename: desc-globallag_hist.tsv.gz globallaghist_json: - filename: _desc-globallag_hist.json + filename: desc-globallag_hist.json globalmeanmask: - filename: _desc-globalmean_mask.nii.gz + filename: desc-globalmean_mask.nii.gz globalmeanmask_json: - filename: _desc-globalmean_mask.json + filename: desc-globalmean_mask.json initialmovingregressor: - filename: _desc-initialmovingregressor_timeseries.tsv.gz + filename: desc-initialmovingregressor_timeseries.tsv.gz initialmovingregressor_json: - filename: _desc-initialmovingregressor_timeseries.json + filename: desc-initialmovingregressor_timeseries.json lagtcgenerator: - filename: _desc-lagtcgenerator_timeseries.tsv.gz + filename: desc-lagtcgenerator_timeseries.tsv.gz lagtcgenerator_json: - filename: _desc-lagtcgenerator_timeseries.json + filename: desc-lagtcgenerator_timeseries.json maxcorrhist: - filename: _desc-maxcorr_hist.tsv.gz + filename: desc-maxcorr_hist.tsv.gz maxcorrhist_json: - filename: _desc-maxcorr_hist.json + filename: desc-maxcorr_hist.json maxcorrmap: - filename: _desc-maxcorr_map.nii.gz + filename: desc-maxcorr_map.nii.gz maxcorrmap_json: - filename: _desc-maxcorr_map.json + filename: desc-maxcorr_map.json maxcorrsqmap: - filename: _desc-maxcorrsq_map.nii.gz + filename: desc-maxcorrsq_map.nii.gz maxcorrsqmap_json: - filename: _desc-maxcorrsq_map.json + filename: desc-maxcorrsq_map.json maxtimehist: - filename: _desc-maxtime_hist.tsv.gz + filename: desc-maxtime_hist.tsv.gz maxtimehist_json: - filename: _desc-maxtime_hist.json + filename: desc-maxtime_hist.json maxtimemap: - filename: _desc-maxtime_map.nii.gz + filename: desc-maxtime_map.nii.gz maxtimemap_json: - filename: _desc-maxtime_map.json + filename: desc-maxtime_map.json maxwidthhist: - filename: _desc-maxwidth_hist.tsv.gz + filename: desc-maxwidth_hist.tsv.gz maxwidthhist_json: - filename: _desc-maxwidth_hist.json + filename: desc-maxwidth_hist.json maxwidthmap: - filename: _desc-maxwidth_map.nii.gz + filename: desc-maxwidth_map.nii.gz maxwidthmap_json: - filename: _desc-maxwidth_map.json + filename: desc-maxwidth_map.json meanmap: - filename: _desc-mean_map.nii.gz + filename: desc-mean_map.nii.gz meanmap_json: - filename: _desc-mean_map.json + filename: desc-mean_map.json movingregressor: - filename: _desc-movingregressor_timeseries.tsv.gz + filename: desc-movingregressor_timeseries.tsv.gz movingregressor_json: - filename: _desc-movingregressor_timeseries.json + filename: desc-movingregressor_timeseries.json mtthist: - filename: _desc-MTT_hist.tsv.gz + filename: desc-MTT_hist.tsv.gz mtthist_json: - filename: _desc-MTT_hist.json + filename: desc-MTT_hist.json mttmap: - filename: _desc-MTT_map.nii.gz + filename: desc-MTT_map.nii.gz mttmap_json: - filename: _desc-MTT_map.json + filename: desc-MTT_map.json nullsimfunchist: - filename: _desc-nullsimfunc_hist.tsv.gz + filename: desc-nullsimfunc_hist.tsv.gz nullsimfunchist_json: - filename: _desc-nullsimfunc_hist.json + filename: desc-nullsimfunc_hist.json oversampledmovingregressor: - filename: _desc-oversampledmovingregressor_timeseries.tsv.gz + filename: desc-oversampledmovingregressor_timeseries.tsv.gz oversampledmovingregressor_json: - filename: _desc-oversampledmovingregressor_timeseries.json + filename: desc-oversampledmovingregressor_timeseries.json preprocessedconfounds: - filename: _desc-preprocessedconfounds_timeseries.tsv.gz + filename: desc-preprocessedconfounds_timeseries.tsv.gz preprocessedconfounds_json: - filename: _desc-preprocessedconfounds_timeseries.json + filename: desc-preprocessedconfounds_timeseries.json processedmask: - filename: _desc-processed_mask.nii.gz + filename: desc-processed_mask.nii.gz processedmask_json: - filename: _desc-processed_mask.json + filename: desc-processed_mask.json refinemask: - filename: _desc-refine_mask.nii.gz + filename: desc-refine_mask.nii.gz refinemask_json: - filename: _desc-refine_mask.json + filename: desc-refine_mask.json refinedmovingregressor: - filename: _desc-refinedmovingregressor_timeseries.tsv.gz + filename: desc-refinedmovingregressor_timeseries.tsv.gz refinedmovingregressor_json: - filename: _desc-refinedmovingregressor_timeseries.json + filename: desc-refinedmovingregressor_timeseries.json timepercentilemap: - filename: _desc-timepercentile_map.nii.gz + filename: desc-timepercentile_map.nii.gz timepercentilemap_json: - filename: _desc-timepercentile_map.json + filename: desc-timepercentile_map.json From 358bd2fe0cebec08a91942bcd15931f8dbe1e57e Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Fri, 6 Sep 2024 17:42:06 -0400 Subject: [PATCH 08/31] Fix stuff. --- src/fmripost_rapidtide/data/io_spec.json | 3 ++- src/fmripost_rapidtide/workflows/base.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/fmripost_rapidtide/data/io_spec.json b/src/fmripost_rapidtide/data/io_spec.json index 7d1c62e..b272f97 100644 --- a/src/fmripost_rapidtide/data/io_spec.json +++ b/src/fmripost_rapidtide/data/io_spec.json @@ -159,9 +159,10 @@ "name": "threshold", "pattern": "(?:^|_)thresh-([a-zA-Z0-9]+)" } - ], + ], "patterns": [ "sub-{subject}[/ses-{session}]/{datatype|func}/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ceagent}][_rec-{reconstruction}][_echo-{echo}][_part-{part}][_space-{space}][_res-{res}][_desc-{desc}]_{suffix}.{extension|nii.gz}", + "sub-{subject}[/ses-{session}]/{datatype|func}/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ceagent}][_dir-{direction}][_rec-{reconstruction}][_run-{run}][_echo-{echo}][_space-{space}][_cohort-{cohort}][_seg-{segmentation}][_res-{res}][_stat-{statistic}][_desc-{desc}]_{suffix}{extension<.nii|.nii.gz|.json>|.nii.gz}", "sub-{subject}[/ses-{session}]/{datatype|func}/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ceagent}][_rec-{reconstruction}][_echo-{echo}][_part-{part}][_space-{space}][_res-{res}][_stat-{statistic}][_desc-{desc}]_{suffix}.{extension|nii.gz}", "sub-{subject}[/ses-{session}]/{datatype|func}/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ceagent}][_rec-{reconstruction}][_echo-{echo}][_part-{part}][_space-{space}][_res-{res}][_stat-{statistic}][_desc-{desc}]_{suffix}.{extension|tsv}", "sub-{subject}[/ses-{session}]/{datatype|func}/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{ceagent}][_rec-{reconstruction}][_part-{part}][_desc-{desc}]_{suffix}.{extension|tsv}", diff --git a/src/fmripost_rapidtide/workflows/base.py b/src/fmripost_rapidtide/workflows/base.py index 65961fb..48e7ad7 100644 --- a/src/fmripost_rapidtide/workflows/base.py +++ b/src/fmripost_rapidtide/workflows/base.py @@ -484,18 +484,20 @@ def init_single_run_wf(bold_file): func_fit_reports_wf.inputs.inputnode.anat_dseg = functional_cache['anat_dseg'] workflow.connect([(boldref_buffer, func_fit_reports_wf, [('bold', 'inputnode.bold_mni6')])]) - return workflow + return clean_datasinks(workflow, bold_file=bold_file) def _prefix(subid): return subid if subid.startswith('sub-') else f'sub-{subid}' -def clean_datasinks(workflow: pe.Workflow) -> pe.Workflow: +def clean_datasinks(workflow: pe.Workflow, bold_file: str) -> pe.Workflow: """Overwrite ``out_path_base`` of smriprep's DataSinks.""" for node in workflow.list_node_names(): if node.split('.')[-1].startswith('ds_'): workflow.get_node(node).interface.out_path_base = '' + workflow.get_node(node).inputs.base_directory = str(config.execution.output_dir) + workflow.get_node(node).inputs.source_file = bold_file return workflow From 9b55cf36f17c41692c53db5dd4752948a0661d02 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sat, 7 Sep 2024 08:34:44 -0400 Subject: [PATCH 09/31] Work on denoising step. --- .../interfaces/rapidtide.py | 3 +- src/fmripost_rapidtide/workflows/base.py | 62 +++++++++++-- src/fmripost_rapidtide/workflows/rapidtide.py | 88 +++++++++++++++++-- 3 files changed, 135 insertions(+), 18 deletions(-) diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index 35c8408..f35b0e0 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -547,9 +547,8 @@ class _RetroGLMInputSpec(CommandLineInputSpec): class _RetroGLMOutputSpec(TraitedSpec): - delay_map = File(exists=True, desc='3D map of optimal delay times') - regressor_file = File(exists=True, desc='Time series of refined regressor') denoised = File(exists=True, desc='Denoised time series') + denoised_json = File(exists=True, desc='Denoised time series metadata') class RetroGLM(CommandLine): diff --git a/src/fmripost_rapidtide/workflows/base.py b/src/fmripost_rapidtide/workflows/base.py index 48e7ad7..b9acc7b 100644 --- a/src/fmripost_rapidtide/workflows/base.py +++ b/src/fmripost_rapidtide/workflows/base.py @@ -134,13 +134,16 @@ def init_single_subject_wf(subject_id: str): """ from bids.utils import listify + from nipype.interfaces import utility as niu from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.bids import BIDSInfo from niworkflows.interfaces.nilearn import NILEARN_VERSION from fmripost_rapidtide.interfaces.bids import DerivativesDataSink + from fmripost_rapidtide.interfaces.nilearn import MeanImage from fmripost_rapidtide.interfaces.reportlets import AboutSummary, SubjectSummary from fmripost_rapidtide.utils.bids import collect_derivatives + from fmripost_rapidtide.workflows.rapidtide import init_denoise_single_run_wf spaces = config.workflow.spaces @@ -279,14 +282,41 @@ def init_single_subject_wf(subject_id: str): """ workflow.__desc__ += func_pre_desc - for bold_file in subject_data['bold']: - single_run_wf = init_single_run_wf(bold_file) + denoise_within_run = (len(subject_data['bold']) > 1) and not config.workflow.average_over_runs + if not denoise_within_run: + # Average the lag map across runs before denoising + merge_lag_maps = pe.Node( + niu.Merge(len(subject_data['bold'])), + name='merge_lag_maps', + ) + average_lag_map = pe.Node( + MeanImage(), + name='average_lag_map', + ) + + for i_run, bold_file in enumerate(subject_data['bold']): + single_run_wf = init_single_run_wf(bold_file, denoise=denoise_within_run) workflow.add_nodes([single_run_wf]) - return clean_datasinks(workflow) + if not denoise_within_run: + # Denoise the BOLD data using the mean lag map + workflow.connect([ + (single_run_wf, merge_lag_maps, [('outputnode.delay_map', f'in{i_run + 1}')]), + (merge_lag_maps, average_lag_map, [('out', 'in_file')]), + ]) # fmt:skip + denoise_single_run_wf = init_denoise_single_run_wf(bold_file) + workflow.connect([ + (single_run_wf, denoise_single_run_wf, [ + ('outputnode.regressor', 'inputnode.regressor'), + ]), + (average_lag_map, denoise_single_run_wf, [('out_file', 'inputnode.delay_map')]), + ]) # fmt:skip -def init_single_run_wf(bold_file): + return workflow + + +def init_single_run_wf(bold_file, denoise=True): """Set up a single-run workflow for fMRIPost-rapidtide.""" from fmriprep.utils.misc import estimate_bold_mem_usage from nipype.interfaces import utility as niu @@ -295,7 +325,10 @@ def init_single_run_wf(bold_file): from fmripost_rapidtide.interfaces.misc import ApplyTransforms from fmripost_rapidtide.utils.bids import collect_derivatives, extract_entities from fmripost_rapidtide.workflows.outputs import init_func_fit_reports_wf - from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_wf + from fmripost_rapidtide.workflows.rapidtide import ( + init_denoise_single_run_wf, + init_rapidtide_wf, + ) spaces = config.workflow.spaces omp_nthreads = config.nipype.omp_nthreads @@ -477,6 +510,16 @@ def init_single_run_wf(bold_file): ]), ]) # fmt:skip + # Denoising workflow + if denoise: + denoise_single_run_wf = init_denoise_single_run_wf(bold_file) + workflow.connect([ + (rapidtide_wf, denoise_single_run_wf, [ + ('outputnode.regressor', 'inputnode.regressor'), + ('outputnode.delay_map', 'inputnode.delay_map'), + ]), + ]) # fmt:skip + # Generate reportlets func_fit_reports_wf = init_func_fit_reports_wf(output_dir=config.execution.output_dir) func_fit_reports_wf.inputs.inputnode.source_file = bold_file @@ -492,12 +535,17 @@ def _prefix(subid): def clean_datasinks(workflow: pe.Workflow, bold_file: str) -> pe.Workflow: - """Overwrite ``out_path_base`` of smriprep's DataSinks.""" + """Overwrite attributes of DataSinks.""" for node in workflow.list_node_names(): - if node.split('.')[-1].startswith('ds_'): + node_name = node.split('.')[-1] + if node_name.startswith('ds_'): workflow.get_node(node).interface.out_path_base = '' workflow.get_node(node).inputs.base_directory = str(config.execution.output_dir) workflow.get_node(node).inputs.source_file = bold_file + + if node_name.startswith('ds_report_'): + workflow.get_node(node).inputs.datatype = 'figures' + return workflow diff --git a/src/fmripost_rapidtide/workflows/rapidtide.py b/src/fmripost_rapidtide/workflows/rapidtide.py index b0c5518..42032d4 100644 --- a/src/fmripost_rapidtide/workflows/rapidtide.py +++ b/src/fmripost_rapidtide/workflows/rapidtide.py @@ -187,28 +187,27 @@ def init_rapidtide_wf( ('gm', 'refineinclude'), # GM mask for refinement ('gm', 'offsetinclude'), # GM mask for offset calculation ]), - (rapidtide, outputnode, [ - ('maxtimemap', 'delay_map'), - ('lagtcgenerator', 'regressor_file'), - ]) ]) # fmt:skip ds_delay_map = pe.Node( DerivativesDataSink( - source_file=bold_file, compress=True, desc='delay', suffix='boldmap', - Units='s', ), name='ds_delay_map', run_without_submitting=True, ) - workflow.connect([(rapidtide, ds_delay_map, [('maxtimemap', 'in_file')])]) + workflow.connect([ + (rapidtide, ds_delay_map, [ + ('maxtimemap', 'in_file'), + ('maxtimemap_json', 'meta_dict'), + ]), + (ds_delay_map, outputnode, [('out_file', 'delay_map')]), + ]) # fmt:skip ds_regressor = pe.Node( DerivativesDataSink( - source_file=bold_file, desc='regressor', suffix='timeseries', extension='.tsv', @@ -216,6 +215,77 @@ def init_rapidtide_wf( name='ds_regressor', run_without_submitting=True, ) - workflow.connect([(rapidtide, ds_regressor, [('lagtcgenerator', 'in_file')])]) + workflow.connect([ + (rapidtide, ds_regressor, [ + ('lagtcgenerator', 'in_file'), + ('lagtcgenerator_json', 'meta_dict'), + ]), + (ds_regressor, outputnode, [('out_file', 'regressor_file')]), + ]) # fmt:skip + + return workflow + + +def init_denoise_single_run_wf( + *, + bold_file: str, + metadata: dict, + mem_gb: dict, +): + """Denoise a single run using rapidtide.""" + + from niworkflows.engine.workflows import LiterateWorkflow as Workflow + + from fmripost_rapidtide.interfaces.bids import DerivativesDataSink + from fmripost_rapidtide.interfaces.rapidtide import RetroGLM + + workflow = Workflow(name=_get_wf_name(bold_file, 'rapidtide_denoise')) + workflow.__postdesc__ = """\ +Identification and removal of traveling wave artifacts was performed using rapidtide. +""" + + inputnode = pe.Node( + niu.IdentityInterface( + fields=[ + 'bold', + 'bold_mask', + 'dseg', + 'regressor', + 'lag_map', + 'skip_vols', + ], + ), + name='inputnode', + ) + denoise_bold = pe.Node( + RetroGLM(), + name='denoise_bold', + ) + workflow.connect([ + (inputnode, denoise_bold, [ + ('bold', 'in_file'), + ('bold_mask', 'brainmask'), + ('dseg', 'dseg'), + ('regressor', 'regressor'), + ('lag_map', 'lag_map'), + ('skip_vols', 'numskip'), + ]), + ]) # fmt:skip + + ds_denoised_bold = pe.Node( + DerivativesDataSink( + compress=True, + desc='denoised', + suffix='bold', + ), + name='ds_denoised_bold', + run_without_submitting=True, + ) + workflow.connect([ + (denoise_bold, ds_denoised_bold, [ + ('denoised', 'in_file'), + ('denoised_json', 'meta_dict'), + ]), + ]) # fmt:skip return workflow From c16841dec83cf1d8b4d03999dff767fabf410faf Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sat, 7 Sep 2024 08:48:25 -0400 Subject: [PATCH 10/31] Update. --- src/fmripost_rapidtide/workflows/base.py | 4 ---- src/fmripost_rapidtide/workflows/rapidtide.py | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/fmripost_rapidtide/workflows/base.py b/src/fmripost_rapidtide/workflows/base.py index d8605f4..4ed1dfa 100644 --- a/src/fmripost_rapidtide/workflows/base.py +++ b/src/fmripost_rapidtide/workflows/base.py @@ -547,10 +547,6 @@ def init_single_run_wf(bold_file, denoise=True): return clean_datasinks(workflow, bold_file=bold_file) -def _prefix(subid): - return subid if subid.startswith('sub-') else f'sub-{subid}' - - def clean_datasinks(workflow: pe.Workflow, bold_file: str) -> pe.Workflow: """Overwrite attributes of DataSinks.""" for node in workflow.list_node_names(): diff --git a/src/fmripost_rapidtide/workflows/rapidtide.py b/src/fmripost_rapidtide/workflows/rapidtide.py index a8c99d2..42032d4 100644 --- a/src/fmripost_rapidtide/workflows/rapidtide.py +++ b/src/fmripost_rapidtide/workflows/rapidtide.py @@ -117,8 +117,6 @@ def init_rapidtide_wf( fields=[ 'delay_map', 'regressor_file', - 'confound_regressed', - 'denoised', ], ), name='outputnode', From adf5c404d5c27e16c189746ffbfe3a6ceaeabe5e Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sat, 7 Sep 2024 11:22:10 -0400 Subject: [PATCH 11/31] Update. --- .../interfaces/reportlets.py | 114 ++++------------ src/fmripost_rapidtide/workflows/base.py | 128 ++++++++++++++---- src/fmripost_rapidtide/workflows/confounds.py | 23 +++- src/fmripost_rapidtide/workflows/rapidtide.py | 65 --------- 4 files changed, 148 insertions(+), 182 deletions(-) diff --git a/src/fmripost_rapidtide/interfaces/reportlets.py b/src/fmripost_rapidtide/interfaces/reportlets.py index 119c5cb..fcf3540 100644 --- a/src/fmripost_rapidtide/interfaces/reportlets.py +++ b/src/fmripost_rapidtide/interfaces/reportlets.py @@ -29,7 +29,6 @@ from nipype.interfaces.base import ( BaseInterfaceInputSpec, - Directory, File, InputMultiObject, SimpleInterface, @@ -163,114 +162,57 @@ def _generate_segment(self): ) -class _ICARapidtideInputSpecRPT(BaseInterfaceInputSpec): - in_file = File( +class _FCInflationPlotInputSpecRPT(BaseInterfaceInputSpec): + fcinflation_file = File( exists=True, mandatory=True, - desc='BOLD series input to Rapidtide', - ) - melodic_dir = Directory( - exists=True, - mandatory=True, - desc='MELODIC directory containing the ICA outputs', - ) - rapidtide_noise_ics = File( - exists=True, - desc='Noise components estimated by Rapidtide, in a comma-separated values file.', + desc='FC inflation time series', ) out_report = File( - 'rapidtide_reportlet.svg', + 'fcinflation_reportlet.svg', usedefault=True, desc='Filename for the visual report generated by Nipype.', ) - report_mask = File( - exists=True, - mandatory=True, - desc=( - 'Mask used to draw the outline on the reportlet. ' - 'If not set the mask will be derived from the data.' - ), - ) - compress_report = traits.Bool( - True, - usedefault=True, - desc='Whether to compress the reportlet with SVGO.', - ) -class _ICARapidtideOutputSpecRPT(TraitedSpec): +class _FCInflationPlotOutputSpecRPT(TraitedSpec): out_report = File( exists=True, desc='Filename for the visual report generated by Nipype.', ) -class ICARapidtideRPT(SimpleInterface): +class FCInflationPlotRPT(SimpleInterface): """Create a reportlet for Rapidtide outputs.""" - input_spec = _ICARapidtideInputSpecRPT - output_spec = _ICARapidtideOutputSpecRPT - - def _run_interface(self, runtime): - from niworkflows.viz.utils import plot_melodic_components - - out_file = os.path.abspath(self.inputs.out_report) - - plot_melodic_components( - melodic_dir=self.inputs.melodic_dir, - in_file=self.inputs.in_file, - out_file=out_file, - compress=self.inputs.compress_report, - report_mask=self.inputs.report_mask, - noise_components_file=self.inputs.rapidtide_noise_ics, - ) - self._results['out_report'] = out_file - return runtime - - -class _ICARapidtideMetricsInputSpecRPT(BaseInterfaceInputSpec): - rapidtide_features = File( - exists=True, - mandatory=True, - desc='Rapidtide metrics', - ) - out_report = File( - 'metrics_reportlet.svg', - usedefault=True, - desc='Filename for the visual report generated by Nipype.', - ) - - -class _ICARapidtideMetricsOutputSpecRPT(TraitedSpec): - out_report = File( - exists=True, - desc='Filename for the visual report generated by Nipype.', - ) - - -class ICARapidtideMetricsRPT(SimpleInterface): - """Create a reportlet for Rapidtide outputs.""" - - input_spec = _ICARapidtideMetricsInputSpecRPT - output_spec = _ICARapidtideMetricsOutputSpecRPT + input_spec = _FCInflationPlotInputSpecRPT + output_spec = _FCInflationPlotOutputSpecRPT def _run_interface(self, runtime): + import matplotlib.pyplot as plt import pandas as pd import seaborn as sns - out_file = os.path.abspath(self.inputs.out_report) - - df = pd.read_table(self.inputs.rapidtide_features) + sns.set_theme(style='whitegrid') - sns.set_theme(style='ticks') + out_file = os.path.abspath(self.inputs.out_report) - g = sns.pairplot( - df, - hue='classification', - vars=['edge_fract', 'csf_fract', 'max_RP_corr', 'HFC'], - palette={'rejected': 'red', 'accepted': 'blue'}, - corner=True, - ) - g.savefig(out_file) + df = pd.read_table(self.inputs.fcinflation_file) + + fig, ax = plt.subplots(figsize=(16, 8)) + palette = ['red', 'lightblue', 'blue'] + for i_col, col in enumerate(['preprocessed', 'denoised', 'rapidtide']): + df[f'{col}_mean_minus_std'] = df[f'{col}_mean'] - df[f'{col}_std'] + df[f'{col}_mean_plus_std'] = df[f'{col}_mean'] + df[f'{col}_std'] + sns.lineplot(x='timepoint', y=f'{col}_mean', data=df, ax=ax, color=palette[i_col]) + ax.fill_between( + x=df['timepoint'], + y1=df[f'{col}_mean_minus_std'], + y2=df[f'{col}_mean_plus_std'], + alpha=0.5, + color=palette[i_col], + ) + ax.set_xlim(0, df['timepoint'].max()) + fig.savefig(out_file) self._results['out_report'] = out_file return runtime diff --git a/src/fmripost_rapidtide/workflows/base.py b/src/fmripost_rapidtide/workflows/base.py index 4ed1dfa..d3e3320 100644 --- a/src/fmripost_rapidtide/workflows/base.py +++ b/src/fmripost_rapidtide/workflows/base.py @@ -143,7 +143,6 @@ def init_single_subject_wf(subject_id: str): from fmripost_rapidtide.interfaces.nilearn import MeanImage from fmripost_rapidtide.interfaces.reportlets import AboutSummary, SubjectSummary from fmripost_rapidtide.utils.bids import collect_derivatives - from fmripost_rapidtide.workflows.rapidtide import init_denoise_single_run_wf spaces = config.workflow.spaces @@ -295,28 +294,33 @@ def init_single_subject_wf(subject_id: str): ) for i_run, bold_file in enumerate(subject_data['bold']): - single_run_wf = init_single_run_wf(bold_file, denoise=denoise_within_run) - workflow.add_nodes([single_run_wf]) + fit_single_run_wf = init_fit_single_run_wf(bold_file) + denoise_single_run_wf = init_denoise_single_run_wf(bold_file) + workflow.connect([ + (fit_single_run_wf, denoise_single_run_wf, [ + ('outputnode.regressor', 'inputnode.regressor'), + ]), + ]) # fmt:skip - if not denoise_within_run: - # Denoise the BOLD data using the mean lag map + if denoise_within_run: + # Denoise the BOLD data using the run-wise lag map workflow.connect([ - (single_run_wf, merge_lag_maps, [('outputnode.delay_map', f'in{i_run + 1}')]), - (merge_lag_maps, average_lag_map, [('out', 'in_file')]), + (fit_single_run_wf, denoise_single_run_wf, [ + ('outputnode.delay_map', 'inputnode.delay_map'), + ]), ]) # fmt:skip - - denoise_single_run_wf = init_denoise_single_run_wf(bold_file) + else: + # Denoise the BOLD data using the mean lag map workflow.connect([ - (single_run_wf, denoise_single_run_wf, [ - ('outputnode.regressor', 'inputnode.regressor'), - ]), + (fit_single_run_wf, merge_lag_maps, [('outputnode.delay_map', f'in{i_run + 1}')]), + (merge_lag_maps, average_lag_map, [('out', 'in_file')]), (average_lag_map, denoise_single_run_wf, [('out_file', 'inputnode.delay_map')]), ]) # fmt:skip return workflow -def init_single_run_wf(bold_file, denoise=True): +def init_fit_single_run_wf(bold_file): """Set up a single-run workflow for fMRIPost-rapidtide.""" from fmriprep.utils.misc import estimate_bold_mem_usage from nipype.interfaces import utility as niu @@ -324,12 +328,9 @@ def init_single_run_wf(bold_file, denoise=True): from fmripost_rapidtide.interfaces.misc import ApplyTransforms from fmripost_rapidtide.utils.bids import collect_derivatives, extract_entities - from fmripost_rapidtide.workflows.confounds import init_confounds_wf + from fmripost_rapidtide.workflows.confounds import init_fit_confounds_wf from fmripost_rapidtide.workflows.outputs import init_func_fit_reports_wf - from fmripost_rapidtide.workflows.rapidtide import ( - init_denoise_single_run_wf, - init_rapidtide_wf, - ) + from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_wf spaces = config.workflow.spaces omp_nthreads = config.nipype.omp_nthreads @@ -511,16 +512,6 @@ def init_single_run_wf(bold_file, denoise=True): ]), ]) # fmt:skip - # Denoising workflow - if denoise: - denoise_single_run_wf = init_denoise_single_run_wf(bold_file) - workflow.connect([ - (rapidtide_wf, denoise_single_run_wf, [ - ('outputnode.regressor', 'inputnode.regressor'), - ('outputnode.delay_map', 'inputnode.delay_map'), - ]), - ]) # fmt:skip - # Generate reportlets func_fit_reports_wf = init_func_fit_reports_wf(output_dir=config.execution.output_dir) func_fit_reports_wf.inputs.inputnode.source_file = bold_file @@ -529,7 +520,7 @@ def init_single_run_wf(bold_file, denoise=True): workflow.connect([(boldref_buffer, func_fit_reports_wf, [('bold', 'inputnode.bold_mni6')])]) # Generate confounds - confounds_wf = init_confounds_wf( + confounds_wf = init_fit_confounds_wf( bold_file=bold_file, mem_gb=mem_gb['filesize'], ) @@ -547,6 +538,85 @@ def init_single_run_wf(bold_file, denoise=True): return clean_datasinks(workflow, bold_file=bold_file) +def init_denoise_single_run_wf( + *, + bold_file: str, +): + """Denoise a single run using rapidtide.""" + + from nipype.interfaces import utility as niu + from niworkflows.engine.workflows import LiterateWorkflow as Workflow + + from fmripost_rapidtide.interfaces.bids import DerivativesDataSink + from fmripost_rapidtide.interfaces.rapidtide import RetroGLM + from fmripost_rapidtide.interfaces.reportlets import FCInflationPlotRPT + from fmripost_rapidtide.workflows.confounds import init_denoising_confounds_wf + + workflow = Workflow(name=_get_wf_name(bold_file, 'rapidtide_denoise')) + workflow.__postdesc__ = """\ +Identification and removal of traveling wave artifacts was performed using rapidtide. +""" + + inputnode = pe.Node( + niu.IdentityInterface( + fields=[ + 'bold', + 'bold_mask', + 'dseg', + 'regressor', + 'lag_map', + 'skip_vols', + ], + ), + name='inputnode', + ) + denoise_bold = pe.Node( + RetroGLM(), + name='denoise_bold', + ) + workflow.connect([ + (inputnode, denoise_bold, [ + ('bold', 'in_file'), + ('bold_mask', 'brainmask'), + ('dseg', 'dseg'), + ('regressor', 'regressor'), + ('lag_map', 'lag_map'), + ('skip_vols', 'numskip'), + ]), + ]) # fmt:skip + + ds_denoised_bold = pe.Node( + DerivativesDataSink( + compress=True, + desc='denoised', + suffix='bold', + ), + name='ds_denoised_bold', + run_without_submitting=True, + ) + workflow.connect([ + (denoise_bold, ds_denoised_bold, [ + ('denoised', 'in_file'), + ('denoised_json', 'meta_dict'), + ]), + ]) # fmt:skip + + # Generate confounds + denoising_confounds_wf = init_denoising_confounds_wf() + workflow.connect([ + (inputnode, denoising_confounds_wf, [ + ('bold', 'inputnode.preprocessed_bold'), + ('bold_mask', 'inputnode.mask'), + ]), + (denoise_bold, denoising_confounds_wf, [ + ('denoised', 'inputnode.denoised_bold'), + ('denoised', 'inputnode.rapidtide_bold'), + ]), + ]) # fmt:skip + + return clean_datasinks(workflow, bold_file=bold_file) + + def clean_datasinks(workflow: pe.Workflow, bold_file: str) -> pe.Workflow: """Overwrite attributes of DataSinks.""" for node in workflow.list_node_names(): diff --git a/src/fmripost_rapidtide/workflows/confounds.py b/src/fmripost_rapidtide/workflows/confounds.py index 9c0201b..d186fee 100644 --- a/src/fmripost_rapidtide/workflows/confounds.py +++ b/src/fmripost_rapidtide/workflows/confounds.py @@ -29,10 +29,10 @@ """ -def init_confounds_wf( +def init_denoising_confounds_wf( bold_file: str, mem_gb: float, - name: str = 'confounds_wf', + name: str = 'denoising_confounds_wf', ): from nipype.interfaces import utility as niu from nipype.pipeline import engine as pe @@ -41,6 +41,7 @@ def init_confounds_wf( from fmripost_rapidtide.config import DEFAULT_MEMORY_MIN_GB from fmripost_rapidtide.interfaces.bids import DerivativesDataSink from fmripost_rapidtide.interfaces.confounds import FCInflation + from fmripost_rapidtide.interfaces.reportlets import FCInflationPlotRPT workflow = Workflow(name=name) @@ -130,6 +131,24 @@ def init_confounds_wf( ) workflow.connect([(merge_fci, ds_metrics, [('confounds_metrics', 'in_file')])]) + # Generate reportlets + plot_fcinflation = pe.Node( + FCInflationPlotRPT(), + name='plot_fcinflation', + ) + workflow.connect([(merge_fci, plot_fcinflation, [('confounds_file', 'fcinflation_file')])]) + + ds_report_fcinflation = pe.Node( + DerivativesDataSink( + desc='fcinflation', + suffix='bold', + extension='.svg', + ), + name='ds_report_fcinflation', + run_without_submitting=True, + ) + workflow.connect([(plot_fcinflation, ds_report_fcinflation, [('out_report', 'in_file')])]) + return workflow diff --git a/src/fmripost_rapidtide/workflows/rapidtide.py b/src/fmripost_rapidtide/workflows/rapidtide.py index 42032d4..a80e1b3 100644 --- a/src/fmripost_rapidtide/workflows/rapidtide.py +++ b/src/fmripost_rapidtide/workflows/rapidtide.py @@ -224,68 +224,3 @@ def init_rapidtide_wf( ]) # fmt:skip return workflow - - -def init_denoise_single_run_wf( - *, - bold_file: str, - metadata: dict, - mem_gb: dict, -): - """Denoise a single run using rapidtide.""" - - from niworkflows.engine.workflows import LiterateWorkflow as Workflow - - from fmripost_rapidtide.interfaces.bids import DerivativesDataSink - from fmripost_rapidtide.interfaces.rapidtide import RetroGLM - - workflow = Workflow(name=_get_wf_name(bold_file, 'rapidtide_denoise')) - workflow.__postdesc__ = """\ -Identification and removal of traveling wave artifacts was performed using rapidtide. -""" - - inputnode = pe.Node( - niu.IdentityInterface( - fields=[ - 'bold', - 'bold_mask', - 'dseg', - 'regressor', - 'lag_map', - 'skip_vols', - ], - ), - name='inputnode', - ) - denoise_bold = pe.Node( - RetroGLM(), - name='denoise_bold', - ) - workflow.connect([ - (inputnode, denoise_bold, [ - ('bold', 'in_file'), - ('bold_mask', 'brainmask'), - ('dseg', 'dseg'), - ('regressor', 'regressor'), - ('lag_map', 'lag_map'), - ('skip_vols', 'numskip'), - ]), - ]) # fmt:skip - - ds_denoised_bold = pe.Node( - DerivativesDataSink( - compress=True, - desc='denoised', - suffix='bold', - ), - name='ds_denoised_bold', - run_without_submitting=True, - ) - workflow.connect([ - (denoise_bold, ds_denoised_bold, [ - ('denoised', 'in_file'), - ('denoised_json', 'meta_dict'), - ]), - ]) # fmt:skip - - return workflow From 806bc5dd2b1c0e6e3f104d3cf3c92f9a0cc3d2f6 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sat, 7 Sep 2024 14:51:33 -0400 Subject: [PATCH 12/31] Keep working. --- src/fmripost_rapidtide/config.py | 2 + .../interfaces/rapidtide.py | 244 +----------------- src/fmripost_rapidtide/workflows/base.py | 29 +-- 3 files changed, 20 insertions(+), 255 deletions(-) diff --git a/src/fmripost_rapidtide/config.py b/src/fmripost_rapidtide/config.py index e28d949..0526b54 100644 --- a/src/fmripost_rapidtide/config.py +++ b/src/fmripost_rapidtide/config.py @@ -613,6 +613,8 @@ class workflow(_Config): """Generate HCP Grayordinates, accepts either ``'91k'`` (default) or ``'170k'``.""" dummy_scans = None """Set a number of initial scans to be considered nonsteady states.""" + average_over_runs = True + """Whether to average lag maps over runs or not.""" class loggers: diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index f35b0e0..d6256f3 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -293,255 +293,39 @@ def _list_outputs(self): class _RetroGLMInputSpec(CommandLineInputSpec): in_file = File( exists=True, - argstr='%s', - position=-2, - mandatory=True, - desc='File to denoise', - ) - outputname = traits.Str( argstr='%s', position=-1, mandatory=True, - desc='Output name', - ) - rapidtide_dir = traits.Directory( - exists=True, - argstr='%s', - position=-1, - mandatory=True, - desc='Directory containing the output of rapidtide', - ) - # Set by the workflow - denoising = traits.Bool( - argstr='--denoising', - mandatory=False, - ) - brainmask = File( - exists=True, - argstr='--brainmask %s', - mandatory=False, - ) - graymattermask = File( - exists=True, - argstr='--graymattermask %s', - mandatory=False, - ) - whitemattermask = File( - exists=True, - argstr='--whitemattermask %s', - mandatory=False, - ) - datatstep = traits.Float( - argstr='--datatstep %f', - mandatory=False, - ) - padseconds = traits.Float( - argstr='--padseconds %f', - mandatory=False, - ) - globalmeaninclude = File( - exists=True, - argstr='--globalmeaninclude %s', - mandatory=False, - ) - motionfile = File( - exists=True, - argstr='--motionfile %s', - mandatory=False, + desc='File to denoise', ) - corrmask = File( + runoptionsfile = File( exists=True, - argstr='--corrmask %s', + argstr='--runoptionsfile %s', mandatory=False, ) - refineinclude = File( + procmaskfile = File( exists=True, - argstr='--refineinclude %s', + argstr='--procmaskfile %s', mandatory=False, ) - offsetinclude = File( + corrmaskfile = File( exists=True, - argstr='--offsetinclude %s', + argstr='--corrmaskfile %s', mandatory=False, ) - # Set by the user - autosync = traits.Bool( - argstr='--autosync', - mandatory=False, - ) - filterband = traits.Enum( - 'vlf', - 'lfo', - 'resp', - 'cardiac', - 'hrv_ulf', - 'hrv_vlf', - 'hrv_lf', - 'hrv_hf', - 'hrv_vhf', - 'lfo_legacy', - argstr='--filterband %s', - mandatory=False, - ) - filterfreqs = traits.List( - traits.Float, - argstr='--filterfreqs %s', - mandatory=False, - minlen=2, - maxlen=2, - ) - filterstopfreqs = traits.List( - traits.Float, - argstr='--filterstopfreqs %s', - mandatory=False, - minlen=2, - maxlen=2, - ) - numnull = traits.Int( - argstr='--numnull %d', - mandatory=False, - ) - detrendorder = traits.Int( - argstr='--detrendorder %d', - mandatory=False, - ) - spatialfilt = traits.Float( - argstr='--spatialfilt %f', - mandatory=False, - ) - confoundfile = File( + lagtimesfile = File( exists=True, - argstr='--confoundfile %s', - mandatory=False, - ) - confoundpowers = traits.Int( - argstr='--confoundpowers %d', - mandatory=False, - ) - confoundderiv = traits.Bool( - argstr='--confoundderiv', - mandatory=False, - ) - globalsignalmethod = traits.Enum( - 'sum', - 'meanscale', - 'pca', - 'random', - argstr='--globalsignalmethod %s', - mandatory=False, - ) - globalpcacomponents = traits.Float( - argstr='--globalpcacomponents %f', - mandatory=False, - ) - numskip = traits.Int( - argstr='--numskip %d', - mandatory=False, - ) - numtozero = traits.Int( - argstr='--numtozero %d', - mandatory=False, - ) - timerange = traits.List( - traits.Int, - argstr='--timerange %s', - mandatory=False, - minlen=2, - maxlen=2, - ) - corrweighting = traits.Enum( - 'phat', - 'liang', - 'eckart', - 'regressor', - argstr='--corrweighting %s', - mandatory=False, - ) - simcalcrange = traits.List( - traits.Int, - argstr='--simcalcrange %s', - mandatory=False, - minlen=2, - maxlen=2, - ) - fixdelay = traits.Float( - argstr='--fixdelay %f', - mandatory=False, - ) - searchrange = traits.List( - traits.Int, - argstr='--searchrange %s', - mandatory=False, - minlen=2, - maxlen=2, - ) - sigmalimit = traits.Float( - argstr='--sigmalimit %f', - mandatory=False, - ) - bipolar = traits.Bool( - argstr='--bipolar', - mandatory=False, - ) - lagminthresh = traits.Float( - argstr='--lagminthresh %f', + argstr='--lagtimesfile %s', mandatory=False, ) - lagmaxthresh = traits.Float( - argstr='--lagmaxthresh %f', - mandatory=False, - ) - ampthresh = traits.Float( - argstr='--ampthresh %f', - mandatory=False, - ) - sigmathresh = traits.Float( - argstr='--sigmathresh %f', - mandatory=False, - ) - pcacomponents = traits.Float( - argstr='--pcacomponents %f', - mandatory=False, - ) - convergencethresh = traits.Float( - argstr='--convergencethresh %f', - mandatory=False, - ) - maxpasses = traits.Int( - argstr='--maxpasses %d', - mandatory=False, - ) - glmsourcefile = File( + lagtcgeneratorfile = File( exists=True, - argstr='--glmsourcefile %s', - mandatory=False, - ) - glmderivs = traits.Int( - argstr='--glmderivs %d', - mandatory=False, - ) - outputlevel = traits.Enum( - 'min', - 'less', - 'normal', - 'more', - 'max', - argstr='--outputlevel %s', + argstr='--lagtcgeneratorfile %s', mandatory=False, ) - territorymap = File( + meanfile = File( exists=True, - argstr='--territorymap %s', - mandatory=False, - ) - autorespdelete = traits.Bool( - argstr='--autorespdelete', - mandatory=False, - ) - nprocs = traits.Int( - default=1, - usedefault=True, - argstr='--nprocs %d', + argstr='--meanfile %s', mandatory=False, ) @@ -554,7 +338,7 @@ class _RetroGLMOutputSpec(TraitedSpec): class RetroGLM(CommandLine): """Run the rapidtide command-line interface.""" - _cmd = 'retroglm --noprogressbar --spcalculation --memprofile' + _cmd = 'retroglm' input_spec = _RetroGLMInputSpec output_spec = _RetroGLMOutputSpec diff --git a/src/fmripost_rapidtide/workflows/base.py b/src/fmripost_rapidtide/workflows/base.py index d3e3320..313797c 100644 --- a/src/fmripost_rapidtide/workflows/base.py +++ b/src/fmripost_rapidtide/workflows/base.py @@ -294,8 +294,8 @@ def init_single_subject_wf(subject_id: str): ) for i_run, bold_file in enumerate(subject_data['bold']): - fit_single_run_wf = init_fit_single_run_wf(bold_file) - denoise_single_run_wf = init_denoise_single_run_wf(bold_file) + fit_single_run_wf = init_fit_single_run_wf(bold_file=bold_file) + denoise_single_run_wf = init_denoise_single_run_wf(bold_file=bold_file) workflow.connect([ (fit_single_run_wf, denoise_single_run_wf, [ ('outputnode.regressor', 'inputnode.regressor'), @@ -320,7 +320,7 @@ def init_single_subject_wf(subject_id: str): return workflow -def init_fit_single_run_wf(bold_file): +def init_fit_single_run_wf(*, bold_file): """Set up a single-run workflow for fMRIPost-rapidtide.""" from fmriprep.utils.misc import estimate_bold_mem_usage from nipype.interfaces import utility as niu @@ -328,7 +328,6 @@ def init_fit_single_run_wf(bold_file): from fmripost_rapidtide.interfaces.misc import ApplyTransforms from fmripost_rapidtide.utils.bids import collect_derivatives, extract_entities - from fmripost_rapidtide.workflows.confounds import init_fit_confounds_wf from fmripost_rapidtide.workflows.outputs import init_func_fit_reports_wf from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_wf @@ -519,29 +518,10 @@ def init_fit_single_run_wf(bold_file): func_fit_reports_wf.inputs.inputnode.anat_dseg = functional_cache['anat_dseg'] workflow.connect([(boldref_buffer, func_fit_reports_wf, [('bold', 'inputnode.bold_mni6')])]) - # Generate confounds - confounds_wf = init_fit_confounds_wf( - bold_file=bold_file, - mem_gb=mem_gb['filesize'], - ) - workflow.connect([ - (boldref_buffer, confounds_wf, [ - ('bold', 'inputnode.preprocessed_bold'), - ('bold_mask', 'inputnode.mask'), - ]), - (rapidtide_wf, confounds_wf, [ - ('outputnode.confound_regressed', 'inputnode.denoised_bold'), - ('outputnode.denoised', 'inputnode.rapidtide_bold'), - ]), - ]) # fmt:skip - return clean_datasinks(workflow, bold_file=bold_file) -def init_denoise_single_run_wf( - *, - bold_file: str, -): +def init_denoise_single_run_wf(*, bold_file: str): """Denoise a single run using rapidtide.""" from nipype.interfaces import utility as niu @@ -578,7 +558,6 @@ def init_denoise_single_run_wf( (inputnode, denoise_bold, [ ('bold', 'in_file'), ('bold_mask', 'brainmask'), - ('dseg', 'dseg'), ('regressor', 'regressor'), ('lag_map', 'lag_map'), ('skip_vols', 'numskip'), From b0475c4c5bd5cd42e2776e1036e3ad4908bdfa08 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 18 Sep 2024 15:28:55 -0400 Subject: [PATCH 13/31] Fix import. --- src/fmripost_rapidtide/workflows/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/fmripost_rapidtide/workflows/base.py b/src/fmripost_rapidtide/workflows/base.py index 313797c..b43e404 100644 --- a/src/fmripost_rapidtide/workflows/base.py +++ b/src/fmripost_rapidtide/workflows/base.py @@ -529,7 +529,6 @@ def init_denoise_single_run_wf(*, bold_file: str): from fmripost_rapidtide.interfaces.bids import DerivativesDataSink from fmripost_rapidtide.interfaces.rapidtide import RetroGLM - from fmripost_rapidtide.interfaces.reportlets import FCInflationPlotRPT from fmripost_rapidtide.workflows.confounds import init_denoising_confounds_wf workflow = Workflow(name=_get_wf_name(bold_file, 'rapidtide_denoise')) From ab3b7fa7628bd9be4510317d4e8a8fdefcc24f42 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 18 Sep 2024 15:29:35 -0400 Subject: [PATCH 14/31] Update rapidtide.py --- src/fmripost_rapidtide/interfaces/rapidtide.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index d6256f3..a8757a0 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -269,6 +269,7 @@ class _RapidtideInputSpec(CommandLineInputSpec): class _RapidtideOutputSpec(TraitedSpec): pass + for name in rapidtide_output_spec.keys(): _RapidtideOutputSpec.add_class_trait(name, File) From 4df6124f7c8fda4c757908a49eaf88fd32c48e9d Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 26 Sep 2024 15:29:19 -0400 Subject: [PATCH 15/31] Work on rapidtide interfaces. --- .../interfaces/rapidtide.py | 155 ++++++++++++++++-- 1 file changed, 138 insertions(+), 17 deletions(-) diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index a8757a0..9ed4bbb 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -6,16 +6,12 @@ from nipype.interfaces.base import ( CommandLine, CommandLineInputSpec, + DynamicTraitedSpec, File, TraitedSpec, traits, ) -from fmripost_rapidtide.data import load as load_data - -with open(load_data('rapidtide_spec.yml')) as f: - rapidtide_output_spec = yaml.safe_load(f) - class _RapidtideInputSpec(CommandLineInputSpec): in_file = File( @@ -25,10 +21,11 @@ class _RapidtideInputSpec(CommandLineInputSpec): mandatory=True, desc='File to denoise', ) - outputname = traits.Str( + prefix = traits.Str( argstr='%s', position=-1, - mandatory=True, + mandatory=False, + genfile=True, desc='Output name', ) # Set by the workflow @@ -267,11 +264,10 @@ class _RapidtideInputSpec(CommandLineInputSpec): class _RapidtideOutputSpec(TraitedSpec): - pass - - -for name in rapidtide_output_spec.keys(): - _RapidtideOutputSpec.add_class_trait(name, File) + prefix = traits.Str(desc='Directory containing the results, with prefix.') + lagtimesfile = File(exists=True, desc='3D map of optimal delay times') + lagtcgeneratorfile = File(exists=True, desc='Time series of refined regressor') + maskfile = File(exists=True, desc='Mask file (usually called XXX_desc-corrfit_mask.nii.gz)') class Rapidtide(CommandLine): @@ -281,12 +277,128 @@ class Rapidtide(CommandLine): input_spec = _RapidtideInputSpec output_spec = _RapidtideOutputSpec + def _gen_filename(self, name): + if name == 'prefix': + return os.path.join(os.getcwd(), 'rapidtide') + + return None + def _list_outputs(self): outputs = self._outputs().get() - out_dir = os.getcwd() - outputname = self.inputs.outputname - for name, spec in rapidtide_output_spec.items(): - outputs[name] = os.path.join(out_dir, f'{outputname}_{spec["filename"]}') + prefix = self.inputs.prefix + outputs['prefix'] = prefix + outputs['lagtimesfile'] = f'{prefix}_desc-maxtime_map.nii.gz' + outputs['lagtcgeneratorfile'] = f'{prefix}_desc-lagtcgenerator_timeseries.tsv.gz' + outputs['maskfile'] = f'{prefix}_desc-corrfit_mask.nii.gz' + + return outputs + + +class _RetroLagTCSInputSpec(CommandLineInputSpec): + in_file = File( + exists=True, + argstr='%s', + position=0, + mandatory=True, + desc='The name of 4D nifti fmri target file.', + ) + maskfile = File( + exists=True, + argstr='%s', + position=1, + mandatory=True, + desc='The mask file to use (usually called XXX_desc-corrfit_mask.nii.gz)', + ) + lagtimesfile = File( + exists=True, + argstr='%s', + position=2, + mandatory=True, + desc='The name of the lag times file (usually called XXX_desc-maxtime_map.nii.gz)', + ) + lagtcgeneratorfile = File( + exists=True, + argstr='%s', + position=3, + mandatory=True, + desc=( + 'The root name of the lagtc generator file ' + '(usually called XXX_desc-lagtcgenerator_timeseries)' + ), + ) + prefix = traits.Str( + argstr='%s', + position=4, + mandatory=False, + genfile=True, + desc='Output root.', + ) + glmderivs = traits.Int( + argstr='--glmderivs %d', + mandatory=False, + desc='When doing final GLM, include derivatives up to NDERIVS order. Default is 0.', + default=0, + usedefault=True, + ) + nprocs = traits.Int( + default=1, + usedefault=True, + argstr='--nprocs %d', + mandatory=False, + desc=( + 'Use NPROCS worker processes for multiprocessing. ' + 'Setting NPROCS to less than 1 sets the number of worker processes to n_cpus.' + ), + ) + numskip = traits.Int( + argstr='--numskip %d', + default=0, + usedefault=True, + mandatory=False, + desc='Skip NUMSKIP points at the beginning of the fmri file.', + ) + noprogressbar = traits.Bool( + argstr='--noprogressbar', + mandatory=False, + default=True, + usedefault=True, + desc='Will disable showing progress bars (helpful if stdout is going to a file).', + ) + debug = traits.Bool( + argstr='--debug', + mandatory=False, + default=False, + usedefault=True, + desc='Output lots of helpful information.', + ) + + +class _RetroLagTCSOutputSpec(DynamicTraitedSpec): + filter_file = File(exists=True, desc='Filter file') + + +class RetroLagTCS(CommandLine): + """Run the retrolagtcs command-line interface.""" + + _cmd = 'retrolagtcs' + input_spec = _RetroLagTCSInputSpec + output_spec = _RetroLagTCSOutputSpec + + def _gen_filename(self, name): + if name == 'prefix': + return os.path.join(os.getcwd(), 'retrolagtcs') + + return None + + def _list_outputs(self): + outputs = self._outputs().get() + prefix = self.inputs.prefix + outputs['filter_file'] = f'{prefix}_desc-lfofilterEV_bold.nii.gz' + if self.inputs.glmderivs > 0: + for i_deriv in range(self.inputs.glmderivs): + outputs[f'filter_file_deriv{i_deriv + 1}'] = ( + f'{prefix}_desc-lfofilterEVDeriv{i_deriv + 1}_bold.nii.gz' + ) return outputs @@ -295,10 +407,19 @@ class _RetroGLMInputSpec(CommandLineInputSpec): in_file = File( exists=True, argstr='%s', - position=-1, + position=0, mandatory=True, desc='File to denoise', ) + datafileroot = traits.Str( + argstr='%s', + position=1, + mandatory=True, + desc=( + 'The root name of the previously run rapidtide dataset ' + '(everything up to but not including the underscore.)' + ), + ) runoptionsfile = File( exists=True, argstr='--runoptionsfile %s', From c7315d31e710cfc01c13eddd8789f1c9715a9545 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Thu, 26 Sep 2024 15:40:31 -0400 Subject: [PATCH 16/31] Update rapidtide.py --- src/fmripost_rapidtide/interfaces/rapidtide.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index 9ed4bbb..1bfcbf9 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -2,7 +2,6 @@ import os -import yaml from nipype.interfaces.base import ( CommandLine, CommandLineInputSpec, @@ -466,15 +465,8 @@ class RetroGLM(CommandLine): def _list_outputs(self): outputs = self._outputs().get() - out_dir = os.getcwd() - outputname = self.inputs.outputname - outputs['delay_map'] = os.path.join(out_dir, f'{outputname}_desc-maxtime_map.nii.gz') - outputs['regressor_file'] = os.path.join( - out_dir, - f'{outputname}_desc-refinedmovingregressor_timeseries.tsv.gz', - ) - outputs['denoised'] = os.path.join( - out_dir, - f'{outputname}_desc-lfofilterCleaned_bold.nii.gz', - ) + datafileroot = self.inputs.datafileroot + outputs['delay_map'] = f'{datafileroot}_desc-maxtime_map.nii.gz' + outputs['regressor_file'] = f'{datafileroot}_desc-refinedmovingregressor_timeseries.tsv.gz' + outputs['denoised'] = f'{datafileroot}_desc-lfofilterCleaned_bold.nii.gz' return outputs From 8bfd10e9007dbb5da5c4b09ec836609890e9f95a Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Sat, 28 Sep 2024 10:54:21 -0400 Subject: [PATCH 17/31] A bit more work. --- .../interfaces/rapidtide.py | 5 +- src/fmripost_rapidtide/workflows/rapidtide.py | 245 +++++++++++++++++- 2 files changed, 243 insertions(+), 7 deletions(-) diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index 1bfcbf9..a33e8d9 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -457,7 +457,7 @@ class _RetroGLMOutputSpec(TraitedSpec): class RetroGLM(CommandLine): - """Run the rapidtide command-line interface.""" + """Run the retroglm command-line interface to denoise BOLD with existing rapidtide outputs.""" _cmd = 'retroglm' input_spec = _RetroGLMInputSpec @@ -466,7 +466,6 @@ class RetroGLM(CommandLine): def _list_outputs(self): outputs = self._outputs().get() datafileroot = self.inputs.datafileroot - outputs['delay_map'] = f'{datafileroot}_desc-maxtime_map.nii.gz' - outputs['regressor_file'] = f'{datafileroot}_desc-refinedmovingregressor_timeseries.tsv.gz' outputs['denoised'] = f'{datafileroot}_desc-lfofilterCleaned_bold.nii.gz' + outputs['denoised_json'] = f'{datafileroot}_desc-lfofilterCleaned_bold.json' return outputs diff --git a/src/fmripost_rapidtide/workflows/rapidtide.py b/src/fmripost_rapidtide/workflows/rapidtide.py index a80e1b3..970f936 100644 --- a/src/fmripost_rapidtide/workflows/rapidtide.py +++ b/src/fmripost_rapidtide/workflows/rapidtide.py @@ -26,11 +26,10 @@ from nipype.pipeline import engine as pe from fmripost_rapidtide import config -from fmripost_rapidtide.interfaces.rapidtide import Rapidtide from fmripost_rapidtide.utils.utils import _get_wf_name -def init_rapidtide_wf( +def init_rapidtide_fit_wf( *, bold_file: str, metadata: dict, @@ -54,9 +53,9 @@ def init_rapidtide_wf( :graph2use: orig :simple_form: yes - from fmripost_rapidtide.workflows.bold.confounds import init_rapidtide_wf + from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_fit_wf - wf = init_rapidtide_wf( + wf = init_rapidtide_fit_wf( bold_file="fake.nii.gz", metadata={"RepetitionTime": 1.0}, ) @@ -93,6 +92,7 @@ def init_rapidtide_wf( from fmripost_rapidtide.interfaces.bids import DerivativesDataSink from fmripost_rapidtide.interfaces.nilearn import SplitDseg + from fmripost_rapidtide.interfaces.rapidtide import Rapidtide workflow = Workflow(name=_get_wf_name(bold_file, 'rapidtide')) workflow.__postdesc__ = """\ @@ -224,3 +224,240 @@ def init_rapidtide_wf( ]) # fmt:skip return workflow + + +def init_rapidtide_denoise_wf( + *, + bold_file: str, + metadata: dict, + mem_gb: dict, +): + """Build a workflow that runs `Rapidtide`_. + + This workflow wraps `Rapidtide`_ to characterize and remove the traveling wave artifact. + + The following steps are performed: + + #. Remove non-steady state volumes from the bold series. + #. Run rapidtide + #. Collect rapidtide outputs + #. Generate a confounds file with the rapidtide outputs + + .. _Rapidtide: https://rapidtide.readthedocs.io/ + + Workflow Graph + .. workflow:: + :graph2use: orig + :simple_form: yes + + from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_fit_wf + + wf = init_rapidtide_fit_wf( + bold_file="fake.nii.gz", + metadata={"RepetitionTime": 1.0}, + ) + + Parameters + ---------- + bold_file + BOLD series used as name source for derivatives + metadata : :obj:`dict` + BIDS metadata for BOLD file + + Inputs + ------ + bold + BOLD series in template space + bold_mask + BOLD series mask in template space + dseg + Tissue segmentation in template space + confounds + fMRIPrep-formatted confounds file, which must include the following columns: + "trans_x", "trans_y", "trans_z", "rot_x", "rot_y", "rot_z". + skip_vols + number of non steady state volumes + + Outputs + ------- + denoised_bold + confounds_file + """ + + from niworkflows.engine.workflows import LiterateWorkflow as Workflow + + from fmripost_rapidtide.interfaces.bids import DerivativesDataSink + from fmripost_rapidtide.interfaces.rapidtide import RetroGLM + + workflow = Workflow(name=_get_wf_name(bold_file, 'denoise')) + workflow.__postdesc__ = """\ +Identification and removal of traveling wave artifacts was performed using rapidtide. +""" + + inputnode = pe.Node( + niu.IdentityInterface( + fields=[ + 'bold', + 'bold_mask', + 'rapidtide_dir', + 'skip_vols', + ], + ), + name='inputnode', + ) + + # Remove the traveling wave artifact + retroglm = pe.Node( + RetroGLM( + nprocs=config.nipype.omp_nthreads, + ), + name='retroglm', + mem_gb=mem_gb['filesize'] * 6, + n_procs=config.nipype.omp_nthreads, + ) + workflow.connect([ + (inputnode, retroglm, [ + ('bold', 'in_file'), + ('bold_mask', 'brainmask'), + ('rapidtide_dir', 'datafileroot'), + ('skip_vols', 'numskip'), + ]), + ]) # fmt:skip + + ds_denoised_bold = pe.Node( + DerivativesDataSink( + compress=True, + desc='denoised', + suffix='bold', + ), + name='ds_denoised_bold', + run_without_submitting=True, + ) + workflow.connect([ + (retroglm, ds_denoised_bold, [ + ('denoised', 'in_file'), + ('denoised_json', 'meta_dict'), + ]), + ]) # fmt:skip + + return workflow + + +def init_rapidtide_confounds_wf( + *, + bold_file: str, + metadata: dict, + mem_gb: dict, +): + """Build a workflow that runs `Rapidtide`_. + + This workflow wraps `Rapidtide`_ to characterize and remove the traveling wave artifact. + + The following steps are performed: + + #. Remove non-steady state volumes from the bold series. + #. Run rapidtide + #. Collect rapidtide outputs + #. Generate a confounds file with the rapidtide outputs + + .. _Rapidtide: https://rapidtide.readthedocs.io/ + + Workflow Graph + .. workflow:: + :graph2use: orig + :simple_form: yes + + from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_fit_wf + + wf = init_rapidtide_fit_wf( + bold_file="fake.nii.gz", + metadata={"RepetitionTime": 1.0}, + ) + + Parameters + ---------- + bold_file + BOLD series used as name source for derivatives + metadata : :obj:`dict` + BIDS metadata for BOLD file + + Inputs + ------ + bold + BOLD series in template space + bold_mask + BOLD series mask in template space + dseg + Tissue segmentation in template space + confounds + fMRIPrep-formatted confounds file, which must include the following columns: + "trans_x", "trans_y", "trans_z", "rot_x", "rot_y", "rot_z". + skip_vols + number of non steady state volumes + + Outputs + ------- + denoised_bold + confounds_file + """ + + from niworkflows.engine.workflows import LiterateWorkflow as Workflow + + from fmripost_rapidtide.interfaces.bids import DerivativesDataSink + from fmripost_rapidtide.interfaces.rapidtide import RetroLagTCS + + workflow = Workflow(name=_get_wf_name(bold_file, 'denoise')) + workflow.__postdesc__ = """\ +Identification and removal of traveling wave artifacts was performed using rapidtide. +""" + + inputnode = pe.Node( + niu.IdentityInterface( + fields=[ + 'bold', + 'bold_mask', + 'delay_map', + 'regressor', + 'skip_vols', + ], + ), + name='inputnode', + ) + + # Remove the traveling wave artifact + retrolagtcs = pe.Node( + RetroLagTCS( + nprocs=config.nipype.omp_nthreads, + glmderivs=config.workflow.glmderivs, + ), + name='retrolagtcs', + mem_gb=mem_gb['filesize'] * 6, + n_procs=config.nipype.omp_nthreads, + ) + workflow.connect([ + (inputnode, retrolagtcs, [ + ('bold', 'in_file'), + ('bold_mask', 'maskfile'), + ('delay_map', 'lagtimesfile'), + ('regressor', 'lagtcgeneratorfile'), + ('skip_vols', 'numskip'), + ]), + ]) # fmt:skip + + ds_voxelwise_regressor = pe.Node( + DerivativesDataSink( + compress=True, + desc='sLFO', + suffix='timeseries', + ), + name='ds_voxelwise_regressor', + run_without_submitting=True, + ) + workflow.connect([ + (retrolagtcs, ds_voxelwise_regressor, [ + ('denoised', 'in_file'), + ('denoised_json', 'meta_dict'), + ]), + ]) # fmt:skip + + return workflow From 320718db6b4597ee74c458c648a44d878f7401a4 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 13:46:32 -0400 Subject: [PATCH 18/31] Fix some stuff up. --- .../interfaces/rapidtide.py | 67 ++++++++++--------- src/fmripost_rapidtide/workflows/rapidtide.py | 3 +- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/fmripost_rapidtide/interfaces/rapidtide.py b/src/fmripost_rapidtide/interfaces/rapidtide.py index a33e8d9..71c2dfd 100644 --- a/src/fmripost_rapidtide/interfaces/rapidtide.py +++ b/src/fmripost_rapidtide/interfaces/rapidtide.py @@ -255,7 +255,7 @@ class _RapidtideInputSpec(CommandLineInputSpec): mandatory=False, ) nprocs = traits.Int( - default=1, + 1, usedefault=True, argstr='--nprocs %d', mandatory=False, @@ -333,14 +333,14 @@ class _RetroLagTCSInputSpec(CommandLineInputSpec): desc='Output root.', ) glmderivs = traits.Int( + 0, argstr='--glmderivs %d', mandatory=False, desc='When doing final GLM, include derivatives up to NDERIVS order. Default is 0.', - default=0, usedefault=True, ) nprocs = traits.Int( - default=1, + 1, usedefault=True, argstr='--nprocs %d', mandatory=False, @@ -350,23 +350,23 @@ class _RetroLagTCSInputSpec(CommandLineInputSpec): ), ) numskip = traits.Int( + 0, argstr='--numskip %d', - default=0, usedefault=True, mandatory=False, desc='Skip NUMSKIP points at the beginning of the fmri file.', ) noprogressbar = traits.Bool( + True, argstr='--noprogressbar', mandatory=False, - default=True, usedefault=True, desc='Will disable showing progress bars (helpful if stdout is going to a file).', ) debug = traits.Bool( + False, argstr='--debug', mandatory=False, - default=False, usedefault=True, desc='Output lots of helpful information.', ) @@ -419,34 +419,27 @@ class _RetroGLMInputSpec(CommandLineInputSpec): '(everything up to but not including the underscore.)' ), ) - runoptionsfile = File( - exists=True, - argstr='--runoptionsfile %s', - mandatory=False, - ) - procmaskfile = File( - exists=True, - argstr='--procmaskfile %s', - mandatory=False, - ) - corrmaskfile = File( - exists=True, - argstr='--corrmaskfile %s', + prefix = traits.Str( + argstr='--alternateoutput %s', mandatory=False, + genfile=True, + desc='Output name', ) - lagtimesfile = File( - exists=True, - argstr='--lagtimesfile %s', + glmderivs = traits.Int( + 0, + argstr='--glmderivs %d', + usedefault=True, mandatory=False, + desc='When doing final GLM, include derivatives up to NDERIVS order.', ) - lagtcgeneratorfile = File( - exists=True, - argstr='--lagtcgeneratorfile %s', + nprocs = traits.Int( + 1, + usedefault=True, + argstr='--nprocs %d', mandatory=False, ) - meanfile = File( - exists=True, - argstr='--meanfile %s', + numskip = traits.Int( + argstr='--numskip %d', mandatory=False, ) @@ -459,13 +452,25 @@ class _RetroGLMOutputSpec(TraitedSpec): class RetroGLM(CommandLine): """Run the retroglm command-line interface to denoise BOLD with existing rapidtide outputs.""" - _cmd = 'retroglm' + _cmd = 'retroglm --noprogressbar' input_spec = _RetroGLMInputSpec output_spec = _RetroGLMOutputSpec + def _gen_filename(self, name): + if name == 'prefix': + return os.path.join(os.getcwd(), 'retroglm') + + return None + def _list_outputs(self): outputs = self._outputs().get() + prefix_dir = self.inputs.prefix datafileroot = self.inputs.datafileroot - outputs['denoised'] = f'{datafileroot}_desc-lfofilterCleaned_bold.nii.gz' - outputs['denoised_json'] = f'{datafileroot}_desc-lfofilterCleaned_bold.json' + file_prefix = os.path.basename(datafileroot) + outputs['denoised'] = os.path.join( + prefix_dir, f'{file_prefix}_desc-lfofilterCleaned_bold.nii.gz' + ) + outputs['denoised_json'] = os.path.join( + prefix_dir, f'{file_prefix}_desc-lfofilterCleaned_bold.json' + ) return outputs diff --git a/src/fmripost_rapidtide/workflows/rapidtide.py b/src/fmripost_rapidtide/workflows/rapidtide.py index 970f936..963921d 100644 --- a/src/fmripost_rapidtide/workflows/rapidtide.py +++ b/src/fmripost_rapidtide/workflows/rapidtide.py @@ -310,6 +310,7 @@ def init_rapidtide_denoise_wf( retroglm = pe.Node( RetroGLM( nprocs=config.nipype.omp_nthreads, + glmderivs=config.workflow.glmderivs, ), name='retroglm', mem_gb=mem_gb['filesize'] * 6, @@ -424,7 +425,7 @@ def init_rapidtide_confounds_wf( name='inputnode', ) - # Remove the traveling wave artifact + # Generate the traveling wave artifact voxel-wise regressor retrolagtcs = pe.Node( RetroLagTCS( nprocs=config.nipype.omp_nthreads, From db47088abf91539b3e331a599661b5dbfb2364bf Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 13:50:49 -0400 Subject: [PATCH 19/31] Minor fix. --- src/fmripost_rapidtide/tests/test_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fmripost_rapidtide/tests/test_base.py b/src/fmripost_rapidtide/tests/test_base.py index a36c2e5..52d5c58 100644 --- a/src/fmripost_rapidtide/tests/test_base.py +++ b/src/fmripost_rapidtide/tests/test_base.py @@ -6,7 +6,7 @@ def test_init_rapidtide_wf(tmp_path_factory): - from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_wf + from fmripost_rapidtide.workflows.base import init_rapidtide_wf tempdir = tmp_path_factory.mktemp('test_init_rapidtide_wf') From a7068a5f230e94c2c3e256fd4f490cf3331bda58 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 14:11:59 -0400 Subject: [PATCH 20/31] Update rapidtide version. --- pyproject.toml | 2 +- src/fmripost_rapidtide/tests/test_utils_bids.py | 8 ++++---- "\342\200\216tests/data/ds000005-fmriprep" | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 "\342\200\216tests/data/ds000005-fmriprep" diff --git a/pyproject.toml b/pyproject.toml index 6d2ab73..ada8a8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ "nitransforms == 23.0.1", "niworkflows @ git+https://github.com/nipreps/niworkflows.git@master", "pybids >= 0.15.6", - "rapidtide == 2.9.5.2", + "rapidtide == 2.9.8", "sdcflows @ git+https://github.com/nipreps/sdcflows.git@master", "smriprep @ git+https://github.com/nipreps/smriprep.git@master", "typer", diff --git a/src/fmripost_rapidtide/tests/test_utils_bids.py b/src/fmripost_rapidtide/tests/test_utils_bids.py index 5fb4040..89fa0a6 100644 --- a/src/fmripost_rapidtide/tests/test_utils_bids.py +++ b/src/fmripost_rapidtide/tests/test_utils_bids.py @@ -80,8 +80,8 @@ def test_collect_derivatives_minimal(minimal_ignore_list): # TODO: Add bold_mask_native to the dataset # 'bold_mask_native': 'sub-01_task-mixedgamblestask_run-01_desc-brain_mask.nii.gz', 'bold_mask_native': None, - 'confounds': None, - 'hmc': ( + 'bold_confounds': None, + 'bold_hmc': ( 'sub-01_task-mixedgamblestask_run-01_from-orig_to-boldref_mode-image_desc-hmc_xfm.txt' ), 'boldref2anat': ( @@ -123,8 +123,8 @@ def test_collect_derivatives_full(full_ignore_list): 'mask.nii.gz' ), 'bold_mask_native': None, - 'confounds': 'sub-01_task-mixedgamblestask_run-01_desc-confounds_timeseries.tsv', - 'hmc': ( + 'bold_confounds': 'sub-01_task-mixedgamblestask_run-01_desc-confounds_timeseries.tsv', + 'bold_hmc': ( 'sub-01_task-mixedgamblestask_run-01_from-orig_to-boldref_mode-image_desc-hmc_xfm.txt' ), 'boldref2anat': ( diff --git "a/\342\200\216tests/data/ds000005-fmriprep" "b/\342\200\216tests/data/ds000005-fmriprep" new file mode 100644 index 0000000..1bd947b --- /dev/null +++ "b/\342\200\216tests/data/ds000005-fmriprep" @@ -0,0 +1 @@ +Subproject commit caeb5acc567ddc94846bfa9e801088a7be3085c0 From 4abb7e2115fc6aaa89c6354a5a575373f113fcfa Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 14:13:24 -0400 Subject: [PATCH 21/31] Fix? --- .../data/ds000005-fmriprep" => tests/data/ds000005-fmriprep | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename "\342\200\216tests/data/ds000005-fmriprep" => tests/data/ds000005-fmriprep (100%) diff --git "a/\342\200\216tests/data/ds000005-fmriprep" b/tests/data/ds000005-fmriprep similarity index 100% rename from "\342\200\216tests/data/ds000005-fmriprep" rename to tests/data/ds000005-fmriprep From d4db0b5c21977b6ce320e6cedb8ee9dee9af2e23 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 14:18:04 -0400 Subject: [PATCH 22/31] update --- src/fmripost_rapidtide/tests/test_base.py | 10 ++++------ src/fmripost_rapidtide/workflows/base.py | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/fmripost_rapidtide/tests/test_base.py b/src/fmripost_rapidtide/tests/test_base.py index 52d5c58..34b61cf 100644 --- a/src/fmripost_rapidtide/tests/test_base.py +++ b/src/fmripost_rapidtide/tests/test_base.py @@ -5,19 +5,17 @@ from fmripost_rapidtide import config -def test_init_rapidtide_wf(tmp_path_factory): - from fmripost_rapidtide.workflows.base import init_rapidtide_wf +def test_init_rapidtide_fit_wf(tmp_path_factory): + from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_fit_wf - tempdir = tmp_path_factory.mktemp('test_init_rapidtide_wf') + tempdir = tmp_path_factory.mktemp('test_init_rapidtide_fit_wf') with mock_config(): config.execution.output_dir = tempdir / 'out' config.execution.work_dir = tempdir / 'work' - config.workflow.denoise_method = ['nonaggr', 'orthaggr'] - config.workflow.melodic_dim = -200 config.workflow.err_on_warn = False - wf = init_rapidtide_wf( + wf = init_rapidtide_fit_wf( bold_file='sub-01_task-rest_bold.nii.gz', metadata={'RepetitionTime': 2.0}, mem_gb={ diff --git a/src/fmripost_rapidtide/workflows/base.py b/src/fmripost_rapidtide/workflows/base.py index b43e404..1864e5d 100644 --- a/src/fmripost_rapidtide/workflows/base.py +++ b/src/fmripost_rapidtide/workflows/base.py @@ -329,7 +329,7 @@ def init_fit_single_run_wf(*, bold_file): from fmripost_rapidtide.interfaces.misc import ApplyTransforms from fmripost_rapidtide.utils.bids import collect_derivatives, extract_entities from fmripost_rapidtide.workflows.outputs import init_func_fit_reports_wf - from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_wf + from fmripost_rapidtide.workflows.rapidtide import init_rapidtide_fit_wf spaces = config.workflow.spaces omp_nthreads = config.nipype.omp_nthreads @@ -410,7 +410,7 @@ def init_fit_single_run_wf(*, bold_file): skip_vols = get_nss(functional_cache['confounds']) # Run rapidtide - rapidtide_wf = init_rapidtide_wf(bold_file=bold_file, metadata=bold_metadata, mem_gb=mem_gb) + rapidtide_wf = init_rapidtide_fit_wf(bold_file=bold_file, metadata=bold_metadata, mem_gb=mem_gb) rapidtide_wf.inputs.inputnode.confounds = functional_cache['confounds'] rapidtide_wf.inputs.inputnode.skip_vols = skip_vols From 743affb96a0715ddb423fffcd15f4b565ea1e1da Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 14:35:00 -0400 Subject: [PATCH 23/31] [DATALAD] Added subdataset --- .gitmodules | 4 ++-- ds000005-fmriprep | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 160000 ds000005-fmriprep diff --git a/.gitmodules b/.gitmodules index 6f548f1..b37aea9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,5 +1,5 @@ -[submodule "tests/data/ds000005-fmriprep"] - path = tests/data/ds000005-fmriprep +[submodule "ds000005-fmriprep"] + path = ds000005-fmriprep url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep datalad-id = 92e566e4-1c80-434e-a4d7-5bf9fb483d76 datalad-url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep diff --git a/ds000005-fmriprep b/ds000005-fmriprep new file mode 160000 index 0000000..caeb5ac --- /dev/null +++ b/ds000005-fmriprep @@ -0,0 +1 @@ +Subproject commit caeb5acc567ddc94846bfa9e801088a7be3085c0 From 7832f033a0f4d729c38007a0e5f51872d45ad925 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 14:36:41 -0400 Subject: [PATCH 24/31] [DATALAD] Added subdataset --- .gitmodules | 2 ++ tests/data/ds000005-fmriprep | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 tests/data/ds000005-fmriprep diff --git a/.gitmodules b/.gitmodules index b37aea9..2928762 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,3 +3,5 @@ url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep datalad-id = 92e566e4-1c80-434e-a4d7-5bf9fb483d76 datalad-url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep +[submodule "tests/data"] + datalad-url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep diff --git a/tests/data/ds000005-fmriprep b/tests/data/ds000005-fmriprep deleted file mode 100644 index 1bd947b..0000000 --- a/tests/data/ds000005-fmriprep +++ /dev/null @@ -1 +0,0 @@ -Subproject commit caeb5acc567ddc94846bfa9e801088a7be3085c0 From 6fb425bf1158e1b833aaf339d637315daaec26bf Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 14:38:04 -0400 Subject: [PATCH 25/31] [DATALAD] Added subdataset --- .gitmodules | 5 +++++ tests/data/ds000005-fmriprep | 1 + 2 files changed, 6 insertions(+) create mode 160000 tests/data/ds000005-fmriprep diff --git a/.gitmodules b/.gitmodules index 2928762..c2efccb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,3 +5,8 @@ datalad-url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep [submodule "tests/data"] datalad-url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep +[submodule "tests/data/ds000005-fmriprep"] + path = tests/data/ds000005-fmriprep + url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep + datalad-id = 92e566e4-1c80-434e-a4d7-5bf9fb483d76 + datalad-url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep diff --git a/tests/data/ds000005-fmriprep b/tests/data/ds000005-fmriprep new file mode 160000 index 0000000..caeb5ac --- /dev/null +++ b/tests/data/ds000005-fmriprep @@ -0,0 +1 @@ +Subproject commit caeb5acc567ddc94846bfa9e801088a7be3085c0 From 5c027dacffc8d6b815be73d21acd88257a101ea5 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 14:38:25 -0400 Subject: [PATCH 26/31] Update. --- ds000005-fmriprep | 1 - src/fmripost_rapidtide/workflows/base.py | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) delete mode 160000 ds000005-fmriprep diff --git a/ds000005-fmriprep b/ds000005-fmriprep deleted file mode 160000 index caeb5ac..0000000 --- a/ds000005-fmriprep +++ /dev/null @@ -1 +0,0 @@ -Subproject commit caeb5acc567ddc94846bfa9e801088a7be3085c0 diff --git a/src/fmripost_rapidtide/workflows/base.py b/src/fmripost_rapidtide/workflows/base.py index 1864e5d..6ccce51 100644 --- a/src/fmripost_rapidtide/workflows/base.py +++ b/src/fmripost_rapidtide/workflows/base.py @@ -410,7 +410,11 @@ def init_fit_single_run_wf(*, bold_file): skip_vols = get_nss(functional_cache['confounds']) # Run rapidtide - rapidtide_wf = init_rapidtide_fit_wf(bold_file=bold_file, metadata=bold_metadata, mem_gb=mem_gb) + rapidtide_wf = init_rapidtide_fit_wf( + bold_file=bold_file, + metadata=bold_metadata, + mem_gb=mem_gb, + ) rapidtide_wf.inputs.inputnode.confounds = functional_cache['confounds'] rapidtide_wf.inputs.inputnode.skip_vols = skip_vols From 387ea9177ce3d664801ea58fca4ced061282efdf Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 14:38:51 -0400 Subject: [PATCH 27/31] Remove old submodules. --- .gitmodules | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitmodules b/.gitmodules index c2efccb..6f548f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,10 +1,3 @@ -[submodule "ds000005-fmriprep"] - path = ds000005-fmriprep - url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep - datalad-id = 92e566e4-1c80-434e-a4d7-5bf9fb483d76 - datalad-url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep -[submodule "tests/data"] - datalad-url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep [submodule "tests/data/ds000005-fmriprep"] path = tests/data/ds000005-fmriprep url = https://gin.g-node.org/nipreps-data/ds000005-fmriprep From b5cf819d5e182ed8e66662a9db34bdb45a19bcf4 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 15:20:53 -0400 Subject: [PATCH 28/31] Update. --- src/fmripost_rapidtide/tests/test_base.py | 3 +++ src/fmripost_rapidtide/tests/test_utils_bids.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/fmripost_rapidtide/tests/test_base.py b/src/fmripost_rapidtide/tests/test_base.py index 34b61cf..1f4b55e 100644 --- a/src/fmripost_rapidtide/tests/test_base.py +++ b/src/fmripost_rapidtide/tests/test_base.py @@ -14,6 +14,9 @@ def test_init_rapidtide_fit_wf(tmp_path_factory): config.execution.output_dir = tempdir / 'out' config.execution.work_dir = tempdir / 'work' config.workflow.err_on_warn = False + config.workflow.timerange = [-1 , -1] + config.workflow.simcalcrange = [-1, -1] + config.workflow.searchrange = [-30, 30] wf = init_rapidtide_fit_wf( bold_file='sub-01_task-rest_bold.nii.gz', diff --git a/src/fmripost_rapidtide/tests/test_utils_bids.py b/src/fmripost_rapidtide/tests/test_utils_bids.py index 89fa0a6..3de7dfe 100644 --- a/src/fmripost_rapidtide/tests/test_utils_bids.py +++ b/src/fmripost_rapidtide/tests/test_utils_bids.py @@ -148,4 +148,4 @@ def check_expected(subject_data, expected): for item, expected_item in zip(subject_data[key], value): assert os.path.basename(item) == expected_item else: - assert subject_data[key] is value + assert subject_data[key] is value, f'Key {key} is not {value}.' From 430a6764dbb4c8218bc6ac10d602d0d34e5abc45 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 15:29:45 -0400 Subject: [PATCH 29/31] Update. --- src/fmripost_rapidtide/tests/test_base.py | 2 +- src/fmripost_rapidtide/workflows/rapidtide.py | 49 +++++++++---------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/fmripost_rapidtide/tests/test_base.py b/src/fmripost_rapidtide/tests/test_base.py index 1f4b55e..e83ce8a 100644 --- a/src/fmripost_rapidtide/tests/test_base.py +++ b/src/fmripost_rapidtide/tests/test_base.py @@ -14,7 +14,7 @@ def test_init_rapidtide_fit_wf(tmp_path_factory): config.execution.output_dir = tempdir / 'out' config.execution.work_dir = tempdir / 'work' config.workflow.err_on_warn = False - config.workflow.timerange = [-1 , -1] + config.workflow.timerange = [-1, -1] config.workflow.simcalcrange = [-1, -1] config.workflow.searchrange = [-30, 30] diff --git a/src/fmripost_rapidtide/workflows/rapidtide.py b/src/fmripost_rapidtide/workflows/rapidtide.py index 963921d..ea98046 100644 --- a/src/fmripost_rapidtide/workflows/rapidtide.py +++ b/src/fmripost_rapidtide/workflows/rapidtide.py @@ -133,41 +133,40 @@ def init_rapidtide_fit_wf( # XXX: simcalcrange is converted to list of strings rapidtide = pe.Node( Rapidtide( - outputname='rapidtide', - datatstep=metadata['RepetitionTime'], + ampthresh=config.workflow.ampthresh, + autorespdelete=config.workflow.autorespdelete, autosync=config.workflow.autosync, + bipolar=config.workflow.bipolar, + confoundderiv=config.workflow.confoundderiv, + confoundfile=config.workflow.confoundfile or Undefined, + confoundpowers=config.workflow.confoundpowers, + convergencethresh=config.workflow.convergencethresh or Undefined, + corrweighting=config.workflow.corrweighting, + datatstep=metadata['RepetitionTime'], + detrendorder=config.workflow.detrendorder, filterband=config.workflow.filterband, filterfreqs=config.workflow.filterfreqs or Undefined, filterstopfreqs=config.workflow.filterstopfreqs or Undefined, - numnull=config.workflow.numnull, - detrendorder=config.workflow.detrendorder, - spatialfilt=config.workflow.spatialfilt, - confoundfile=config.workflow.confoundfile or Undefined, - confoundpowers=config.workflow.confoundpowers, - confoundderiv=config.workflow.confoundderiv, - globalsignalmethod=config.workflow.globalsignalmethod, + fixdelay=config.workflow.fixdelay or Undefined, + glmderivs=config.workflow.glmderivs, + glmsourcefile=config.workflow.glmsourcefile or Undefined, globalpcacomponents=config.workflow.globalpcacomponents, + globalsignalmethod=config.workflow.globalsignalmethod, + lagmaxthresh=config.workflow.lagmaxthresh, + lagminthresh=config.workflow.lagminthresh, + maxpasses=config.workflow.maxpasses, + nprocs=config.nipype.omp_nthreads, + numnull=config.workflow.numnull, numtozero=config.workflow.numtozero, - timerange=[int(float(i)) for i in config.workflow.timerange], - corrweighting=config.workflow.corrweighting, - simcalcrange=[int(float(i)) for i in config.workflow.simcalcrange], - fixdelay=config.workflow.fixdelay or Undefined, + outputlevel=config.workflow.outputlevel, + pcacomponents=config.workflow.pcacomponents, searchrange=[int(float(i)) for i in config.workflow.searchrange], sigmalimit=config.workflow.sigmalimit, - bipolar=config.workflow.bipolar, - lagminthresh=config.workflow.lagminthresh, - lagmaxthresh=config.workflow.lagmaxthresh, - ampthresh=config.workflow.ampthresh, sigmathresh=config.workflow.sigmathresh, - pcacomponents=config.workflow.pcacomponents, - convergencethresh=config.workflow.convergencethresh or Undefined, - maxpasses=config.workflow.maxpasses, - glmsourcefile=config.workflow.glmsourcefile or Undefined, - glmderivs=config.workflow.glmderivs, - outputlevel=config.workflow.outputlevel, + simcalcrange=[int(float(i)) for i in config.workflow.simcalcrange], + spatialfilt=config.workflow.spatialfilt, territorymap=config.workflow.territorymap or Undefined, - autorespdelete=config.workflow.autorespdelete, - nprocs=config.nipype.omp_nthreads, + timerange=[int(float(i)) for i in config.workflow.timerange], ), name='rapidtide', mem_gb=mem_gb['filesize'] * 6, From eec3bc8665f4027033f225dd79b08f507c0a021a Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 16:49:27 -0400 Subject: [PATCH 30/31] Update test_base.py --- src/fmripost_rapidtide/tests/test_base.py | 26 +++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/fmripost_rapidtide/tests/test_base.py b/src/fmripost_rapidtide/tests/test_base.py index e83ce8a..0fa57ca 100644 --- a/src/fmripost_rapidtide/tests/test_base.py +++ b/src/fmripost_rapidtide/tests/test_base.py @@ -14,9 +14,31 @@ def test_init_rapidtide_fit_wf(tmp_path_factory): config.execution.output_dir = tempdir / 'out' config.execution.work_dir = tempdir / 'work' config.workflow.err_on_warn = False - config.workflow.timerange = [-1, -1] - config.workflow.simcalcrange = [-1, -1] + config.workflow.ampthresh = -1.0 + config.workflow.autorespdelete = False + config.workflow.autosync = False + config.workflow.bipolar = False + config.workflow.confoundderiv = True + config.workflow.confoundpowers = 1 + config.workflow.corrweighting = 'regressor' + config.workflow.detrendorder = 1 + config.workflow.filterband = 'lfo' + config.workflow.glmderivs = 1 + config.workflow.globalpcacomponents = 0.8 + config.workflow.globalsignalmethod = 'sum' + config.workflow.lagmaxthresh = 5.0 + config.workflow.lagminthresh = 0.5 + config.workflow.maxpasses = 1 + config.workflow.numnull = 100 + config.workflow.numtozero = 0 + config.workflow.outputlevel = 'min' + config.workflow.pcacomponents = 0.8 + config.workflow.sigmalimit = 1000.0 config.workflow.searchrange = [-30, 30] + config.workflow.sigmathresh = 100.0 + config.workflow.simcalcrange = [-1, -1] + config.workflow.spatialfilt = 4.0 + config.workflow.timerange = [-1, -1] wf = init_rapidtide_fit_wf( bold_file='sub-01_task-rest_bold.nii.gz', From 22be22840dda451d17e66a40cb62d5efe19d0917 Mon Sep 17 00:00:00 2001 From: Taylor Salo Date: Wed, 9 Oct 2024 16:55:01 -0400 Subject: [PATCH 31/31] Update test_base.py --- src/fmripost_rapidtide/tests/test_base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fmripost_rapidtide/tests/test_base.py b/src/fmripost_rapidtide/tests/test_base.py index 0fa57ca..d3a8d12 100644 --- a/src/fmripost_rapidtide/tests/test_base.py +++ b/src/fmripost_rapidtide/tests/test_base.py @@ -39,6 +39,7 @@ def test_init_rapidtide_fit_wf(tmp_path_factory): config.workflow.simcalcrange = [-1, -1] config.workflow.spatialfilt = 4.0 config.workflow.timerange = [-1, -1] + config.nipype.omp_nthreads = 1 wf = init_rapidtide_fit_wf( bold_file='sub-01_task-rest_bold.nii.gz',