From a8ee022d6c3d0217aae9837147d16fad110f18db Mon Sep 17 00:00:00 2001 From: Ali Ghayoor Date: Wed, 21 Oct 2020 12:22:15 -0400 Subject: [PATCH 1/5] ENH: added afni NetCorr Afni 3dNetCorr is added to nipype. For more details, please see: https://afni.nimh.nih.gov/pub/dist/doc/program_help/3dNetCorr.html --- nipype/interfaces/afni/__init__.py | 1 + nipype/interfaces/afni/preprocess.py | 73 ++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/nipype/interfaces/afni/__init__.py b/nipype/interfaces/afni/__init__.py index d5f2bb4361..3629090ac0 100644 --- a/nipype/interfaces/afni/__init__.py +++ b/nipype/interfaces/afni/__init__.py @@ -29,6 +29,7 @@ LFCD, Maskave, Means, + NetCorr, OutlierCount, QualityIndex, ROIStats, diff --git a/nipype/interfaces/afni/preprocess.py b/nipype/interfaces/afni/preprocess.py index 1d53aac98c..03bee38ef2 100644 --- a/nipype/interfaces/afni/preprocess.py +++ b/nipype/interfaces/afni/preprocess.py @@ -2556,6 +2556,79 @@ def _format_arg(self, name, trait_spec, value): return super(TCorrMap, self)._format_arg(name, trait_spec, value) +class NetCorrInputSpec(AFNICommandInputSpec): + in_file = File(exists=True, argstr="-inset %s", mandatory=True) + in_rois = File(exists=True, argstr="-in_rois %s", mandatory=True) + mask = File(exists=True, argstr="-mask %s") + weight_ts = File(exists=True, argstr="-weight_ts %s") + fish_z = traits.Bool(argstr="-fish_z") + part_corr = traits.Bool(argstr="-part_corr") + ts_out = traits.Bool(argstr="-ts_out") + ts_label = traits.Bool(argstr="-ts_label") + ts_indiv = traits.Bool(argstr="-ts_indiv") + ts_wb_corr = traits.Bool(argstr="-ts_wb_corr") + ts_wb_Z = traits.Bool(argstr="-ts_wb_Z") + ts_wb_strlabel = traits.Bool(argstr="-ts_wb_strlabel") + nifti = traits.Bool(argstr="-nifti") + output_mask_nonnull = traits.Bool(argstr="-output_mask_nonnull") + push_thru_many_zeros = traits.Bool(argstr="-push_thru_many_zeros") + ignore_LT = traits.Bool(argstr="-ignore_LT") + out_file = File( + name_template="%s_netcorr", + desc="output file name part", + argstr="-prefix %s", + position=1, + name_source="in_file", + ) + +class NetCorrOutputSpec(TraitedSpec): + out_matrix = File(desc="output text file for correlation stats") + +class NetCorr(AFNICommand): + """Calculate correlation matrix of a set of ROIs (using mean time series of + each). Several networks may be analyzed simultaneously, one per brick. + + For complete details, see the `3dTcorrMap Documentation. + `_ + + Examples + -------- + >>> from nipype.interfaces import afni + >>> ncorr = afni.NetCorr() + >>> ncorr.inputs.in_file = 'functional.nii' + >>> ncorr.inputs.mask = 'mask.nii' + >>> ncorr.inputs.in_rois = 'rois.nii' + >>> ncorr.inputs.ts_wb_corr = True + >>> ncorr.inputs.ts_wb_Z = True + >>> ncorr.inputs.fish_z = True + >>> ncorr.inputs.prefix = 'sub0.tp1.ncorr' + >>> ncorr.cmdline # doctest: +SKIP + '3dNetCorr -prefix sub0.tp1.ncorr -inset functional.nii -mask mask.nii -in_rois rois.nii -ts_wb_corr -ts_wb_Z -fish_z' + >>> res = ncorr.run() # doctest: +SKIP + + """ + + _cmd = "3dNetCorr" + input_spec = NetCorrInputSpec + output_spec = NetCorrOutputSpec + + def _list_outputs(self): + outputs = self.output_spec().get() + + if not isdefined(self.inputs.out_file): + prefix = self._gen_fname(self.inputs.in_file, suffix="_netcorr") + else: + prefix = self.inputs.out_file + + # All outputs should be in the same directory as the prefix + out_dir = os.path.dirname(os.path.abspath(prefix)) + + outputs["out_matrix"] = ( + fname_presuffix(prefix, suffix="_000", use_ext=False, newpath=out_dir) + ".netcc" + ) + return outputs + + class TCorrelateInputSpec(AFNICommandInputSpec): xset = File( desc="input xset", From 6da8ee8865fa9b81a271884559769d042344f541 Mon Sep 17 00:00:00 2001 From: Ali Ghayoor Date: Mon, 1 Mar 2021 14:49:46 -0500 Subject: [PATCH 2/5] ENH: modified afni NetCorr development --- nipype/interfaces/afni/preprocess.py | 148 ++++++++++++++++++++++----- 1 file changed, 125 insertions(+), 23 deletions(-) diff --git a/nipype/interfaces/afni/preprocess.py b/nipype/interfaces/afni/preprocess.py index 03bee38ef2..9ca4c3948b 100644 --- a/nipype/interfaces/afni/preprocess.py +++ b/nipype/interfaces/afni/preprocess.py @@ -2557,25 +2557,127 @@ def _format_arg(self, name, trait_spec, value): class NetCorrInputSpec(AFNICommandInputSpec): - in_file = File(exists=True, argstr="-inset %s", mandatory=True) - in_rois = File(exists=True, argstr="-in_rois %s", mandatory=True) - mask = File(exists=True, argstr="-mask %s") - weight_ts = File(exists=True, argstr="-weight_ts %s") - fish_z = traits.Bool(argstr="-fish_z") - part_corr = traits.Bool(argstr="-part_corr") - ts_out = traits.Bool(argstr="-ts_out") - ts_label = traits.Bool(argstr="-ts_label") - ts_indiv = traits.Bool(argstr="-ts_indiv") - ts_wb_corr = traits.Bool(argstr="-ts_wb_corr") - ts_wb_Z = traits.Bool(argstr="-ts_wb_Z") - ts_wb_strlabel = traits.Bool(argstr="-ts_wb_strlabel") - nifti = traits.Bool(argstr="-nifti") - output_mask_nonnull = traits.Bool(argstr="-output_mask_nonnull") - push_thru_many_zeros = traits.Bool(argstr="-push_thru_many_zeros") - ignore_LT = traits.Bool(argstr="-ignore_LT") + in_file = File( + desc="input time series file (4D data set)", + exists=True, + argstr="-inset %s", + mandatory=True) + in_rois = File( + desc="input set of ROIs, each labelled with distinct integers", + exists=True, + argstr="-in_rois %s", + mandatory=True) + mask = File( + desc="can include a whole brain mask within which to " + "calculate correlation. Otherwise, data should be " + "masked already", + exists=True, + argstr="-mask %s") + weight_ts = File( + desc="input a 1D file WTS of weights that will be applied " + "multiplicatively to each ROI's average time series. " + "WTS can be a column- or row-file of values, but it " + "must have the same length as the input time series " + "volume. " + "If the initial average time series was A[n] for " + "n=0,..,(N-1) time points, then applying a set of " + "weights W[n] of the same length from WTS would " + "produce a new time series: B[n] = A[n] * W[n]", + exists=True, + argstr="-weight_ts %s") + fish_z = traits.Bool( + desc="switch to also output a matrix of Fisher Z-transform " + "values for the corr coefs (r): " + "Z = atanh(r) , " + "(with Z=4 being output along matrix diagonals where " + "r=1, as the r-to-Z conversion is ceilinged at " + "Z = atanh(r=0.999329) = 4, which is still *quite* a " + "high Pearson-r value", + argstr="-fish_z") + part_corr = traits.Bool( + desc="output the partial correlation matrix", + argstr="-part_corr") + ts_out = traits.Bool( + desc="switch to output the mean time series of the ROIs that " + "have been used to generate the correlation matrices. " + "Output filenames mirror those of the correlation " + "matrix files, with a '.netts' postfix", + argstr="-ts_out") + ts_label = traits.Bool( + desc="additional switch when using '-ts_out'. Using this " + "option will insert the integer ROI label at the start " + "of each line of the *.netts file created. Thus, for " + "a time series of length N, each line will have N+1 " + "numbers, where the first is the integer ROI label " + "and the subsequent N are scientific notation values", + argstr="-ts_label") + ts_indiv = traits.Bool( + desc="switch to create a directory for each network that " + "contains the average time series for each ROI in " + "individual files (each file has one line). " + "The directories are labelled PREFIX_000_INDIV/, " + "PREFIX_001_INDIV/, etc. (one per network). Within each " + "directory, the files are labelled ROI_001.netts, " + "ROI_002.netts, etc., with the numbers given by the " + "actual ROI integer labels", + argstr="-ts_indiv") + ts_wb_corr = traits.Bool( + desc="switch to create a set of whole brain correlation maps. " + "Performs whole brain correlation for each " + "ROI's average time series; this will automatically " + "create a directory for each network that contains the " + "set of whole brain correlation maps (Pearson 'r's). " + "The directories are labelled as above for '-ts_indiv' " + "Within each directory, the files are labelled " + "WB_CORR_ROI_001+orig, WB_CORR_ROI_002+orig, etc., with " + "the numbers given by the actual ROI integer labels", + argstr="-ts_wb_corr") + ts_wb_Z = traits.Bool( + desc="same as above in '-ts_wb_corr', except that the maps " + "have been Fisher transformed to Z-scores the relation: " + "Z=atanh(r). " + "To avoid infinities in the transform, Pearson values " + "are effectively capped at |r| = 0.999329 (where |Z| = 4.0). " + "Files are labelled WB_Z_ROI_001+orig, etc", + argstr="-ts_wb_Z") + ts_wb_strlabel = traits.Bool( + desc="by default, '-ts_wb_{corr,Z}' output files are named " + "using the int number of a given ROI, such as: " + "WB_Z_ROI_001+orig. " + "With this option, one can replace the int (such as '001') " + "with the string label (such as 'L-thalamus') " + "*if* one has a labeltable attached to the file", + argstr="-ts_wb_strlabel") + nifti = traits.Bool( + desc="output any correlation map files as NIFTI files " + "(default is BRIK/HEAD). Only useful if using " + "'-ts_wb_corr' and/or '-ts_wb_Z'", + argstr="-nifti") + output_mask_nonnull = traits.Bool( + desc="internally, this program checks for where there are " + "nonnull time series, because we don't like those, in " + "general. With this flag, the user can output the " + "determined mask of non-null time series.", + argstr="-output_mask_nonnull") + push_thru_many_zeros = traits.Bool( + desc="by default, this program will grind to a halt and " + "refuse to calculate if any ROI contains >10 percent " + "of voxels with null times series (i.e., each point is " + "0), as of April, 2017. This is because it seems most " + "likely that hidden badness is responsible. However, " + "if the user still wants to carry on the calculation " + "anyways, then this option will allow one to push on " + "through. However, if any ROI *only* has null time " + "series, then the program will not calculate and the " + "user will really, really, really need to address their masking", + argstr="-push_thru_many_zeros") + ignore_LT = traits.Bool( + desc="switch to ignore any label table labels in the " + "'-in_rois' file, if there are any labels attached", + argstr="-ignore_LT") out_file = File( - name_template="%s_netcorr", desc="output file name part", + name_template="%s_netcorr", argstr="-prefix %s", position=1, name_source="in_file", @@ -2588,8 +2690,8 @@ class NetCorr(AFNICommand): """Calculate correlation matrix of a set of ROIs (using mean time series of each). Several networks may be analyzed simultaneously, one per brick. - For complete details, see the `3dTcorrMap Documentation. - `_ + For complete details, see the `3dNetCorr Documentation + `_. Examples -------- @@ -2597,13 +2699,13 @@ class NetCorr(AFNICommand): >>> ncorr = afni.NetCorr() >>> ncorr.inputs.in_file = 'functional.nii' >>> ncorr.inputs.mask = 'mask.nii' - >>> ncorr.inputs.in_rois = 'rois.nii' + >>> ncorr.inputs.in_rois = 'maps.nii' >>> ncorr.inputs.ts_wb_corr = True >>> ncorr.inputs.ts_wb_Z = True >>> ncorr.inputs.fish_z = True - >>> ncorr.inputs.prefix = 'sub0.tp1.ncorr' - >>> ncorr.cmdline # doctest: +SKIP - '3dNetCorr -prefix sub0.tp1.ncorr -inset functional.nii -mask mask.nii -in_rois rois.nii -ts_wb_corr -ts_wb_Z -fish_z' + >>> ncorr.inputs.out_file = 'sub0.tp1.ncorr' + >>> ncorr.cmdline + '3dNetCorr -prefix sub0.tp1.ncorr -inset functional.nii -mask mask.nii -in_rois maps.nii -ts_wb_corr -ts_wb_Z -fish_z' >>> res = ncorr.run() # doctest: +SKIP """ From 380d798c1e7b5211add0577ccc31f39cc1afac39 Mon Sep 17 00:00:00 2001 From: Ali Ghayoor Date: Tue, 2 Mar 2021 15:40:56 -0500 Subject: [PATCH 3/5] ENH: AFNI NetCorr sorted commandline Co-authored-by: Chris Markiewicz --- nipype/interfaces/afni/preprocess.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nipype/interfaces/afni/preprocess.py b/nipype/interfaces/afni/preprocess.py index 9ca4c3948b..1b55e87e10 100644 --- a/nipype/interfaces/afni/preprocess.py +++ b/nipype/interfaces/afni/preprocess.py @@ -2705,7 +2705,7 @@ class NetCorr(AFNICommand): >>> ncorr.inputs.fish_z = True >>> ncorr.inputs.out_file = 'sub0.tp1.ncorr' >>> ncorr.cmdline - '3dNetCorr -prefix sub0.tp1.ncorr -inset functional.nii -mask mask.nii -in_rois maps.nii -ts_wb_corr -ts_wb_Z -fish_z' + '3dNetCorr -prefix sub0.tp1.ncorr -fish_z -inset functional.nii -in_rois maps.nii -mask mask.nii -ts_wb_Z -ts_wb_corr' >>> res = ncorr.run() # doctest: +SKIP """ From bfcfa94e3668ff57c5a7139dd76571e0cde429c8 Mon Sep 17 00:00:00 2001 From: Ali Ghayoor Date: Wed, 3 Mar 2021 19:12:36 -0500 Subject: [PATCH 4/5] ENH: added the correlation maps to the output afni NetCorr --- nipype/interfaces/afni/preprocess.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/nipype/interfaces/afni/preprocess.py b/nipype/interfaces/afni/preprocess.py index 1b55e87e10..ada49fc9ab 100644 --- a/nipype/interfaces/afni/preprocess.py +++ b/nipype/interfaces/afni/preprocess.py @@ -2684,8 +2684,9 @@ class NetCorrInputSpec(AFNICommandInputSpec): ) class NetCorrOutputSpec(TraitedSpec): - out_matrix = File(desc="output text file for correlation stats") - + out_corr_matrix = File(desc="output correlation matrix between ROIs written to a text file with .netcc suffix") + out_corr_maps = traits.List(File(), desc="output correlation maps in Pearson and/or Z-scores") + class NetCorr(AFNICommand): """Calculate correlation matrix of a set of ROIs (using mean time series of each). Several networks may be analyzed simultaneously, one per brick. @@ -2715,6 +2716,8 @@ class NetCorr(AFNICommand): output_spec = NetCorrOutputSpec def _list_outputs(self): + import glob + outputs = self.output_spec().get() if not isdefined(self.inputs.out_file): @@ -2723,11 +2726,13 @@ def _list_outputs(self): prefix = self.inputs.out_file # All outputs should be in the same directory as the prefix - out_dir = os.path.dirname(os.path.abspath(prefix)) + odir = os.path.dirname(os.path.abspath(prefix)) + outputs["out_corr_matrix"] = glob.glob(os.path.join(odir, "*.netcc"))[0] + + if isdefined(self.inputs.ts_wb_corr) or isdefined(self.inputs.ts_Z_corr): + corrdir = os.path.join(odir, prefix + "_000_INDIV") + outputs["out_corr_maps"] = glob.glob(os.path.join(corrdir, "*.nii.gz")) - outputs["out_matrix"] = ( - fname_presuffix(prefix, suffix="_000", use_ext=False, newpath=out_dir) + ".netcc" - ) return outputs From c00dd2fdce6b1ec09f2e97afd8a9276e5c8b3f25 Mon Sep 17 00:00:00 2001 From: Ali Ghayoor Date: Wed, 3 Mar 2021 21:27:58 -0500 Subject: [PATCH 5/5] ENH: added test_auto_NetCorr This file was automatically generated by running "make specs" --- .../afni/tests/test_auto_NetCorr.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 nipype/interfaces/afni/tests/test_auto_NetCorr.py diff --git a/nipype/interfaces/afni/tests/test_auto_NetCorr.py b/nipype/interfaces/afni/tests/test_auto_NetCorr.py new file mode 100644 index 0000000000..e613dc13eb --- /dev/null +++ b/nipype/interfaces/afni/tests/test_auto_NetCorr.py @@ -0,0 +1,99 @@ +# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT +from ..preprocess import NetCorr + + +def test_NetCorr_inputs(): + input_map = dict( + args=dict( + argstr="%s", + ), + environ=dict( + nohash=True, + usedefault=True, + ), + fish_z=dict( + argstr="-fish_z", + ), + ignore_LT=dict( + argstr="-ignore_LT", + ), + in_file=dict( + argstr="-inset %s", + extensions=None, + mandatory=True, + ), + in_rois=dict( + argstr="-in_rois %s", + extensions=None, + mandatory=True, + ), + mask=dict( + argstr="-mask %s", + extensions=None, + ), + nifti=dict( + argstr="-nifti", + ), + num_threads=dict( + nohash=True, + usedefault=True, + ), + out_file=dict( + argstr="-prefix %s", + extensions=None, + name_source="in_file", + name_template="%s_netcorr", + position=1, + ), + output_mask_nonnull=dict( + argstr="-output_mask_nonnull", + ), + outputtype=dict(), + part_corr=dict( + argstr="-part_corr", + ), + push_thru_many_zeros=dict( + argstr="-push_thru_many_zeros", + ), + ts_indiv=dict( + argstr="-ts_indiv", + ), + ts_label=dict( + argstr="-ts_label", + ), + ts_out=dict( + argstr="-ts_out", + ), + ts_wb_Z=dict( + argstr="-ts_wb_Z", + ), + ts_wb_corr=dict( + argstr="-ts_wb_corr", + ), + ts_wb_strlabel=dict( + argstr="-ts_wb_strlabel", + ), + weight_ts=dict( + argstr="-weight_ts %s", + extensions=None, + ), + ) + inputs = NetCorr.input_spec() + + for key, metadata in list(input_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(inputs.traits()[key], metakey) == value + + +def test_NetCorr_outputs(): + output_map = dict( + out_corr_maps=dict(), + out_corr_matrix=dict( + extensions=None, + ), + ) + outputs = NetCorr.output_spec() + + for key, metadata in list(output_map.items()): + for metakey, value in list(metadata.items()): + assert getattr(outputs.traits()[key], metakey) == value