diff --git a/fmriprep/data/reports-spec.yml b/fmriprep/data/reports-spec.yml index af0b3f698..4552a20e3 100644 --- a/fmriprep/data/reports-spec.yml +++ b/fmriprep/data/reports-spec.yml @@ -85,11 +85,14 @@ sections: subtitle: Alignment of functional and anatomical MRI data (surface driven) - bids: {datatype: figures, desc: rois, suffix: bold} caption: Brain mask calculated on the BOLD signal (red contour), along with the - masks used for a/tCompCor.
The aCompCor mask (magenta contour) is a conservative - CSF and white-matter mask for extracting physiological and movement confounds. -
The fCompCor mask (blue contour) contains the top 5% most variable voxels - within a heavily-eroded brain-mask. - subtitle: Brain mask and (temporal/anatomical) CompCor ROIs + regions of interest (ROIs) used in a/tCompCor for extracting + physiological and movement confounding components.
+ The anatomical CompCor ROI (magenta contour) is a mask combining + CSF and WM (white-matter), where voxels containing a minimal partial volume + of GM have been removed.
+ The temporal CompCor ROI (blue contour) contains the top 2% most + variable voxels within the brain mask. + subtitle: Brain mask and (anatomical/temporal) CompCor ROIs - bids: datatype: figures desc: '[at]compcor' @@ -107,10 +110,11 @@ sections: in the BOLD data. Global signals calculated within the whole-brain (GS), within the white-matter (WM) and within cerebro-spinal fluid (CSF) show the mean BOLD signal in their corresponding masks. DVARS and FD show the standardized DVARS - and framewise-displacement measures for each time point.
A carpet plot - shows the time series for all voxels within the brain mask, or if ``--cifti-output`` - was enabled, all grayordinates. Voxels are grouped into cortical (dark/light blue), - and subcortical (orange) gray matter, cerebellum (green) and white matter and CSF + and framewise-displacement measures for each time point.
+ A carpet plot shows the time series for all voxels within the brain mask, + or if --cifti-output was enabled, all grayordinates. + Voxels are grouped into cortical (dark/light blue), and subcortical (orange) + gray matter, cerebellum (green) and white matter and CSF (red), indicated by the color map on the left-hand side. subtitle: BOLD Summary - bids: {datatype: figures, desc: 'confoundcorr', suffix: bold} diff --git a/fmriprep/interfaces/confounds.py b/fmriprep/interfaces/confounds.py index a4c8b863c..2a8a68cfe 100644 --- a/fmriprep/interfaces/confounds.py +++ b/fmriprep/interfaces/confounds.py @@ -18,12 +18,41 @@ from nipype.utils.filemanip import fname_presuffix from nipype.interfaces.base import ( traits, TraitedSpec, BaseInterfaceInputSpec, File, Directory, isdefined, - SimpleInterface + SimpleInterface, InputMultiObject, OutputMultiObject ) LOGGER = logging.getLogger('nipype.interface') +class _aCompCorMasksInputSpec(BaseInterfaceInputSpec): + in_vfs = InputMultiObject(File(exists=True), desc="Input volume fractions.") + is_aseg = traits.Bool(False, usedefault=True, + desc="Whether the input volume fractions come from FS' aseg.") + bold_zooms = traits.Tuple(traits.Float, traits.Float, traits.Float, mandatory=True, + desc="BOLD series zooms") + + +class _aCompCorMasksOutputSpec(TraitedSpec): + out_masks = OutputMultiObject(File(exists=True), + desc="CSF, WM and combined masks, respectively") + + +class aCompCorMasks(SimpleInterface): + """Generate masks in T1w space for aCompCor.""" + + input_spec = _aCompCorMasksInputSpec + output_spec = _aCompCorMasksOutputSpec + + def _run_interface(self, runtime): + from ..utils.confounds import acompcor_masks + self._results["out_masks"] = acompcor_masks( + self.inputs.in_vfs, + self.inputs.is_aseg, + self.inputs.bold_zooms, + ) + return runtime + + class GatherConfoundsInputSpec(BaseInterfaceInputSpec): signals = File(exists=True, desc='input signals') dvars = File(exists=True, desc='file containing DVARS') diff --git a/fmriprep/utils/confounds.py b/fmriprep/utils/confounds.py new file mode 100644 index 000000000..25a19980c --- /dev/null +++ b/fmriprep/utils/confounds.py @@ -0,0 +1,136 @@ +"""Utilities for confounds manipulation.""" + + +def mask2vf(in_file, zooms=None, out_file=None): + """ + Convert a binary mask on a volume fraction map. + + The algorithm simply applies a Gaussian filter with the kernel size scaled + by the zooms given as argument. + + """ + import numpy as np + import nibabel as nb + from scipy.ndimage import gaussian_filter + + img = nb.load(in_file) + imgzooms = np.array(img.header.get_zooms()[:3], dtype=float) + if zooms is None: + zooms = imgzooms + + zooms = np.array(zooms, dtype=float) + sigma = 0.5 * (zooms / imgzooms) + + data = gaussian_filter(img.get_fdata(dtype=np.float32), sigma=sigma) + + max_data = np.percentile(data[data > 0], 99) + data = np.clip(data / max_data, a_min=0, a_max=1) + + if out_file is None: + return data + + hdr = img.header.copy() + hdr.set_data_dtype(np.float32) + nb.Nifti1Image(data.astype(np.float32), img.affine, hdr).to_filename(out_file) + return out_file + + +def acompcor_masks(in_files, is_aseg=False, zooms=None): + """ + Generate aCompCor masks. + + This function selects the CSF partial volume map from the input, + and generates the WM and combined CSF+WM masks for aCompCor. + + The implementation deviates from Behzadi et al. + Their original implementation thresholded the CSF and the WM partial-volume + masks at 0.99 (i.e., 99% of the voxel volume is filled with a particular tissue), + and then binary eroded that 2 voxels: + + > Anatomical data were segmented into gray matter, white matter, + > and CSF partial volume maps using the FAST algorithm available + > in the FSL software package (Smith et al., 2004). Tissue partial + > volume maps were linearly interpolated to the resolution of the + > functional data series using AFNI (Cox, 1996). In order to form + > white matter ROIs, the white matter partial volume maps were + > thresholded at a partial volume fraction of 0.99 and then eroded by + > two voxels in each direction to further minimize partial voluming + > with gray matter. CSF voxels were determined by first thresholding + > the CSF partial volume maps at 0.99 and then applying a threedimensional + > nearest neighbor criteria to minimize multiple tissue + > partial voluming. Since CSF regions are typically small compared + > to white matter regions mask, erosion was not applied. + + This particular procedure is not generalizable to BOLD data with different voxel zooms + as the mathematical morphology operations will be scaled by those. + Also, from reading the excerpt above and the tCompCor description, I (@oesteban) + believe that they always operated slice-wise given the large slice-thickness of + their functional data. + + Instead, *fMRIPrep*'s implementation deviates from Behzadi's implementation on two + aspects: + + * the masks are prepared in high-resolution, anatomical space and then + projected into BOLD space; and, + * instead of using binary erosion, a dilated GM map is generated -- thresholding + the corresponding PV map at 0.05 (i.e., pixels containing at least 5% of GM tissue) + and then subtracting that map from the CSF, WM and CSF+WM (combined) masks. + This should be equivalent to eroding the masks, except that the erosion + only happens at direct interfaces with GM. + + When the probseg maps provene from FreeSurfer's ``recon-all`` (i.e., they are + discrete), binary maps are *transformed* into some sort of partial volume maps + by means of a Gaussian smoothing filter with sigma adjusted by the size of the + BOLD data. + + """ + from pathlib import Path + import numpy as np + import nibabel as nb + from scipy.ndimage import binary_dilation + from skimage.morphology import ball + + csf_file = in_files[2] # BIDS labeling (CSF=2; last of list) + # Load PV maps (fast) or segments (recon-all) + gm_vf = nb.load(in_files[0]) + wm_vf = nb.load(in_files[1]) + csf_vf = nb.load(csf_file) + + # Prepare target zooms + imgzooms = np.array(gm_vf.header.get_zooms()[:3], dtype=float) + if zooms is None: + zooms = imgzooms + zooms = np.array(zooms, dtype=float) + + if not is_aseg: + gm_data = gm_vf.get_fdata() > 0.05 + wm_data = wm_vf.get_fdata() + csf_data = csf_vf.get_fdata() + else: + csf_file = mask2vf( + csf_file, + zooms=zooms, + out_file=str(Path("acompcor_csf.nii.gz").absolute()), + ) + csf_data = nb.load(csf_file).get_fdata() + wm_data = mask2vf(in_files[1], zooms=zooms) + + # We do not have partial volume maps (recon-all route) + gm_data = np.asanyarray(gm_vf.dataobj, np.uint8) > 0 + + # Dilate the GM mask + gm_data = binary_dilation(gm_data, structure=ball(3)) + + # Output filenames + wm_file = str(Path("acompcor_wm.nii.gz").absolute()) + combined_file = str(Path("acompcor_wmcsf.nii.gz").absolute()) + + # Prepare WM mask + wm_data[gm_data] = 0 # Make sure voxel does not contain GM + nb.Nifti1Image(wm_data, gm_vf.affine, gm_vf.header).to_filename(wm_file) + + # Prepare combined CSF+WM mask + comb_data = csf_data + wm_data + comb_data[gm_data] = 0 # Make sure voxel does not contain GM + nb.Nifti1Image(comb_data, gm_vf.affine, gm_vf.header).to_filename(combined_file) + return [csf_file, wm_file, combined_file] diff --git a/fmriprep/workflows/bold/base.py b/fmriprep/workflows/bold/base.py index a8494ff74..fa11d60b5 100644 --- a/fmriprep/workflows/bold/base.py +++ b/fmriprep/workflows/bold/base.py @@ -349,6 +349,7 @@ def init_func_preproc_wf(bold_file): bold_confounds_wf = init_bold_confs_wf( mem_gb=mem_gb['largemem'], metadata=metadata, + freesurfer=freesurfer, regressors_all_comps=config.workflow.regressors_all_comps, regressors_fd_th=config.workflow.regressors_fd_th, regressors_dvars_th=config.workflow.regressors_dvars_th, diff --git a/fmriprep/workflows/bold/confounds.py b/fmriprep/workflows/bold/confounds.py index 3780248d6..2416ab567 100644 --- a/fmriprep/workflows/bold/confounds.py +++ b/fmriprep/workflows/bold/confounds.py @@ -27,6 +27,7 @@ def init_bold_confs_wf( regressors_all_comps, regressors_dvars_th, regressors_fd_th, + freesurfer=False, name="bold_confs_wf", ): """ @@ -126,6 +127,7 @@ def init_bold_confs_wf( from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms from niworkflows.interfaces.images import SignalExtraction from niworkflows.interfaces.masks import ROIsPlot + from niworkflows.interfaces.nibabel import ApplyMask, Binarize from niworkflows.interfaces.patches import ( RobustACompCor as ACompCor, RobustTCompCor as TCompCor, @@ -134,11 +136,17 @@ def init_bold_confs_wf( CompCorVariancePlot, ConfoundsCorrelationPlot ) from niworkflows.interfaces.utils import ( - TPM2ROI, AddTPMs, AddTSVHeader, TSV2JSON, DictMerge + AddTSVHeader, TSV2JSON, DictMerge + ) + from ...interfaces.confounds import aCompCorMasks + + gm_desc = ( + "dilating a GM mask extracted from the FreeSurfer's *aseg* segmentation" if freesurfer + else "thresholding the corresponding partial volume map at 0.05" ) workflow = Workflow(name=name) - workflow.__desc__ = """\ + workflow.__desc__ = f"""\ Several confounding time-series were calculated based on the *preprocessed BOLD*: framewise displacement (FD), DVARS and three region-wise global signals. @@ -155,15 +163,18 @@ def init_bold_confs_wf( *preprocessed BOLD* time-series (using a discrete cosine filter with 128s cut-off) for the two *CompCor* variants: temporal (tCompCor) and anatomical (aCompCor). -tCompCor components are then calculated from the top 5% variable -voxels within a mask covering the subcortical regions. -This subcortical mask is obtained by heavily eroding the brain mask, -which ensures it does not include cortical GM regions. -For aCompCor, components are calculated within the intersection of -the aforementioned mask and the union of CSF and WM masks calculated -in T1w space, after their projection to the native space of each -functional run (using the inverse BOLD-to-T1w transformation). Components -are also calculated separately within the WM and CSF masks. +tCompCor components are then calculated from the top 2% variable +voxels within the brain mask. +For aCompCor, three probabilistic masks (CSF, WM and combined CSF+WM) +are generated in anatomical space. +The implementation differs from that of Behzadi et al. in that instead +of eroding the masks by 2 pixels on BOLD space, the aCompCor masks are +subtracted a mask of pixels that likely contain a volume fraction of GM. +This mask is obtained by {gm_desc}, and it ensures components are not extracted +from voxels containing a minimal fraction of GM. +Finally, these masks are resampled into BOLD space and binarized by +thresholding at 0.99 (as in the original implementation). +Components are also calculated separately within the WM and CSF masks. For each CompCor decomposition, the *k* components with the largest singular values are retained, such that the retained components' time series are sufficient to explain 50 percent of variance across the nuisance mask (CSF, @@ -174,9 +185,9 @@ def init_bold_confs_wf( The confound time series derived from head motion estimates and global signals were expanded with the inclusion of temporal derivatives and quadratic terms for each [@confounds_satterthwaite_2013]. -Frames that exceeded a threshold of {fd} mm FD or {dv} standardised DVARS -were annotated as motion outliers. -""".format(fd=regressors_fd_th, dv=regressors_dvars_th) +Frames that exceeded a threshold of {regressors_fd_th} mm FD or +{regressors_dvars_th} standardised DVARS were annotated as motion outliers. +""" inputnode = pe.Node(niu.IdentityInterface( fields=['bold', 'bold_mask', 'movpar_file', 'rmsd_file', 'skip_vols', 't1w_mask', 't1w_tpms', 't1_bold_xform']), @@ -185,33 +196,6 @@ def init_bold_confs_wf( fields=['confounds_file', 'confounds_metadata']), name='outputnode') - # Get masks ready in T1w space - acc_tpm = pe.Node(AddTPMs(indices=[1, 2]), # BIDS convention (WM=1, CSF=2) - name='acc_tpm') # acc stands for aCompCor - csf_roi = pe.Node(TPM2ROI(erode_mm=0, mask_erode_mm=30), name='csf_roi') - wm_roi = pe.Node(TPM2ROI( - erode_prop=0.6, mask_erode_prop=0.6**3), # 0.6 = radius; 0.6^3 = volume - name='wm_roi') - acc_roi = pe.Node(TPM2ROI( - erode_prop=0.6, mask_erode_prop=0.6**3), # 0.6 = radius; 0.6^3 = volume - name='acc_roi') - - # Map ROIs in T1w space into BOLD space - csf_tfm = pe.Node(ApplyTransforms(interpolation='NearestNeighbor', float=True), - name='csf_tfm', mem_gb=0.1) - wm_tfm = pe.Node(ApplyTransforms(interpolation='NearestNeighbor', float=True), - name='wm_tfm', mem_gb=0.1) - acc_tfm = pe.Node(ApplyTransforms(interpolation='NearestNeighbor', float=True), - name='acc_tfm', mem_gb=0.1) - tcc_tfm = pe.Node(ApplyTransforms(interpolation='NearestNeighbor', float=True), - name='tcc_tfm', mem_gb=0.1) - - # Ensure ROIs don't go off-limits (reduced FoV) - csf_msk = pe.Node(niu.Function(function=_maskroi), name='csf_msk') - wm_msk = pe.Node(niu.Function(function=_maskroi), name='wm_msk') - acc_msk = pe.Node(niu.Function(function=_maskroi), name='acc_msk') - tcc_msk = pe.Node(niu.Function(function=_maskroi), name='tcc_msk') - # DVARS dvars = pe.Node(nac.ComputeDVARS(save_nstd=True, save_std=True, remove_zerovariance=True), name="dvars", mem_gb=mem_gb) @@ -220,21 +204,29 @@ def init_bold_confs_wf( fdisp = pe.Node(nac.FramewiseDisplacement(parameter_source="SPM"), name="fdisp", mem_gb=mem_gb) - # a/t-CompCor - mrg_lbl_cc = pe.Node(niu.Merge(3), name='merge_rois_cc', run_without_submitting=True) + # Generate aCompCor probseg maps + acc_masks = pe.Node(aCompCorMasks(is_aseg=freesurfer), name="acc_masks") + + # Resample probseg maps in BOLD space via T1w-to-BOLD transform + acc_msk_tfm = pe.MapNode(ApplyTransforms( + interpolation='Gaussian', float=False), iterfield=["input_image"], + name='acc_msk_tfm', mem_gb=0.1) + acc_msk_brain = pe.MapNode(ApplyMask(), name="acc_msk_brain", + iterfield=["in_file"]) + acc_msk_bin = pe.MapNode(Binarize(thresh_low=0.99), name='acc_msk_bin', + iterfield=["in_file"]) + acompcor = pe.Node( + ACompCor(components_file='acompcor.tsv', header_prefix='a_comp_cor_', pre_filter='cosine', + save_pre_filter=True, save_metadata=True, mask_names=['CSF', 'WM', 'combined'], + merge_method='none', failure_mode='NaN'), + name="acompcor", mem_gb=mem_gb) tcompcor = pe.Node( TCompCor(components_file='tcompcor.tsv', header_prefix='t_comp_cor_', pre_filter='cosine', - save_pre_filter=True, save_metadata=True, percentile_threshold=.05, + save_pre_filter=True, save_metadata=True, percentile_threshold=.02, failure_mode='NaN'), name="tcompcor", mem_gb=mem_gb) - acompcor = pe.Node( - ACompCor(components_file='acompcor.tsv', header_prefix='a_comp_cor_', pre_filter='cosine', - save_pre_filter=True, save_metadata=True, mask_names=['combined', 'CSF', 'WM'], - merge_method='none', failure_mode='NaN'), - name="acompcor", mem_gb=mem_gb) - # Set number of components if regressors_all_comps: acompcor.inputs.num_components = 'all' @@ -249,8 +241,11 @@ def init_bold_confs_wf( acompcor.inputs.repetition_time = metadata['RepetitionTime'] # Global and segment regressors + gs_select = pe.Node(niu.Select(index=[0, 1]), name="gs_select", + run_without_submitting=True) signals_class_labels = ["csf", "white_matter", "global_signal"] - mrg_lbl = pe.Node(niu.Merge(3), name='merge_rois', run_without_submitting=True) + merge_rois = pe.Node(niu.Merge(2, ravel_inputs=True), name='merge_rois', + run_without_submitting=True) signals = pe.Node(SignalExtraction(class_labels=signals_class_labels), name="signals", mem_gb=mem_gb) @@ -297,7 +292,8 @@ def init_bold_confs_wf( name='spike_regressors') # Generate reportlet (ROIs) - mrg_compcor = pe.Node(niu.Merge(2), name='merge_compcor', run_without_submitting=True) + mrg_compcor = pe.Node(niu.Merge(2, ravel_inputs=True), + name='mrg_compcor', run_without_submitting=True) rois_plot = pe.Node(ROIsPlot(colors=['b', 'magenta'], generate_report=True), name='rois_plot', mem_gb=mem_gb) @@ -327,67 +323,38 @@ def init_bold_confs_wf( name='ds_report_conf_corr', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) - def _pick_csf(files): - return files[2] # after smriprep#189, this is BIDS-compliant. - - def _pick_wm(files): - return files[1] # after smriprep#189, this is BIDS-compliant. + def _last(inlist): + return inlist[-1] workflow.connect([ - # Massage ROIs (in T1w space) - (inputnode, acc_tpm, [('t1w_tpms', 'in_files')]), - (inputnode, csf_roi, [(('t1w_tpms', _pick_csf), 'in_tpm'), - ('t1w_mask', 'in_mask')]), - (inputnode, wm_roi, [(('t1w_tpms', _pick_wm), 'in_tpm'), - ('t1w_mask', 'in_mask')]), - (inputnode, acc_roi, [('t1w_mask', 'in_mask')]), - (acc_tpm, acc_roi, [('out_file', 'in_tpm')]), - # Map ROIs to BOLD - (inputnode, csf_tfm, [('bold_mask', 'reference_image'), - ('t1_bold_xform', 'transforms')]), - (csf_roi, csf_tfm, [('roi_file', 'input_image')]), - (inputnode, wm_tfm, [('bold_mask', 'reference_image'), - ('t1_bold_xform', 'transforms')]), - (wm_roi, wm_tfm, [('roi_file', 'input_image')]), - (inputnode, acc_tfm, [('bold_mask', 'reference_image'), - ('t1_bold_xform', 'transforms')]), - (acc_roi, acc_tfm, [('roi_file', 'input_image')]), - (inputnode, tcc_tfm, [('bold_mask', 'reference_image'), - ('t1_bold_xform', 'transforms')]), - (csf_roi, tcc_tfm, [('eroded_mask', 'input_image')]), - # Mask ROIs with bold_mask - (inputnode, csf_msk, [('bold_mask', 'in_mask')]), - (inputnode, wm_msk, [('bold_mask', 'in_mask')]), - (inputnode, acc_msk, [('bold_mask', 'in_mask')]), - (inputnode, tcc_msk, [('bold_mask', 'in_mask')]), # connect inputnode to each non-anatomical confound node (inputnode, dvars, [('bold', 'in_file'), ('bold_mask', 'in_mask')]), (inputnode, fdisp, [('movpar_file', 'in_file')]), - # tCompCor - (inputnode, tcompcor, [('bold', 'realigned_file')]), - (inputnode, tcompcor, [('skip_vols', 'ignore_initial_volumes')]), - (tcc_tfm, tcc_msk, [('output_image', 'roi_file')]), - (tcc_msk, tcompcor, [('out', 'mask_files')]), - # aCompCor - (inputnode, acompcor, [('bold', 'realigned_file')]), - (inputnode, acompcor, [('skip_vols', 'ignore_initial_volumes')]), - (acc_tfm, acc_msk, [('output_image', 'roi_file')]), - (acc_msk, mrg_lbl_cc, [('out', 'in1')]), - (csf_msk, mrg_lbl_cc, [('out', 'in2')]), - (wm_msk, mrg_lbl_cc, [('out', 'in3')]), - (mrg_lbl_cc, acompcor, [('out', 'mask_files')]), + (inputnode, acompcor, [("bold", "realigned_file"), + ("skip_vols", "ignore_initial_volumes")]), + (inputnode, acc_masks, [("t1w_tpms", "in_vfs"), + (("bold", _get_zooms), "bold_zooms")]), + (inputnode, acc_msk_tfm, [("t1_bold_xform", "transforms"), + ("bold_mask", "reference_image")]), + (inputnode, acc_msk_brain, [("bold_mask", "in_mask")]), + (acc_masks, acc_msk_tfm, [("out_masks", "input_image")]), + (acc_msk_tfm, acc_msk_brain, [("output_image", "in_file")]), + (acc_msk_brain, acc_msk_bin, [("out_file", "in_file")]), + (acc_msk_bin, acompcor, [("out_file", "mask_files")]), + # tCompCor + (inputnode, tcompcor, [("bold", "realigned_file"), + ("skip_vols", "ignore_initial_volumes"), + ("bold_mask", "mask_files")]), # Global signals extraction (constrained by anatomy) (inputnode, signals, [('bold', 'in_file')]), - (csf_tfm, csf_msk, [('output_image', 'roi_file')]), - (csf_msk, mrg_lbl, [('out', 'in1')]), - (wm_tfm, wm_msk, [('output_image', 'roi_file')]), - (wm_msk, mrg_lbl, [('out', 'in2')]), - (inputnode, mrg_lbl, [('bold_mask', 'in3')]), - (mrg_lbl, signals, [('out', 'label_files')]), + (acc_msk_bin, gs_select, [('out_file', 'inlist')]), + (gs_select, merge_rois, [('out', 'in1')]), + (inputnode, merge_rois, [('bold_mask', 'in2')]), + (merge_rois, signals, [('out', 'label_files')]), # Collate computed confounds together (inputnode, add_motion_headers, [('movpar_file', 'in_file')]), @@ -421,7 +388,7 @@ def _pick_wm(files): (inputnode, rois_plot, [('bold', 'in_file'), ('bold_mask', 'in_mask')]), (tcompcor, mrg_compcor, [('high_variance_masks', 'in1')]), - (acc_msk, mrg_compcor, [('out', 'in2')]), + (acc_msk_bin, mrg_compcor, [(('out_file', _last), 'in2')]), (mrg_compcor, rois_plot, [('out', 'in_rois')]), (rois_plot, ds_report_bold_rois, [('out_report', 'in_file')]), (tcompcor, mrg_cc_metadata, [('metadata_file', 'in1')]), @@ -792,7 +759,6 @@ def _remove_volumes(bold_file, skip_vols): bold_img = nb.load(bold_file) bold_img.__class__(bold_img.dataobj[..., skip_vols:], bold_img.affine, bold_img.header).to_filename(out) - return out @@ -813,21 +779,9 @@ def _add_volumes(bold_file, bold_cut_file, skip_vols): out = fname_presuffix(bold_cut_file, suffix='_addnonsteady') bold_img.__class__(bold_data, bold_img.affine, bold_img.header).to_filename(out) - return out -def _maskroi(in_mask, roi_file): - import numpy as np +def _get_zooms(in_file): import nibabel as nb - from nipype.utils.filemanip import fname_presuffix - - roi = nb.load(roi_file) - roidata = roi.get_data().astype(np.uint8) - msk = nb.load(in_mask).get_data().astype(bool) - roidata[~msk] = 0 - roi.set_data_dtype(np.uint8) - - out = fname_presuffix(roi_file, suffix='_boldmsk') - roi.__class__(roidata, roi.affine, roi.header).to_filename(out) - return out + return tuple(nb.load(in_file).header.get_zooms()[:3])