Skip to content

Commit 6ecd4a9

Browse files
committed
ref: dcm2niix interface
1 parent 04aa3c5 commit 6ecd4a9

File tree

3 files changed

+127
-42
lines changed

3 files changed

+127
-42
lines changed

nipype/interfaces/dcm2nii.py

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ class Dcm2niixInputSpec(CommandLineInputSpec):
251251
argstr="%s",
252252
position=-1,
253253
copyfile=False,
254+
deprecated='1.0.2',
255+
new_name='source_dir',
254256
mandatory=True,
255257
xor=['source_dir'])
256258
source_dir = Directory(
@@ -260,16 +262,28 @@ class Dcm2niixInputSpec(CommandLineInputSpec):
260262
mandatory=True,
261263
xor=['source_names'])
262264
out_filename = traits.Str(
263-
'%t%p', argstr="-f %s", usedefault=True, desc="Output filename")
265+
argstr="-f %s",
266+
desc="Output filename")
264267
output_dir = Directory(
265-
exists=True, argstr='-o %s', genfile=True, desc="Output directory")
268+
os.getcwd(),
269+
usedefault=True,
270+
exists=True,
271+
argstr='-o %s',
272+
desc="Output directory")
266273
bids_format = traits.Bool(
267-
True, argstr='-b', usedefault=True, desc="Create a BIDS sidecar file")
274+
True,
275+
argstr='-b',
276+
usedefault=True,
277+
desc="Create a BIDS sidecar file")
278+
anon_bids = traits.Bool(
279+
argstr='-ba',
280+
requires=["bids_format"],
281+
desc="Anonymize BIDS")
268282
compress = traits.Enum(
269-
'i', ['y', 'i', 'n'],
283+
'y', 'i', 'n', '3',
270284
argstr='-z %s',
271285
usedefault=True,
272-
desc="Gzip compress images - [y=pigz, i=internal, n=no]")
286+
desc="Gzip compress images - [y=pigz, i=internal, n=no, 3=no,3D]")
273287
merge_imgs = traits.Bool(
274288
False,
275289
argstr='-m',
@@ -279,16 +293,39 @@ class Dcm2niixInputSpec(CommandLineInputSpec):
279293
False,
280294
argstr='-s',
281295
usedefault=True,
282-
desc="Convert only one image (filename as last input")
296+
desc="Single file mode")
283297
verbose = traits.Bool(
284-
False, argstr='-v', usedefault=True, desc="Verbose output")
298+
False,
299+
argstr='-v',
300+
usedefault=True,
301+
desc="Verbose output")
285302
crop = traits.Bool(
286-
False, argstr='-x', usedefault=True, desc="Crop 3D T1 acquisitions")
303+
False,
304+
argstr='-x',
305+
usedefault=True,
306+
desc="Crop 3D T1 acquisitions")
287307
has_private = traits.Bool(
288308
False,
289309
argstr='-t',
290310
usedefault=True,
291311
desc="Flag if text notes includes private patient details")
312+
compression = traits.Enum(
313+
1, 2, 3, 4, 5, 6, 7, 8, 9,
314+
argstr='-%s',
315+
desc="Gz compression level (1=fastest, 9=smallest)")
316+
comment = traits.Str(
317+
argstr='-c %s',
318+
desc="Comment stored as NIfTI aux_file")
319+
ignore_deriv = traits.Bool(
320+
argstr='-i',
321+
desc="Ignore derived, localizer and 2D images")
322+
series_numbers = InputMultiPath(
323+
traits.Str(),
324+
argstr='-n %s',
325+
desc="Selectively convert by series number - can be used up to 16 times")
326+
philips_float = traits.Bool(
327+
argstr='-p',
328+
desc="Philips precise float (not display) scaling")
292329

293330

