From 1f0b85cfc16d1703fda5dbdc1183d07a2e6db66f Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 18 Sep 2019 18:01:49 -0400 Subject: [PATCH] rf+enh: dcm2niix --- docker/generate_dockerfiles.sh | 3 +- nipype/info.py | 1 + nipype/interfaces/dcm2nii.py | 90 ++++++++++--------- nipype/interfaces/tests/test_auto_Dcm2niix.py | 1 + nipype/interfaces/tests/test_extra_dcm2nii.py | 8 +- 5 files changed, 57 insertions(+), 46 deletions(-) diff --git a/docker/generate_dockerfiles.sh b/docker/generate_dockerfiles.sh index 37fc5d338b..44173ee009 100755 --- a/docker/generate_dockerfiles.sh +++ b/docker/generate_dockerfiles.sh @@ -68,10 +68,11 @@ function generate_base_dockerfile() { --spm12 version=r7219 \ --env 'LD_LIBRARY_PATH=/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH' \ --freesurfer version=6.0.0-min \ + --dcm2niix version=v1.0.20190902 method=source \ --run 'echo "cHJpbnRmICJrcnp5c3p0b2YuZ29yZ29sZXdza2lAZ21haWwuY29tCjUxNzIKICpDdnVtdkVWM3pUZmcKRlM1Si8yYzFhZ2c0RQoiID4gL29wdC9mcmVlc3VyZmVyLTYuMC4wLW1pbi9saWNlbnNlLnR4dA==" | base64 -d | sh' \ --install afni ants apt-utils bzip2 convert3d file fsl-core \ fsl-mni152-templates fusefat g++ git graphviz make python ruby \ - unzip xvfb \ + unzip xvfb git-annex-standalone liblzma-dev \ --add-to-entrypoint "source /etc/fsl/fsl.sh && source /etc/afni/afni.sh" \ --env ANTSPATH='/usr/lib/ants' \ PATH='/usr/lib/ants:$PATH' \ diff --git a/nipype/info.py b/nipype/info.py index deec53aee5..313278b555 100644 --- a/nipype/info.py +++ b/nipype/info.py @@ -182,6 +182,7 @@ def get_nipype_gitversion(): ] EXTRA_REQUIRES = { + 'data': ['datalad'], 'doc': ['Sphinx>=1.4', 'numpydoc', 'matplotlib', 'pydotplus', 'pydot>=1.2.3'], 'duecredit': ['duecredit'], 'nipy': ['nitime', 'nilearn<0.5.0', 'dipy', 'nipy', 'matplotlib'], diff --git a/nipype/interfaces/dcm2nii.py b/nipype/interfaces/dcm2nii.py index 31885adaba..2635d8e78b 100644 --- a/nipype/interfaces/dcm2nii.py +++ b/nipype/interfaces/dcm2nii.py @@ -7,6 +7,8 @@ import os import re from copy import deepcopy +import itertools as it +from glob import iglob from ..utils.filemanip import split_filename from .base import (CommandLine, CommandLineInputSpec, InputMultiPath, traits, @@ -328,7 +330,7 @@ class Dcm2niixInputSpec(CommandLineInputSpec): False, argstr='-t', usedefault=True, - desc="Flag if text notes include private patient details") + desc="Text notes including private patient details") compression = traits.Enum( 1, 2, 3, 4, 5, 6, 7, 8, 9, argstr='-%d', @@ -346,6 +348,9 @@ class Dcm2niixInputSpec(CommandLineInputSpec): philips_float = traits.Bool( argstr='-p', desc="Philips precise float (not display) scaling") + to_nrrd = traits.Bool( + argstr="-e", + desc="Export as NRRD instead of NIfTI") class Dcm2niixOutputSpec(TraitedSpec): @@ -393,8 +398,11 @@ def version(self): return Info.version() def _format_arg(self, opt, spec, val): - bools = ['bids_format', 'merge_imgs', 'single_file', 'verbose', 'crop', - 'has_private', 'anon_bids', 'ignore_deriv', 'philips_float'] + bools = [ + 'bids_format', 'merge_imgs', 'single_file', 'verbose', 'crop', + 'has_private', 'anon_bids', 'ignore_deriv', 'philips_float', + 'to_nrrd', + ] if opt in bools: spec = deepcopy(spec) if val: @@ -410,52 +418,54 @@ def _run_interface(self, runtime): # may use return code 1 despite conversion runtime = super(Dcm2niix, self)._run_interface( runtime, correct_return_codes=(0, 1, )) - if self.inputs.bids_format: - (self.output_files, self.bvecs, self.bvals, - self.bids) = self._parse_stdout(runtime.stdout) - else: - (self.output_files, self.bvecs, self.bvals) = self._parse_stdout( - runtime.stdout) + self._parse_files(self._parse_stdout(runtime.stdout)) return runtime def _parse_stdout(self, stdout): - files = [] - bvecs = [] - bvals = [] - bids = [] - skip = False - find_b = False + filenames = [] for line in stdout.split("\n"): - if not skip: - out_file = None - if line.startswith("Convert "): # output - fname = str(re.search('\S+/\S+', line).group(0)) - out_file = os.path.abspath(fname) - # extract bvals - if find_b: - bvecs.append(out_file + ".bvec") - bvals.append(out_file + ".bval") - find_b = False - # next scan will have bvals/bvecs - elif 'DTI gradients' in line or 'DTI gradient directions' in line or 'DTI vectors' in line: - find_b = True - if out_file: - ext = '.nii' if self.inputs.compress == 'n' else '.nii.gz' - files.append(out_file + ext) - if self.inputs.bids_format: - bids.append(out_file + ".json") - skip = False - # just return what was done - if not bids: - return files, bvecs, bvals + if line.startswith("Convert "): # output + fname = str(re.search(r'\S+/\S+', line).group(0)) + filenames.append(os.path.abspath(fname)) + return filenames + + def _parse_files(self, filenames): + outfiles, bvals, bvecs, bids = [], [], [], [] + outtypes = [".bval", ".bvec", ".json", ".txt"] + if self.inputs.to_nrrd: + outtypes += [".nrrd", ".nhdr", ".raw.gz"] else: - return files, bvecs, bvals, bids + outtypes += [".nii", ".nii.gz"] + + for filename in filenames: + # search for relevant files, and sort accordingly + for fl in search_files(filename, outtypes): + if ( + fl.endswith(".nii") or + fl.endswith(".gz") or + fl.endswith(".nrrd") or + fl.endswith(".nhdr") + ): + outfiles.append(fl) + elif fl.endswith(".bval"): + bvals.append(fl) + elif fl.endswith(".bvec"): + bvecs.append(fl) + elif fl.endswith(".json") or fl.endswith(".txt"): + bids.append(fl) + self.output_files = outfiles + self.bvecs = bvecs + self.bvals = bvals + self.bids = bids def _list_outputs(self): outputs = self.output_spec().get() outputs['converted_files'] = self.output_files outputs['bvecs'] = self.bvecs outputs['bvals'] = self.bvals - if self.inputs.bids_format: - outputs['bids'] = self.bids + outputs['bids'] = self.bids return outputs + +# https://stackoverflow.com/a/4829130 +def search_files(prefix, outtypes): + return it.chain.from_iterable(iglob(prefix + outtype) for outtype in outtypes) diff --git a/nipype/interfaces/tests/test_auto_Dcm2niix.py b/nipype/interfaces/tests/test_auto_Dcm2niix.py index 5917f48583..729b9aa6db 100644 --- a/nipype/interfaces/tests/test_auto_Dcm2niix.py +++ b/nipype/interfaces/tests/test_auto_Dcm2niix.py @@ -61,6 +61,7 @@ def test_Dcm2niix_inputs(): position=-1, xor=['source_dir'], ), + to_nrrd=dict(argstr='-e', ), verbose=dict( argstr='-v', usedefault=True, diff --git a/nipype/interfaces/tests/test_extra_dcm2nii.py b/nipype/interfaces/tests/test_extra_dcm2nii.py index dd68454ad0..44fb7196f8 100644 --- a/nipype/interfaces/tests/test_extra_dcm2nii.py +++ b/nipype/interfaces/tests/test_extra_dcm2nii.py @@ -41,17 +41,15 @@ def assert_dwi(eg, bids): # ensure all outputs are of equal lengths assert len(set(map(len, outputs))) == 1 else: - assert not eg2.outputs.bids + assert not eg.outputs.bids dcm = Dcm2niix() dcm.inputs.source_dir = datadir dcm.inputs.out_filename = '%u%z' - eg1 = dcm.run() - assert_dwi(eg1, True) + assert_dwi(dcm.run(), True) # now run specifying output directory and removing BIDS option outdir = tmpdir.mkdir('conversion').strpath dcm.inputs.output_dir = outdir dcm.inputs.bids_format = False - eg2 = dcm.run() - assert_dwi(eg2, False) + assert_dwi(dcm.run(), False)