Skip to content

Commit 6314fde

Browse files
authored
Merge pull request #2936 from mgxd/enh/dcm2niix
RF: dcm2niix interface
2 parents 500d8de + 1f0b85c commit 6314fde

File tree

5 files changed

+57
-46
lines changed

5 files changed

+57
-46
lines changed

docker/generate_dockerfiles.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ function generate_base_dockerfile() {
6868
--spm12 version=r7219 \
6969
--env 'LD_LIBRARY_PATH=/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH' \
7070
--freesurfer version=6.0.0-min \
71+
--dcm2niix version=v1.0.20190902 method=source \
7172
--run 'echo "cHJpbnRmICJrcnp5c3p0b2YuZ29yZ29sZXdza2lAZ21haWwuY29tCjUxNzIKICpDdnVtdkVWM3pUZmcKRlM1Si8yYzFhZ2c0RQoiID4gL29wdC9mcmVlc3VyZmVyLTYuMC4wLW1pbi9saWNlbnNlLnR4dA==" | base64 -d | sh' \
7273
--install afni ants apt-utils bzip2 convert3d file fsl-core \
7374
fsl-mni152-templates fusefat g++ git graphviz make python ruby \
74-
unzip xvfb \
75+
unzip xvfb git-annex-standalone liblzma-dev \
7576
--add-to-entrypoint "source /etc/fsl/fsl.sh && source /etc/afni/afni.sh" \
7677
--env ANTSPATH='/usr/lib/ants' \
7778
PATH='/usr/lib/ants:$PATH' \

nipype/info.py

+1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def get_nipype_gitversion():
182182
]
183183