294331
class Dcm2niixOutputSpec(TraitedSpec):
@@ -306,27 +343,25 @@ class Dcm2niix(CommandLine):
306343
307344
>>> from nipype.interfaces.dcm2nii import Dcm2niix
308345
>>> converter = Dcm2niix()
309-
>>> converter.inputs.source_names = ['functional_1.dcm', 'functional_2.dcm']
346+
>>> converter.inputs.source_dir = '.'
310347
>>> converter.inputs.compress = 'i'
311348
>>> converter.inputs.single_file = True
312-
>>> converter.inputs.output_dir = '.'
313349
>>> converter.cmdline # doctest: +SKIP
314-
'dcm2niix -b y -z i -x n -t n -m n -f %t%p -o . -s y -v n functional_1.dcm'
350+
'dcm2niix -b y -z y -x n -t n -m n -s y -v n .'
315351
316352
>>> flags = '-'.join([val.strip() + ' ' for val in sorted(' '.join(converter.cmdline.split()[1:-1]).split('-'))])
317353
>>> flags
318-
' -b y -f %t%p -m n -o . -s y -t n -v n -x n -z i '
354+
' -b y -m n -s y -t n -v n -x n -z y '
319355
"""
320356

321357
input_spec = Dcm2niixInputSpec
322358
output_spec = Dcm2niixOutputSpec
323359
_cmd = 'dcm2niix'
324360

325361
def _format_arg(self, opt, spec, val):
326-
if opt in [
327-
'bids_format', 'merge_imgs', 'single_file', 'verbose', 'crop',
328-
'has_private'
329-
]:
362+
bools = ['bids_format', 'merge_imgs', 'single_file', 'verbose', 'crop',
363+
'has_private', 'anon_bids', 'ignore_deriv', 'philips_float']
364+
if opt in bools:
330365
spec = deepcopy(spec)
331366
if val:
332367
spec.argstr += ' y'
@@ -338,14 +373,16 @@ def _format_arg(self, opt, spec, val):
338373
return super(Dcm2niix, self)._format_arg(opt, spec, val)
339374

340375
def _run_interface(self, runtime):
341-
new_runtime = super(Dcm2niix, self)._run_interface(runtime)
376+
# may use return code 1 despite conversion
377+
runtime = super(Dcm2niix, self)._run_interface(
378+
runtime, correct_return_codes=(0, 1, ))
342379
if self.inputs.bids_format:
343380
(self.output_files, self.bvecs, self.bvals,
344-
self.bids) = self._parse_stdout(new_runtime.stdout)
381+
self.bids) = self._parse_stdout(runtime.stdout)
345382
else:
346383
(self.output_files, self.bvecs, self.bvals) = self._parse_stdout(
347-
new_runtime.stdout)
348-
return new_runtime
384+
runtime.stdout)
385+
return runtime
349386

350387
def _parse_stdout(self, stdout):
351388
files = []
@@ -359,11 +396,7 @@ def _parse_stdout(self, stdout):
359396
out_file = None
360397
if line.startswith("Convert "): # output
361398
fname = str(re.search('\S+/\S+', line).group(0))
362-
if isdefined(self.inputs.output_dir):
363-
output_dir = self.inputs.output_dir
364-
else:
365-
output_dir = self._gen_filename('output_dir')
366-
out_file = os.path.abspath(os.path.join(output_dir, fname))
399+
out_file = os.path.abspath(fname)
367400
# extract bvals
368401
if find_b:
369402
bvecs.append(out_file + ".bvec")
@@ -372,16 +405,11 @@ def _parse_stdout(self, stdout):
372405
# next scan will have bvals/bvecs
373406
elif 'DTI gradients' in line or 'DTI gradient directions' in line or 'DTI vectors' in line:
374407
find_b = True
375-
else:
376-
pass
377408
if out_file:
378-
if self.inputs.compress == 'n':
379-
files.append(out_file + ".nii")
380-
else:
381-
files.append(out_file + ".nii.gz")
409+
ext = '.nii' if self.inputs.compress == 'n' else '.nii.gz'
410+
files.append(out_file + ext)
382411
if self.inputs.bids_format:
383412
bids.append(out_file + ".json")
384-
continue
385413
skip = False
386414
# just return what was done
387415
if not bids:
@@ -397,8 +425,3 @@ def _list_outputs(self):
397425
if self.inputs.bids_format:
398426
outputs['bids'] = self.bids
399427
return outputs
400-
401-
def _gen_filename(self, name):
402-
if name == 'output_dir':
403-
return os.getcwd()
404-
return None

nipype/interfaces/tests/test_auto_Dcm2niix.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,21 @@
55

66
def test_Dcm2niix_inputs():
77
input_map = dict(
8+
anon_bids=dict(
9+
argstr='-ba',
10+
requires=['bids_format'],
11+
),
812
args=dict(argstr='%s', ),
913
bids_format=dict(
1014
argstr='-b',
1115
usedefault=True,
1216
),
17+
comment=dict(argstr='-c %s', ),
1318
compress=dict(
1419
argstr='-z %s',
1520
usedefault=True,
1621
),
22+
compression=dict(argstr='-%s', ),
1723
crop=dict(
1824
argstr='-x',
1925
usedefault=True,
@@ -26,6 +32,7 @@ def test_Dcm2niix_inputs():
2632
argstr='-t',
2733
usedefault=True,
2834
),
35+
ignore_deriv=dict(argstr='-i', ),
2936
ignore_exception=dict(
3037
deprecated='1.0.0',
3138
nohash=True,
@@ -35,14 +42,13 @@ def test_Dcm2niix_inputs():
3542
argstr='-m',
3643
usedefault=True,
3744
),
38-
out_filename=dict(
39-
argstr='-f %s',
40-
usedefault=True,
41-
),
45+
out_filename=dict(argstr='-f %s', ),
4246
output_dir=dict(
4347
argstr='-o %s',
44-
genfile=True,
48+
usedefault=True,
4549
),
50+
philips_float=dict(argstr='-p', ),
51+
series_numbers=dict(argstr='-n %s', ),
4652
single_file=dict(
4753
argstr='-s',
4854
usedefault=True,
@@ -56,7 +62,9 @@ def test_Dcm2niix_inputs():
5662
source_names=dict(
5763
argstr='%s',
5864
copyfile=False,
65+
deprecated='1.0.2',
5966
mandatory=True,
67+
new_name='source_dir',
6068
position=-1,
6169
xor=['source_dir'],
6270
),
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import os
2+
import pytest
3+
4+
from nipype.interfaces.dcm2nii import Dcm2niix
5+
no_datalad = False
6+
try:
7+
from datalad import api # to pull and grab data
8+
from datalad.support.exceptions import IncompleteResultsError
9+
except ImportError:
10+
no_datalad = True
11+
12+
DICOM_DIR = 'http://datasets-tests.datalad.org/dicoms/dcm2niix-tests'
13+
14+
15+
def fetch_data(tmpdir, dicoms):
16+
"""Fetches some test DICOMs using datalad"""
17+
data = os.path.join(tmpdir, 'data')
18+
api.install(path=data, source=DICOM_DIR)
19+
data = os.path.join(data, dicoms)
20+
api.get(path=data)
21+
return data
22+
23+
@pytest.mark.skipif(no_datalad, reason="Datalad required")
24+
def test_dcm2niix_dwi(tmpdir):
25+
tmpdir.chdir()
26+
try:
27+
datadir = fetch_data(tmpdir.strpath, 'Siemens_Sag_DTI_20160825_145811')
28+
except IncompleteResultsError as exc:
29+
pytest.skip("Failed to fetch test data: %s" % str(exc))
30+
31+
def assert_dwi(eg, bids):
32+
"Some assertions we will make"
33+
assert eg.outputs.converted_files
34+
assert eg.outputs.bvals
35+
assert eg.outputs.bvecs
36+
outputs = [y for x,y in eg.outputs.get().items()]
37+
if bids:
38+
# ensure all outputs are of equal lengths
39+
assert len(set(map(len, outputs))) == 1
40+
else:
41+
assert not eg2.outputs.bids
42+
43+
dcm = Dcm2niix()
44+
dcm.inputs.source_dir = datadir
45+
dcm.inputs.out_filename = '%u%z'
46+
eg1 = dcm.run()
47+
assert_dwi(eg1, True)
48+
49+
# now run specifying output directory and removing BIDS option
50+
outdir = tmpdir.mkdir('conversion').strpath
51+
dcm.inputs.output_dir = outdir
52+
dcm.inputs.bids_format = False
53+
eg2 = dcm.run()
54+
assert_dwi(eg1, False)

0 commit comments

Comments
 (0)