diff --git a/CHANGES b/CHANGES index 1de391fd9f..50337ab634 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Release 0.12.0-rc1 (April 20, 2016) ============ +* ENH: Added dcm2niix interface (https://github.com/nipy/nipype/pull/1435) * ENH: Add nipype_crash_search command (https://github.com/nipy/nipype/pull/1422) * ENH: Created interface for BrainSuite Cortical Surface Extraction command line tools (https://github.com/nipy/nipype/pull/1305) * FIX: job execution on systems/approaches where locale is undefined (https://github.com/nipy/nipype/pull/1401) diff --git a/nipype/interfaces/dcm2nii.py b/nipype/interfaces/dcm2nii.py index af99c4adb8..7897e6d951 100644 --- a/nipype/interfaces/dcm2nii.py +++ b/nipype/interfaces/dcm2nii.py @@ -23,21 +23,35 @@ class Dcm2niiInputSpec(CommandLineInputSpec): copyfile=False, mandatory=True, xor=['source_dir']) source_dir = Directory(exists=True, argstr="%s", position=-1, mandatory=True, xor=['source_names']) - anonymize = traits.Bool(True, argstr='-a', usedefault=True) - config_file = File(exists=True, argstr="-b %s", genfile=True) - collapse_folders = traits.Bool(True, argstr='-c', usedefault=True) - date_in_filename = traits.Bool(True, argstr='-d', usedefault=True) - events_in_filename = traits.Bool(True, argstr='-e', usedefault=True) - source_in_filename = traits.Bool(False, argstr='-f', usedefault=True) - gzip_output = traits.Bool(False, argstr='-g', usedefault=True) - id_in_filename = traits.Bool(False, argstr='-i', usedefault=True) - nii_output = traits.Bool(True, argstr='-n', usedefault=True) - output_dir = Directory(exists=True, argstr='-o %s', genfile=True) - protocol_in_filename = traits.Bool(True, argstr='-p', usedefault=True) - reorient = traits.Bool(argstr='-r') - spm_analyze = traits.Bool(argstr='-s', xor=['nii_output']) - convert_all_pars = traits.Bool(True, argstr='-v', usedefault=True) - reorient_and_crop = traits.Bool(False, argstr='-x', usedefault=True) + anonymize = traits.Bool(True, argstr='-a', usedefault=True, + desc="Remove identifying information") + config_file = File(exists=True, argstr="-b %s", genfile=True, + desc="Load settings from specified inifile") + collapse_folders = traits.Bool(True, argstr='-c', usedefault=True, + desc="Collapse input folders") + date_in_filename = traits.Bool(True, argstr='-d', usedefault=True, + desc="Date in filename") + events_in_filename = traits.Bool(True, argstr='-e', usedefault=True, + desc="Events (series/acq) in filename") + source_in_filename = traits.Bool(False, argstr='-f', usedefault=True, + desc="Source filename") + gzip_output = traits.Bool(False, argstr='-g', usedefault=True, + desc="Gzip output (.gz)") + id_in_filename = traits.Bool(False, argstr='-i', usedefault=True, + desc="ID in filename") + nii_output = traits.Bool(True, argstr='-n', usedefault=True, + desc="Save as .nii - if no, create .hdr/.img pair") + output_dir = Directory(exists=True, argstr='-o %s', genfile=True, + desc="Output dir - if unspecified, source directory is used") + protocol_in_filename = traits.Bool(True, argstr='-p', usedefault=True, + desc="Protocol in filename") + reorient = traits.Bool(argstr='-r', desc="Reorient image to nearest orthogonal") + spm_analyze = traits.Bool(argstr='-s', xor=['nii_output'], + desc="SPM2/Analyze not SPM5/NIfTI") + convert_all_pars = traits.Bool(True, argstr='-v', usedefault=True, + desc="Convert every image in directory") + reorient_and_crop = traits.Bool(False, argstr='-x', usedefault=True, + desc="Reorient and crop 3D images") class Dcm2niiOutputSpec(TraitedSpec): @@ -185,3 +199,125 @@ def _gen_filename(self, name): f.close() return config_file return None + + +class Dcm2niixInputSpec(CommandLineInputSpec): + source_names = InputMultiPath(File(exists=True), argstr="%s", position=-1, + copyfile=False, mandatory=True, xor=['source_dir']) + source_dir = Directory(exists=True, argstr="%s", position=-1, mandatory=True, + xor=['source_names']) + out_filename = traits.Str('%t%p', argstr="-f %s", usedefault=True, + desc="Output filename") + output_dir = Directory(exists=True, argstr='-o %s', genfile=True, + desc="Output directory") + bids_format = traits.Bool(True, argstr='-b', usedefault=True, + desc="Create a BIDS sidecar file") + compress = traits.Enum('i', ['y','i','n'], argstr='-z %s', usedefault=True, + desc="Gzip compress images - [y=pigz, i=internal, n=no]") + merge_imgs = traits.Bool(False, argstr='-m', usedefault=True, + desc="merge 2D slices from same series") + single_file = traits.Bool(False, argstr='-s', usedefault=True, + desc="Convert only one image (filename as last input") + verbose = traits.Bool(False, argstr='-v', usedefault=True, + desc="Verbose output") + + +class Dcm2niixOutputSpec(TraitedSpec): + converted_files = OutputMultiPath(File(exists=True)) + bvecs = OutputMultiPath(File(exists=True)) + bvals = OutputMultiPath(File(exists=True)) + bids = OutputMultiPath(File(exists=True)) + + +class Dcm2niix(CommandLine): + """Uses Chris Rorden's dcm2niix to convert dicom files + Examples + ======== + >>> from nipype.interfaces.dcm2nii import Dcm2niix + >>> converter = Dcm2niix() + >>> converter.inputs.source_names = ['functional_1.dcm', 'functional_2.dcm'] + >>> converter.inputs.compress = 'i' + >>> converter.inputs.single_file = True + >>> converter.inputs.output_dir = '.' + >>> converter.cmdline + 'dcm2niix -b y -z i -m n -f %t%p -o . -s y -v n functional_1.dcm' + """ + + input_spec = Dcm2niixInputSpec + output_spec = Dcm2niixOutputSpec + _cmd = 'dcm2niix' + + def _format_arg(self, opt, spec, val): + if opt in ['bids_format', 'merge_imgs', 'single_file', 'verbose']: + spec = deepcopy(spec) + if val: + spec.argstr += ' y' + else: + spec.argstr += ' n' + val = True + if opt == 'source_names': + return spec.argstr % val[0] + return super(Dcm2niix, self)._format_arg(opt, spec, val) + + def _run_interface(self, runtime): + new_runtime = super(Dcm2niix, self)._run_interface(runtime) + if self.inputs.bids_format: + (self.output_files, self.bvecs, + self.bvals, self.bids) = self._parse_stdout(new_runtime.stdout) + else: + (self.output_files, self.bvecs, + self.bvals) = self._parse_stdout(new_runtime.stdout) + return new_runtime + + def _parse_stdout(self, stdout): + files = [] + bvecs = [] + bvals = [] + bids = [] + skip = False + find_b = False + 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)) + if isdefined(self.inputs.output_dir): + output_dir = self.inputs.output_dir + else: + output_dir = self._gen_filename('output_dir') + out_file = os.path.abspath(os.path.join(output_dir, 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 gradient directions' in line: + find_b = True + else: + pass + if out_file: + files.append(out_file + ".nii.gz") + if self.inputs.bids_format: + bids.append(out_file + ".bids") + continue + skip = False + # just return what was done + if not bids: + return files, bvecs, bvals + else: + return files, bvecs, bvals, 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 + return outputs + + def _gen_filename(self, name): + if name == 'output_dir': + return os.getcwd() + return None