184184
EXTRA_REQUIRES = {
185+
'data': ['datalad'],
185186
'doc': ['Sphinx>=1.4', 'numpydoc', 'matplotlib', 'pydotplus', 'pydot>=1.2.3'],
186187
'duecredit': ['duecredit'],
187188
'nipy': ['nitime', 'nilearn<0.5.0', 'dipy', 'nipy', 'matplotlib'],

nipype/interfaces/dcm2nii.py

+50-40
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import os
88
import re
99
from copy import deepcopy
10+
import itertools as it
11+
from glob import iglob
1012

1113
from ..utils.filemanip import split_filename
1214
from .base import (CommandLine, CommandLineInputSpec, InputMultiPath, traits,
@@ -328,7 +330,7 @@ class Dcm2niixInputSpec(CommandLineInputSpec):
328330
False,
329331
argstr='-t',
330332
usedefault=True,
331-
desc="Flag if text notes include private patient details")
333+
desc="Text notes including private patient details")
332334
compression = traits.Enum(
333335
1, 2, 3, 4, 5, 6, 7, 8, 9,
334336
argstr='-%d',
@@ -346,6 +348,9 @@ class Dcm2niixInputSpec(CommandLineInputSpec):
346348
philips_float = traits.Bool(
347349
argstr='-p',
348350
desc="Philips precise float (not display) scaling")
351+
to_nrrd = traits.Bool(
352+
argstr="-e",
353+
desc="Export as NRRD instead of NIfTI")
349354

350355

351356
class Dcm2niixOutputSpec(TraitedSpec):
@@ -393,8 +398,11 @@ def version(self):
393398
return Info.version()
394399

395400
def _format_arg(self, opt, spec, val):
396-
bools = ['bids_format', 'merge_imgs', 'single_file', 'verbose', 'crop',
397-
'has_private', 'anon_bids', 'ignore_deriv', 'philips_float']
401+
bools = [
402+
'bids_format', 'merge_imgs', 'single_file', 'verbose', 'crop',
403+
'has_private', 'anon_bids', 'ignore_deriv', 'philips_float',
404+
'to_nrrd',
405+
]
398406
if opt in bools:
399407
spec = deepcopy(spec)
400408
if val:
@@ -410,52 +418,54 @@ def _run_interface(self, runtime):
410418
# may use return code 1 despite conversion
411419
runtime = super(Dcm2niix, self)._run_interface(
412420
runtime, correct_return_codes=(0, 1, ))
413-
if self.inputs.bids_format:
414-
(self.output_files, self.bvecs, self.bvals,
415-
self.bids) = self._parse_stdout(runtime.stdout)
416-
else:
417-
(self.output_files, self.bvecs, self.bvals) = self._parse_stdout(
418-
runtime.stdout)
421+
self._parse_files(self._parse_stdout(runtime.stdout))
419422
return runtime
420423

421424
def _parse_stdout(self, stdout):
422-
files = []
423-
bvecs = []
424-
bvals = []
425-
bids = []
426-
skip = False
427-
find_b = False
425+
filenames = []
428426
for line in stdout.split("\n"):
429-
if not skip:
430-
out_file = None
431-
if line.startswith("Convert "): # output
432-
fname = str(re.search('\S+/\S+', line).group(0))
433-
out_file = os.path.abspath(fname)
434-
# extract bvals
435-
if find_b:
436-
bvecs.append(out_file + ".bvec")
437-
bvals.append(out_file + ".bval")
438-
find_b = False
439-
# next scan will have bvals/bvecs
440-
elif 'DTI gradients' in line or 'DTI gradient directions' in line or 'DTI vectors' in line:
441-
find_b = True
442-
if out_file:
443-
ext = '.nii' if self.inputs.compress == 'n' else '.nii.gz'
444-
files.append(out_file + ext)
445-
if self.inputs.bids_format:
446-
bids.append(out_file + ".json")
447-
skip = False
448-
# just return what was done
449-
if not bids:
450-
return files, bvecs, bvals
427+
if line.startswith("Convert "): # output
428+
fname = str(re.search(r'\S+/\S+', line).group(0))
429+
filenames.append(os.path.abspath(fname))
430+
return filenames
431+
432+
def _parse_files(self, filenames):
433+
outfiles, bvals, bvecs, bids = [], [], [], []
434+
outtypes = [".bval", ".bvec", ".json", ".txt"]
435+
if self.inputs.to_nrrd:
436+
outtypes += [".nrrd", ".nhdr", ".raw.gz"]
451437
else:
452-
return files, bvecs, bvals, bids
438+
outtypes += [".nii", ".nii.gz"]
439+
440+
for filename in filenames:
441+
# search for relevant files, and sort accordingly
442+
for fl in search_files(filename, outtypes):
443+
if (
444+
fl.endswith(".nii") or
445+
fl.endswith(".gz") or
446+
fl.endswith(".nrrd") or
447+
fl.endswith(".nhdr")
448+
):
449+
outfiles.append(fl)
450+
elif fl.endswith(".bval"):
451+
bvals.append(fl)
452+
elif fl.endswith(".bvec"):
453+
bvecs.append(fl)
454+
elif fl.endswith(".json") or fl.endswith(".txt"):
455+
bids.append(fl)
456+
self.output_files = outfiles
457+
self.bvecs = bvecs
458+
self.bvals = bvals
459+
self.bids = bids
453460

454461
def _list_outputs(self):
455462
outputs = self.output_spec().get()
456463
outputs['converted_files'] = self.output_files
457464
outputs['bvecs'] = self.bvecs
458465
outputs['bvals'] = self.bvals
459-
if self.inputs.bids_format:
460-
outputs['bids'] = self.bids
466+
outputs['bids'] = self.bids
461467
return outputs
468+
469+
# https://stackoverflow.com/a/4829130
470+
def search_files(prefix, outtypes):
471+
return it.chain.from_iterable(iglob(prefix + outtype) for outtype in outtypes)

nipype/interfaces/tests/test_auto_Dcm2niix.py

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def test_Dcm2niix_inputs():
6161
position=-1,
6262
xor=['source_dir'],
6363
),
64+
to_nrrd=dict(argstr='-e', ),
6465
verbose=dict(
6566
argstr='-v',
6667
usedefault=True,

nipype/interfaces/tests/test_extra_dcm2nii.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,15 @@ def assert_dwi(eg, bids):
4141
# ensure all outputs are of equal lengths
4242
assert len(set(map(len, outputs))) == 1
4343
else:
44-
assert not eg2.outputs.bids
44+
assert not eg.outputs.bids
4545

4646
dcm = Dcm2niix()
4747
dcm.inputs.source_dir = datadir
4848
dcm.inputs.out_filename = '%u%z'
49-
eg1 = dcm.run()
50-
assert_dwi(eg1, True)
49+
assert_dwi(dcm.run(), True)
5150

5251
# now run specifying output directory and removing BIDS option
5352
outdir = tmpdir.mkdir('conversion').strpath
5453
dcm.inputs.output_dir = outdir
5554
dcm.inputs.bids_format = False
56-
eg2 = dcm.run()
57-
assert_dwi(eg2, False)
55+
assert_dwi(dcm.run(), False)

0 commit comments

Comments
 (0)