Repository: nipy/nipype

Issue:
FSL (+6.0) BET chokes with long filenames
Would adding the copyfile metadata just fix this, @satra?

I think I've seen this issue reported before, but I can't seem to find it. Feel free to link and close this if it is a duplicate

Comments:

Patch:
diff --git a/nipype/interfaces/afni/__init__.py b/nipype/interfaces/afni/__init__.py
index d5f2bb436..3629090ac 100644
--- a/nipype/interfaces/afni/__init__.py
+++ b/nipype/interfaces/afni/__init__.py
@@ -29,6 +29,7 @@ from .preprocess import (
     LFCD,
     Maskave,
     Means,
+    NetCorr,
     OutlierCount,
     QualityIndex,
     ROIStats,
diff --git a/nipype/interfaces/afni/preprocess.py b/nipype/interfaces/afni/preprocess.py
index 1d53aac98..ada49fc9a 100644
--- a/nipype/interfaces/afni/preprocess.py
+++ b/nipype/interfaces/afni/preprocess.py
@@ -2556,6 +2556,186 @@ class TCorrMap(AFNICommand):
             return super(TCorrMap, self)._format_arg(name, trait_spec, value)
 
 
+class NetCorrInputSpec(AFNICommandInputSpec):
+    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(
+        desc="output file name part",
+        name_template="%s_netcorr",
+        argstr="-prefix %s",
+        position=1,
+        name_source="in_file",
+    )
+
+class NetCorrOutputSpec(TraitedSpec):
+    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.
+
+    For complete details, see the `3dNetCorr Documentation
+    <https://afni.nimh.nih.gov/pub/dist/doc/program_help/3dNetCorr.html>`_.
+
+    Examples
+    --------
+    >>> from nipype.interfaces import afni
+    >>> ncorr = afni.NetCorr()
+    >>> ncorr.inputs.in_file = 'functional.nii'
+    >>> ncorr.inputs.mask = 'mask.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.out_file = 'sub0.tp1.ncorr'
+    >>> ncorr.cmdline
+    '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
+
+    """
+
+    _cmd = "3dNetCorr"
+    input_spec = NetCorrInputSpec
+    output_spec = NetCorrOutputSpec
+
+    def _list_outputs(self):
+        import glob
+        
+        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
+        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"))
+
+        return outputs
+
+
 class TCorrelateInputSpec(AFNICommandInputSpec):
     xset = File(
         desc="input xset",
diff --git a/nipype/interfaces/fsl/model.py b/nipype/interfaces/fsl/model.py
index e06377cb4..d7484c0f9 100644
--- a/nipype/interfaces/fsl/model.py
+++ b/nipype/interfaces/fsl/model.py
@@ -1886,7 +1886,7 @@ class SmoothEstimateInputSpec(FSLCommandInputSpec):
 class SmoothEstimateOutputSpec(TraitedSpec):
     dlh = traits.Float(desc="smoothness estimate sqrt(det(Lambda))")
     volume = traits.Int(desc="number of voxels in mask")
-    resels = traits.Float(desc="number of resels")
+    resels = traits.Float(desc="volume of resel, in voxels, defined as FWHM_x * FWHM_y * FWHM_z")
 
 
 class SmoothEstimate(FSLCommand):
diff --git a/nipype/interfaces/fsl/preprocess.py b/nipype/interfaces/fsl/preprocess.py
index f2fbe8c07..54a41be03 100644
--- a/nipype/interfaces/fsl/preprocess.py
+++ b/nipype/interfaces/fsl/preprocess.py
@@ -35,6 +35,7 @@ class BETInputSpec(FSLCommandInputSpec):
         argstr="%s",
         position=0,
         mandatory=True,
+        copyfile=False,
     )
     out_file = File(
         desc="name of output skull stripped image",
@@ -164,15 +165,27 @@ class BET(FSLCommand):
             self.raise_exception(runtime)
         return runtime
 
+    def _format_arg(self, name, spec, value):
+        formatted = super(BET, self)._format_arg(name, spec, value)
+        if name == "in_file":
+            # Convert to relative path to prevent BET failure
+            # with long paths.
+            return op.relpath(formatted, start=os.getcwd())
+        return formatted
+
     def _gen_outfilename(self):
         out_file = self.inputs.out_file
+        # Generate default output filename if non specified.
         if not isdefined(out_file) and isdefined(self.inputs.in_file):
             out_file = self._gen_fname(self.inputs.in_file, suffix="_brain")
-        return os.path.abspath(out_file)
+            # Convert to relative path to prevent BET failure
+            # with long paths.
+            return op.relpath(out_file, start=os.getcwd())
+        return out_file
 
     def _list_outputs(self):
         outputs = self.output_spec().get()
-        outputs["out_file"] = self._gen_outfilename()
+        outputs["out_file"] = os.path.abspath(self._gen_outfilename())
 
         basename = os.path.basename(outputs["out_file"])
         cwd = os.path.dirname(outputs["out_file"])
@@ -1309,10 +1322,7 @@ class FNIRT(FSLCommand):
 
             if key == "out_intensitymap_file" and isdefined(outputs[key]):
                 basename = FNIRT.intensitymap_file_basename(outputs[key])
-                outputs[key] = [
-                    outputs[key],
-                    "%s.txt" % basename,
-                ]
+                outputs[key] = [outputs[key], "%s.txt" % basename]
         return outputs
 
     def _format_arg(self, name, spec, value):


Test patch:
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 000000000..e613dc13e
--- /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
diff --git a/nipype/interfaces/fsl/tests/test_preprocess.py b/nipype/interfaces/fsl/tests/test_preprocess.py
index 438f3f0ec..6b1b6cb61 100644
--- a/nipype/interfaces/fsl/tests/test_preprocess.py
+++ b/nipype/interfaces/fsl/tests/test_preprocess.py
@@ -31,6 +31,9 @@ def setup_infile(tmpdir):
 @pytest.mark.skipif(no_fsl(), reason="fsl is not installed")
 def test_bet(setup_infile):
     tmp_infile, tp_dir = setup_infile
+    # BET converts the in_file path to be relative to prevent
+    # failure with long paths.
+    tmp_infile = os.path.relpath(tmp_infile, start=os.getcwd())
     better = fsl.BET()
     assert better.cmd == "bet"
 
@@ -41,8 +44,7 @@ def test_bet(setup_infile):
     # Test generated outfile name
     better.inputs.in_file = tmp_infile
     outfile = fsl_name(better, "foo_brain")
-    outpath = os.path.join(os.getcwd(), outfile)
-    realcmd = "bet %s %s" % (tmp_infile, outpath)
+    realcmd = "bet %s %s" % (tmp_infile, outfile)
     assert better.cmdline == realcmd
     # Test specified outfile name
     outfile = fsl_name(better, "/newdata/bar")
@@ -79,12 +81,11 @@ def test_bet(setup_infile):
     # test each of our arguments
     better = fsl.BET()
     outfile = fsl_name(better, "foo_brain")
-    outpath = os.path.join(os.getcwd(), outfile)
     for name, settings in list(opt_map.items()):
         better = fsl.BET(**{name: settings[1]})
         # Add mandatory input
         better.inputs.in_file = tmp_infile
-        realcmd = " ".join([better.cmd, tmp_infile, outpath, settings[0]])
+        realcmd = " ".join([better.cmd, tmp_infile, outfile, settings[0]])
         assert better.cmdline == realcmd